Diving into Abstract Classes!


I fully admit, while I knew the basics of interfaces in PHP, I couldn’t really see much use for them.  They just didn’t seem to have much purpose to me, so I mostly ignored their existing.  Until now!

While working on the redo of one of our core applications, AgriLife People (ALP), it finally clicked on how they can be useful!  So, one big aspect of ALP is a system for submitting requests to modify people, positions, and/or IT services.  These are done through over a dozen “wizards”, which guide the requestor through several pages to complete the request, with some pages’ form fields being determined by previous entries.  Some of these requests include requirements for approvals from one or more persons, and sometimes requests are added by backend systems rather than users.

These wizards are all under a single module, and while each one has its own unique combination of what it involves, they also have some bits that are common across most, if not all, of them.  For example, everyone needs to check the requestor’s access to ensure they have the necessary rights to work on the person they are trying to submit a request for, and that said person isn’t locked to changes due to an existing request waiting to process.

As I was starting work on the first one, I realized that an interface could be a useful way to ensure that all of the individual wizard controllers, and possibly other aspects as well, follow some minimal, consistent guidelines to improve code quality, maintainability, and ensure that other parts of the module that works with all the wizard results can properly get expected stuff. For the short explanation, interfaces are special “classes” that describe specific methods that any class that implements it must implement, without describing how those methods are implemented beyond any required arguments and optional type-hinting.

Having never done an interface, I started reading up on documentation on it and learned about abstract classes.  I hadn’t really heard about them, though I most likely had seen them even if I wasn’t paying much attention to them.  An abstract class is similar to an interface, with one big exception: it can have both abstract methods to just say “hey any class extending me must have these methods with these arguments, at minimum.  Like interfaces, you can also do type-hinting on these abstract methods.  But, unlike an interface, an abstract class can also have methods with bodies that are then usable by any of the child classes that extend it!

For our needs, this was much more fitting than an interface, because certain core methods can go straight in the abstract class versus having to be repeated in each child – meaningless code duplication and much better maintainability!

In this case, all of my wizards need to have at least four things passed in for dependency injection because they are integral to all of them.  So the abstract class controller has an abstract construct that dictates those four required items to be included in each child wizard construct.  But this also allows each child to have more dependencies injected if needed while ensuring the same four are always there.

<?php
    namespace PendingRequest\Controller;

    abstract class AbstractWizardController extends AbstractActionController
    {

        // bare minimum construct all wizard controllers should have
        abstract public function __construct(
            Converter $converter,
            PendingRequestRepository $pending_request_repository,
            PersonRepository $person_repository,
            Container $session_container
        );

And while that part is also doable with the interface, the abstract class lets us have the access check method noted earlier in one place, along with some other basic methods for the wizards: setting, retrieving, and unsetting session variables, getting the request entity in progress, checking if a wizard is in progress or not, and so fore.

        protected function getSesssionValue(string $value_to_retrieve)
        {
            return $this->session_container->$value_to_retrieve;
        }

        // cleans out all the wizard session variables
        protected function resetWizard() : void
        {
            $this->session_container->getManager()->getStorage()->clear();
        }

        protected function setSessionValue(string $value_to_set, $content_to_set) : void
        {
            $this->session_container->$value_to_set = $content_to_set;
        }

        protected function unsetSessionValue(string $value_to_unset) : void
        {
            unset($this->session_container->$value_to_unset);
        }

Now, one thing I should note that I learned after a good bit of frustration: with abstract methods, you can indicate required arguments while the child can require additional ones after those.  I’d seen it in the documentation, and thus with my first child wizard controller, I had another dependency to inject: a form that was created with a factory due to needing to do some repository calls.

So I added it…and then got an error saying the child wasn’t matching the signature of its parent.  I was like “what!? the docs clearly say this is allowed”!  I checked the PHP docs again and it confirmed that yes, optional arguments are fine after the required ones.  So I had no idea why I was getting an error.

Finally, after a bit of Googling, I spotted a quick little note on one site talking about abstract classes: any optional arguments for the child’s method must have a default value to make the signature match!  Ah-ha!  Added a default of null to my optional argument and all good.

        public function __construct($converter, $pending_request_repository, $person_repository, $session_container, $form = null)
        {
            $this->converter = $converter;
            $this->pending_request_repository = $pending_request_repository;
            $this->person_repository = $person_repository;
            $this->session_container = $session_container;
            $this->form = $form;
        }

I’m still working on fleshing this all out, of course, as these wizards will take weeks of work, but it is great to learn something new and I’m really looking forward to seeing how it turns out in the end.  I think this will help make adding new wizards in the future easier as they will be far more consistent, but flexible, and should help reduce the need for new code.

And with that…back to coding 🙂