netcarver Posted March 10, 2022 Share Posted March 10, 2022 Hello, I'm starting to play catch-up with some of the recently (and not-so-recently) added features to PW. In particular, I'd like to ask about more complex uses of Page Classes. I have a site where the end users are actual PW Users with first and last name fields. I also have several other templates in the system that have the first and last name fields but they are not actually Users of the system, rather they are entities like Contacts or other personages that can be also be named. I thought I'd enable page classes and add a simple getName() method to the UserPage class to allow their name fields to be nicely concatenated. In itself, no problem, as long as I remember to extend from User. So here's exhibit A: UserPage.php... <?php namespace ProcessWire; class UserPage extends User { function getName() { return trim($this->first_name . ' ' . $this->last_name); } } This works fine, I can call getName() on my users, nice. Now, my other nameable entities need this function too. I can extend from UserPage to inherit the getName() function, which works well and is straightforward... <?php namespace ProcessWire; class ContactPage extends UserPage { } So I can now use $contact->getName() as well as $user->getName(). But, my contacts aren't really system users, so I'd prefer not to inherit from UserPage at all, but either inherit from some intermediate class representing some form of NameableEntity or else use traits to compose the functionality into the individual page classes which need it. However, I'm hitting snags with both approaches and would appreciate some feedback.Attempt at Traits So I add the following as site/classes/NameHandlingTrait.php... <?php namespace ProcessWire; trait NameHandlingTrait { public function getName() { return trim($this->first_name . ' ' . $this->last_name); } } And switch my ContactPage.php over to using it... <?php namespace ProcessWire; class ContactPage extends Page { use NameHandlingTrait; } And that works. I can access $contact->getName(); (I actually did it by setting up the wire autoloader in ready.php to look for a specific namespace in a traits/ subdirectory - but I'll skip showing that here, it did work for my page classes that extend from Page.) However, when I try switching UserPage.php over to the same trait code to keep it DRY... <?php namespace ProcessWire; class UserPage extends User { use NameHandlingTrait; } I immediately hit the following error: "Fatal Error: Trait 'NameHandlingTrait' not found (line 5 of /site/classes/UserPage.php)" either when visiting the site (front end or admin) or by bootstrapping from the CLI. I can overcome this by manually requiring/including the trait file at the start of UserPage.php - but I'd much rather autoload it if at all possible. No matter how early I try to declare the extension of the class loader to attempt to autoload the trait, it isn't soon enough and I always hit the fatal error shown above. Can anyone shed any light on this? How do you go about using the PageClasses, especially UserPage, when the structure might need to take more of a composition-rather-than-inheritance form? Also, if you have any suggestions as to other places to get autoloading set up in PW prior to init.php or ready.php I'm all ears. I tried setting up the autoloader mappings in both places and they work for classes derived from Page but not for UserPage - so I suspect PW is creating a User instance pretty early on in its bootstrap, well before it is loading in extensions to the autoloader. I'll try adding a PSR-4 clause to composer.json and dumping the composer autoloader next - but I'd rather stick with native PW autoloading if possible.Intermediate Base Class The other approach I've tried is to use an intermediate base class to house the getName() function and inherit from that. This is doable for the classes derived from Pages but again, I hit the snag that the UserPage seems to have to extend User, and not my base class.@ryan What would you recommend here? Could UserPage extend something other than User? 5 Link to comment Share on other sites More sharing options...
thetuningspoon Posted March 10, 2022 Share Posted March 10, 2022 Have you tried adding the namespace to the autoloader inside site/init.php instead of site/ready.php? wire('classLoader')->addNamespace('ProcessWire', wire('config')->paths->templates . "traits/"); If you have usePageClasses set to true in the config.php and are using the /site/classes/ folder then I would think this wouldn't even be necessary since that should already be included in the places for the autoloader to look. I haven't come across the need for traits yet, although I keep forgetting that they exist in php now. When I think of composition I usually think of moving certain functionality out into a separate class that would then be instantiated somewhere inside the other class. You can create classes that don't extend Page (perhaps that that extend Wire or WireData if you want easy API access) and then use those in your Page derived class. Your example is probably intentionally oversimplified, but sometimes I find that sometimes a bit of duplicated functionality is really just incidental and doesn't actually justify creating a new layer of inheritance or a separate class at all, especially as you might later discover that the two functions actually serve slightly different purposes and at that point are tied together and difficult to pull apart. You could also use a PW hook to add this function to both classes, although it wouldn't be as elegant. 2 Link to comment Share on other sites More sharing options...
netcarver Posted March 10, 2022 Author Share Posted March 10, 2022 Thanks for the reply, @thetuningspoon I did try extending the autoloader in init.php instead of ready.php but it seemed to make no difference. The only thing that seemed to work for me was a direct include of the trait file from within site/classes/UserPage.php. Will try again though in case I did something wrong first attempt. No, still getting that error when it comes to autoloading for UserPage regardless of the location of the extension to the autoload mechanism. Quote You could also use a PW hook to add this function to both classes, although it wouldn't be as elegant. Actually, that might work fairly well in this case, though I'd really like to be able to get to the bottom of the autoloading. 1 Link to comment Share on other sites More sharing options...
netcarver Posted March 10, 2022 Author Share Posted March 10, 2022 Update: I can get the trait composition to work by adding a psr-4 clause to the autoload part of the root composer.json and then running composer dump-autoload from the command line. Not ideal, but seems to be the only way I can get the autoload information in to PW in time for it to use it when creating the UserPage class. Link to comment Share on other sites More sharing options...
Zeka Posted March 11, 2022 Share Posted March 11, 2022 @netcarver Have you tried using boot.php? In site/config.php $config->statusFiles = array( 'boot' => 'boot.php', 'init' => 'init.php', 'ready' => 'ready.php', 'finished' => 'finished.php' ); and in boot.php <?php namespace ProcessWire; wire('classLoader')->addNamespace('Wireframe\Blocks', paths('templates') . 'blocks/'); wire('classLoader')->addNamespace('Wireframe\Traits', paths('templates') . 'traits/'); I don't remember if I used traits in pages classes or in some other parts of the code, but you can try. 11 Link to comment Share on other sites More sharing options...
netcarver Posted March 11, 2022 Author Share Posted March 11, 2022 @Zeka Thank you, I will give that a try as well. Updated to add: Yes! Using boot.php works nicely, I can now stick with PWs own autoload mechanism, thank you for the suggestion. 4 Link to comment Share on other sites More sharing options...
thetuningspoon Posted March 11, 2022 Share Posted March 11, 2022 Great. I forgot (or maybe never knew?) that boot.php existed! 2 Link to comment Share on other sites More sharing options...
Alpine418 Posted 6 hours ago Share Posted 6 hours ago Hi guys. I've the same issue. My trait classes in sites/classes won't get loaded. I'm getting a not found fatal error. What is the solution to get traits outloaded by ProcessWire itself? 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