Recently, as you may know, I’ve been building a small user authentication module for an upcoming tutorial for a European magazine. As part of developing the application, I followed my standard approach of using Mezzio’s Command Line Tooling to quickly bootstrap handlers and middleware, such as by running the following command.
composer mezzio mezzio:handler:create "App\Handler\ForgotPasswordHandler"
If you’re not familiar with Mezzio’s command line tooling, the above command generates a PSR-15 Handler class and a factory to instantiate it, registers the handler with the application’s DI Container, and creates a view template for it.
Using the command greatly simplifies Handler creation, as it avoids a large number of potential mistakes and bugs that may creep in when creating them manually. Moreover, you know that the files will be created in a consistent manner. However, while the command brings a lot of efficiency to Handler creation, depending on your use case, it may create more work than necessary.
This might seem like a strange thing to say, given my support for the command, but hear me out. Perhaps you’re creating a Handler, and it’s rapidly evolving as the needs themselves evolve. For example, you might be building a Handler that performs user authentication, retrieves content from a social media service such as LinkedIn or Twitter, or provides some other service that requires an ever-growing number of constructor parameters.
In this case, as you iteratively refactor the Handler class you also have to iterate on the Handler’s factory. Otherwise, an exception will be thrown when attempting to retrieve the service from the DI container or when attempting to use it. Yes, it’d be great to have requirements mapped out clearly and concretely ahead of time. But that’s not always practical nor possible.
So, what about iteratively refactoring the handler as the needs of the project evolve without needing to keep refactoring the handler’s factory class? Better yet, why not skip creating a custom factory altogether?
At first blush, this might seem like a ludicrous thing to say, no? After all, if there’s no factory for the handler, how can it be instantiated? Fair point.
However, if you revisit what I said, I only said skip creating a custom factory. You still need a factory to retrieve the constructor parameters so that the class can be instantiated. But that doesn’t mean that you have to create said factory yourself.
If your Mezzio application is using laminas-servicemanager as it’s DI Container, you’re in luck. Enter the Reflection-based Abstract Factory (or Reflection Factory). This class uses PHP’s Reflection API to introspect which constructor parameters are required and, where possible, retrieve them from the DI Container (if they’ve been registered).
Worried about performance?
Given that the class uses reflection, you may have concerns about application performance, as the Reflection API does have a performance overhead. It’s a fair concern for a production application, but during development, I’d suggest not.
Remember, the recommendation to use the Reflection-based Abstract Factory is only during initial development. That way your energies can stay on the Handler’s logic not instantiating it.
Then, after the Handler’s state becomes stable enough, you can use a more performant factory, such as a custom one or perhaps the Config Abstract Factory.
Some things to bear in mind about the Reflection Factory
That said, there are a few things to bear in mind when using it:
If a class' constructor has a parameter named
$configtype-hinted as an array, it will receive the application’s "config" service (i.e., the merged configuration).
An empty array will be injected for parameters type-hinted as an array but not named
Scalar parameters will result in the factory raising an exception, unless a default value is present. If it is, however, that value will be used.
If a service cannot be found for a given typehint, the factory will raise an exception detailing this.
How do you use it?
To use the Reflection-based Abstract Factory instead of a custom factory you just need to use it to instantiate the handler when registering the handler with the DI container.
In the relevant module’s ConfigProvider.php file, in the array returned from the
getDependencies() method, add your Handler class to the
factories element setting the
ReflectionBasedAbstractFactory to instantiate it.
'factories' => [ Handler\ForgotPasswordHandler::class => ReflectionBasedAbstractFactory::class, ],
That’s how to save time instantiating Mezzio Handlers by using the Reflection-based Abstract Factory
If you’re rapidly prototyping application, I highly recommend using the Reflection-based Abstract Factory so that you can concentrate on the Handler’s logic and not worry about instantiation, until the code’s semi-stable.
I’d love to know what you think in the comments.