I’m not moaning about the FIG! Horray!
I wanted to introduce the new ConfigAbstractFactory
that has been written for Zend\ServiceManager 3
and got merged to develop today and will be included in the next 3.2.0
release of the ServiceManager.
I’ve been messing around with Laravel a bit lately. While I didn’t get to give Laravel the actual, real life project I was hoping to when I blogged a few months ago, I’ve been reading much more about it and generally been more interested in how things are done. Laravel has shown us that developer usability is a real thing and that by making things easier for your target audience you gain traction and Good Things Happen.
This is why in response to an issue on the Service Manager repository, I’ve written the catchily named Config Abstract Factory. Essentially, it allows you to create service factories from configuration rather than having to write all the code.
I’m a big fan of Zend Framework’s “configuration over magic” philosophy, but it can make writing factories for all your classes either incredibly time consuming (factory classes for everything), or incredibly unmaintainable (using closures). Life’s too short to create a factory class for every single class that has even the simplest dependency.
Say hello to the Config Abstract Factory that solves the problem by allowing you to supply a configuration map of how your dependencies look, and it sorts out creating your services for you. It’s not magic; it’s still configuration but it speeds up development in that you don’t have to go through the painful act of creating brand new classes that effectively are:
namespace My\Module; use My\Module\Cache; use My\Module\Logger; class UserServiceFactory { public function __invoke(Container $container) { return new UserService( $container->get('UserServiceTable'), $container->get(Cache::class), $container->get(Logger::class) ); } }
It’s not for everyone, but for me, and my well publicised lazy tendancies it’s a big step forward. I’m also convinced that it lowers the barrier to entry as understanding how to create factories for the service manager is not the easiest for new users to comprehend.
To use the Config Abstract Factory, it first needs to be enabled. You can do this is the same way that you’d enable any other abstract factory (by adding it to the abstract_factories
key in a config somewhere).
// module.config.php use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory; return [ 'service_manager' => [ 'abstract_factories' => [ ConfigAbstractFactory::class, ], ], ];
Now that the abstract factory is enabled, it will look for dependency configuration in the Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory
key of the merged config (or anything in the config
key if you’re using the abstract factory stand-alone). The configuration is done in an array (because ZF), and is a simple list of the container keys to retrieve to fulfil the dependency. This sounds a lot more complex in theory than it does in practice. Take the above UserService, with it’s dependencies on UserServiceTable, Logger and Cache, it can easily be modelled with the config:
// module.config.php use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory; return [ ConfigAbstractFactory::class => [ UserService::class => [ // key name of the service we are configuring 'UserTable', // not configured here, it's another abstract factory Logger::class, // traditional factory configured in another module, Cache::class, // Configured as another class below ], Cache::class => [], // replaces 'invokables' or 'InvokableFactory' ], 'service_manager' => [ 'abstract_factories' => [ ConfigAbstractFactory::class, TableGatewayAbstractFactory::class, ], ], ];
You can see that the dependency list is just the names of the keys to request in this service manager (or plugin manager), the abstract factory doesn’t care how the container creates these keys, which means that traditional factories, invokables (now deprecated), aliases, and other abstract factories can all be used to fulfil the dependencies. You can check the documentation for information.
Going forward, I’d love to write a couple of command line tools to help the experience. Firstly, it would be amazing if a tool existed that automatically created the config array for a given dependency:
php vendor/bin/configaf add "\App\Service\UserService"
This should load a configured service manager and find the missing dependencies. It would then add those dependencies to the ConfigAbstractFactory key to enable everything to work just-so. This is a reasonably challenging job but something I would be very interested to tackle.
I also think that there is value in a command line tool that takes the configurtion and creates “real” factories from them. This would be a performance win when you are building the app for production, and makes things more manageable going forward (full factories are the holy grail for maintainability for me). I’m not convinced at this idea yet, but it’s interesting.
Edit: After discussion with Rob Allen a happy medium may be a script that adds the abstract factory as a factory key to all the services that need it – this gives you the performance gain without the need for all the classes. I still think for maximum performance and maintainability you’d probably want all the classes generated.
Let me know if you use the ConfigAbstractFactory – I’m itching to know what people think.