If you’re creating handlers in a Mezzio application and don’t want to spend time building and maintaining custom factories to instantiate them (or other classes), you need to know about the Reflection Factory.
:idseparator: -
:idprefix:
:experimental:
:source-highlighter: rouge
:rouge-style: pastie
:imagesdir: /images
Recently, https://twitter.com/settermjd/status/1531299966680965120[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 https://docs.mezzio.dev/mezzio/v3/reference/cli-tooling/[Mezzio's Command Line Tooling] to quickly bootstrap handlers and middleware, such as by running the following command.
[source,php]
composer mezzio mezzio:handler:create “App\Handler\ForgotPasswordHandler”
If you’re not familiar with Mezzio’s command line tooling, the above command generates https://www.php-fig.org/psr/psr-15/#11-request-handlers[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 https://docs.laminas.dev/laminas-servicemanager/quick-start/[laminas-servicemanager] as it’s DI Container, you’re in luck.
Enter https://docs.laminas.dev/laminas-servicemanager/reflection-abstract-factory/[the Reflection-based Abstract Factory (or Reflection Factory)].
This class uses https://www.php.net/manual/en/intro.reflection.php[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 https://docs.laminas.dev/laminas-servicemanager/config-abstract-factory/[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
$config
type-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
$config
.
- 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.
Want to Learn More About Mezzio?
Mezzio Essentials teaches you the fundamentals of PHP's Mezzio framework — the fundamentals that you need — to begin building get paid for applications with the Mezzio framework right away. It’s a practical, hands-on approach, which shows you just enough of about the underlying principles and concepts before stepping you through the process of creating an application.
Join the discussion
comments powered by Disqus