Jonathan Lahijani Posted September 3 Share Posted September 3 Let's say I have the following pages with these names that use the "company" template: microsoft apple google Let's say my page class has a method called 'foo'. I want the code that's run to be dependent on the page name, but I don't want to do it like this: public function foo() { if($this->name=="microsoft") { // code for microsoft } elseif($this->name=="apple") { // code for apple } elseif($this->name=="google") { // code for google } } Instead, I want each company to have their own class (but keep the pages as 'company' class -- don't touch!) with each method implemented within that class. How would you go about this? I solved this by adding a property to the Page class using property hooks which has access to the respective method (what's this technique called? is there a name for it?), but wondering if there's a better way: class MicrosoftCompanyTools { public function foo() { echo "test"; } } wire()->addHookProperty('CompanyPage::tools', function($event) { $company = $event->object; $companyClassName = "ProcessWire\\" . ucfirst(str_replace('-', '', $company->name))."CompanyTools"; if(!class_exists($companyClassName)) $event->return = false; $event->return = new $companyClassName(); }); // example call $pages->get('template=company,name=microsoft')->tools->foo(); Link to comment Share on other sites More sharing options...
da² Posted September 3 Share Posted September 3 (edited) Hi, To be clean I wouldn't use the page name. Name purpose is essentially for url management, not for "company"'s one. 🙂 Name can change in the future, but the company associated with the page won't. So I would create, maybe a select options with company names and ID. Or if these tools have properties that need to be stored in PW fields, I would create a tools template and tools page references on the company page. For the code part (using a select options), I don't like using hooks when it's not necessary, hooks make code less clear and harder to maintain on long term. So my approach is like good old classic and verbose OOP. 😄 I wrote this on a simple text editor, so maybe mistakes... /** * Values of this enum must be also set in select options values. */ enum CompanyToolsType:int { case MICROSOFT = 1; case GOOGLE = 2; } interface CompanyTools { public function foo():void; } class MicrosoftTools implements CompanyTools { public function foo():void { echo "I'm a Microsoft tool"; } } class GoogleTools implements CompanyTools { public function foo():void { echo "I'm a Google tool"; } } class CompanyToolsFactory { public static function getTools(CompanyToolsType $type):CompanyTools { return match($type) { CompanyToolsType::MICROSOFT => new MicrosoftTools(), CompanyToolsType::GOOGLE => new GoogleTools(), }; } } /** * @property $companyTypeSelect I don't remember what type it is (Select Options field with single selected value) */ class CompanyPage extends Page { public function getTools():CompanyTools { return CompanyToolsFactory::getTools($this->getToolsType()); } public function getToolsType():CompanyToolsType { // TODO Can throw ValueError return CompanyToolsType::from(intval($this->companyTypeSelect->value)); } } Edited September 4 by da² 2 Link to comment Share on other sites More sharing options...
da² Posted September 3 Share Posted September 3 (edited) And in case using page names is mandatory, the changes are: /** * Values of this enum must be the same as company pages names. */ enum CompanyToolsType:string { case MICROSOFT = "microsoft"; case GOOGLE = "google"; } class CompanyPage extends Page { //public function getTools... public function getToolsType():CompanyToolsType { // TODO Can throw ValueError return CompanyToolsType::from($this->name); } } You can also remove the enum and ask the factory directly with a string, but enum adds a layer of error checking. Same for factory, it can be removed and code moved to CompanyPage, the factory primarily helps with better separation of responsibilities. Edited September 3 by da² 1 Link to comment Share on other sites More sharing options...
poljpocket Posted September 3 Share Posted September 3 (edited) As @da² said, you shouldn't rely on page names like that in your code since names can change and also, you shouldn't prevent that (for SEO, localization and other reasons). The next best thing is to use different templates for each of your companies. With that, you get custom page classes for each of them and your challenge solves itself. Doubling down on your code though, why not use a new method in your custom page class, e.g. <?php namespace ProcessWire; class CompanyPage extends Page { // [...] public function tools() { $companyClassName = "ProcessWire\\" . ucfirst(str_replace('-', '', $this->name))."CompanyTools"; if(!class_exists($companyClassName)) return false; $event->return = new $companyClassName(); } // [...] } instead of the hook? You can also go all in on the PW style using hooks: <?php namespace ProcessWire; wire()->addHookProperty('CompanyPage(name=microsoft)::tools', function($event) { $event->return = new MicrosoftCompanyTools(); }); wire()->addHookProperty('CompanyPage(name=google)::tools', function($event) { $event->return = new GoogleCompanyTools(); }); wire()->addHookProperty('CompanyPage(name=apple)::tools', function($event) { $event->return = new AppleCompanyTools(); }); // [...] .. but this removes some of the flexibility of the class_exists call above. Edited September 3 by poljpocket fix hooks Link to comment Share on other sites More sharing options...
Jonathan Lahijani Posted September 3 Author Share Posted September 3 Thanks for the replies. I agree that using the page name has a bit of an "ick" factor, but the name won't change and I can lock the field regardless. I am purposely trying not to create a new specific template for each company, and although that approach is probably more clean and direct, it messes with the following extra part... 32 minutes ago, poljpocket said: Doubling down on your code though, why not use a new method in your custom page class, e.g. I didn't mention in my example that there is also a "Company Types" field where a company can have multiple selections of the following: Provider Fulfiller Biller If the company is a "Provider", then I want the tools method (I've already programmed this but left it out in my example code to keep things simple). Although that could lead to some issues if I tried accessing that method and it's not available unless I did some extra checks, but that's not totally clean as well. Link to comment Share on other sites More sharing options...
da² Posted September 3 Share Posted September 3 (edited) 1 hour ago, Jonathan Lahijani said: If the company is a "Provider", then I want the tools method So not all company pages have tools? Why not adding a method CompanyPage::hasTools():bool? if ($companyPage->hasTools()) $companyPage->getTools()->foo(); 1 hour ago, Jonathan Lahijani said: Although that could lead to some issues if I tried accessing that method and it's not available Yes, methods should always be available. Edited September 4 by da² Link to comment Share on other sites More sharing options...
poljpocket Posted September 4 Share Posted September 4 (edited) In this case, you can use this hook which gets rid of any complicated logic: <?php namespace ProcessWire; wire()->addHookProperty('Page(template=company,company_types=provider)::tools', function($event) { $event->return = new ProviderCompanyTools(); }); Edited September 4 by poljpocket Link to comment Share on other sites More sharing options...
bernhard Posted September 4 Share Posted September 4 13 hours ago, Jonathan Lahijani said: wire()->addHookProperty('CompanyPage::tools', function($event) { $company = $event->object; $companyClassName = "ProcessWire\\" . ucfirst(str_replace('-', '', $company->name))."CompanyTools"; if(!class_exists($companyClassName)) $event->return = false; $event->return = new $companyClassName(); }); // example call $pages->get('template=company,name=microsoft')->tools->foo(); I think this is perfectly fine, but why not add this as a method in your class to get rid of the hook? // in your pageclass public function tools() { // get tools based on page name $toolsClass = ... // return instance of tools return new $toolsClass(); } // usage $pages->get(...)->tools()->foo(); The benefit is that your IDE will understand your code and if you add return types etc. it will help you with code completion. Link to comment Share on other sites More sharing options...
Recommended Posts
Create an account or sign in to comment
You need to be a member in order to leave a comment
Create an account
Sign up for a new account in our community. It's easy!
Register a new accountSign in
Already have an account? Sign in here.
Sign In Now