Reflections on Being Part of the PHP Community
Despite being developing with PHP since a chance encounter in 1999, I’ve only recently gotten myself involved in the community. So here’s a reflection on what’s changed since getting involved.
Zend Expressive, like the Zend Skeleton project before it, is a great platform for building robust, maintainable, highly scalable applications. But it’s generally not the platform you’d think of for rapidly application development. However one little technique can change all that. Today, let’s learn that technique and begin changing your perception.
Zend Expressive, like the Zend Skeleton project before it, is a great platform for building robust, maintainable, highly scalable applications. But it’s generally not the platform you’d think of for rapidly application development.
However one little technique can change all that. Today, let’s learn that technique and begin changing your perception of Zend Framework as a rapid application development platform.
If you’ve got your knowledge of the Zend Framework ServiceManager down pat, you’ll know that it provides several methods for configuration. These are:
Through these, the Zend ServiceManager provides a wealth of possibilities for instantiating objects in your application.
However, if you’ve used most of these, and you’re testing your applications, you’ll know that the amount of code can quickly start to add up. That is, if your application’s anything other than a proverbial one-page application.
Recently I’ve been finding this myself as the application has a number of page actions, input filters and so on. It’s fine when you don’t have too many of them. But as the application grows, not only do you have a large number of classes, but you also have a large number of accompanying tests.
Sure, there’s nothing wrong with absolute flexibility and being able to test each and every component. Actually, it’s generally considered the ideal. But if the cognitive load to manage all those objects becomes prohibitive, is it really worth pursuing?
This is exactly the situation which I found myself in lately. One which I was talking with some colleagues about. I love how I can create, individually, all the components I need, ensure that they’re all instantiated via dependency injection, or secondarily service location.
Given that it’s crystal clear how they’re used. But after not a small period of time, it became increasingly difficult, even frustrating, to keep track of all the configuration steps required to ensure they’re ready to use.
As a result, the initial gains were overshadowed by a rather hefty cognitive load required for remembering how everything was strung together.
I don’t know when exactly I came across it, or who mentioned it to me, but sometime recently, either in Washington or New York, someone really pressed the point with me about using abstract factories wherever possible instead.
So I had a think about it and came up with two use cases for using them. These are:
Lets assume that you have a set of classes which match these criteria, specifically a set of page actions. How do you go about using abstract factories in Zend Expressive. Let’s step through it a piece at a time.
Here’s a page action which will be, for all intents and purposes, the same for a number of other page actions.
namespace App\Action;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Zend\Diactoros\Response\HtmlResponse;
use Zend\Expressive\Router;
use Zend\Expressive\Template;
class AboutPageAction
{
private $router;
private $template;
public function __construct(
Router\RouterInterface $router,
Template\TemplateRendererInterface $template = null
) {
$this->router = $router;
$this->template = $template;
}
public function __invoke(
ServerRequestInterface $request,
ResponseInterface $response,
callable $next = null
) {
$data = [];
return new HtmlResponse(
$this->template->render(
'app::about-page',
$data
)
);
}
}
All that’s different in each class is the name of the template it renders. However, given that it’s internal to the class, and what we’re concerned with is the instantiation of the class, then we don’t need to worry about it.
If you’re experienced with Zend Expressive, you’ll know that the standard approach is to instantiate a PageAction
class via an accompanying PageFactory
class.
Then, to configure the ServiceManager, in config/autoload/dependencies.global.php
, you’ll need to add an entry such as the following in the factories
element:
App\Action\AboutPageAction::class => App\Action\AboutPageFactory::class,
But let’s make that simpler. Assuming that you are still using the App namespace, which comes by default with Zend Expressive, add the directory structure ServiceManager/AbstractFactory
under /src/App/
.
Then, in there, add a new class, called PageActionAbstractFactory.php
. When that’s created, add the following code in to the class, which we’ll step through.
<?php
namespace App\ServiceManager\AbstractFactory;
use Zend\Expressive\Router;
use Zend\Expressive\Template;
use Zend\Expressive\Router\RouterInterface;
use Zend\Expressive\Template\TemplateRendererInterface;
use Zend\ServiceManager\AbstractFactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
class PageActionAbstractFactory implements AbstractFactoryInterface
{
All abstract factory classes need to implement the AbstractFactoryInterface. This way the ServiceManager knows, implicitly, how to work with it.
public function canCreateServiceWithName(
ServiceLocatorInterface $serviceLocator, $name, $requestedName
) {
return (fnmatch('*PageAction', $requestedName));
}
Next we flesh out the first of the two required methods of the interface. This one says whether this abstract factory handles creation of the requested service. We could be much more explicit than I’ve been.
But I don’t think it’s necessary, and have not yet needed to be so. Using PHP’s fnmatch function, we check if the name of the requested service ends with PageAction
. If so, then we return true, otherwise we return false;
public function createServiceWithName(
ServiceLocatorInterface $serviceLocator, $name, $requestedName
) {
if (class_exists($requestedName)) {
$router = $serviceLocator->get(RouterInterface::class);
$template = ($serviceLocator->has(TemplateRendererInterface::class))
? $serviceLocator->get(TemplateRendererInterface::class)
: null;
switch ($requestedName) {
case(\App\Action\AboutPageAction::class):
case(\App\Action\ContactPageAction::class):
return new $requestedName($router, $template);
break;
}
}
return false;
}
}
Next we have the createServiceWithName
function. This does the bulk of the work of actually instantiating the requested service.
Here we’ve taken the core logic of retrieving the RouterInterface
and TemplateRendererInterface
objects from the service locator. Then, via a simple switch statement, instantiated and returned the requested service.
On thinking about it further, as all of the PageActions in this example are have the same constructor signature, the switch statement is redundant. I could just have easily done nothing more than return new $requestedName($router, $template);
and achieved the same net result.
However, perhaps this approach is clearer. If there are some differences, it makes it easier to transition to that point later on.
With the hard work done, we just have one last step to take care of, the ServiceManager configuration. As I covered earlier, previously we’d have had a factories
entry for each PageAction class which we were instantiating. Now that’s not necessary!
To use our abstract factory, we now need to remove all of the relevant factories elements, replacing them with a single abstract_factories
element, which you can see below.
'abstract_factories' => [
App\ServiceManager\AbstractFactory\PageActionAbstractFactory::class
],
Saving that, our PageActions are now ready to go, whether that’s one or 200.
So, have we saved effort by taking this approach? Definitely yes. Admittedly this is a slightly simpler example, as all of the classes have the same constructor signature. Would it have been so simple had the classes had different signatures or other requirements? Perhaps not.
But in each application there are usually classes which are barely different from each other. Given that, to have a host of different factory classes, along with the accompanying tests and ServiceManager configuration is a lot of work for not a lot of gain.
Consequently, I strongly encourage you to consider using an abstract factory approach instead. Not only will you reduce the code you write, test, and maintain, but it’s less of a cognitive load from a configuration perspective as well.
Whether you’re the developer or maintainer, it’ll be easier to create, and easier to get up to speed if you take over the project later.
CC Image (background of main image) Courtesy of Spyros Papaspyropoulos on Flickr
Despite being developing with PHP since a chance encounter in 1999, I’ve only recently gotten myself involved in the community. So here’s a reflection on what’s changed since getting involved.
In part two of the series on testing Zend Framework applications with Codeception, we see how to retrieve and test registered services using BDD-style testing
Recently, I was asked to build an application with a rapid turnaround time and modest budget. Being the sole developer, here’s the approach I took to make the development process as simple and efficient as possible
Testing is essential for creating reliable software. Whether you’re writing a small application for your local club or an application to back your startup, it needs test coverage to ensure it works properly. In this series, I show you how to test Zend Framework 2 applications using the comprehensive testing framework - Codeception.
Please consider buying me a coffee. It really helps me to keep producing new tutorials.
Join the discussion
comments powered by Disqus