How To Generate Dependency Configuration's Easily with ConfigDumper

Want to save time generating dependency configuration files for your Zend ServiceManager dependencies? In today's tutorial, I'll show you how, by using ConfigDumper, available in ServiceManager 3.2.0.

In the previous tutorial, we saw how to use FactoryCreator’s command-line tool, generate-factory-for-class, to quickly and easily create factories for classes.

In this, the follow-up tutorial, we’re going to see how to use generate-deps-for-config-factory, the command-line tool for ConfigDumper, to save time when generating dependency configuration files for use with our classes.

Install ServiceManager 3.2.0

If you haven’t already, make sure that ServiceManager 3.2.0 is already available in your project. To do so, if ServiceManager is a project dependency, update your project’s composer.json to include: zendframework/zend-servicemanager": "^2.7.3 || ^3.2.

After that, run composer update. If ServiceManager’s not an existing dependency, run composer require zendframework/zend-servicemanager, to add it as one, and to make the library available. Either way, after a minute or so, you’ll have the latest release installed, and be ready to follow along with the remainder of the tutorial.

Tool Overview

Now that we’re ready to go,generate-deps-for-config-factory will be available in vendor/bin/. Given that, let’s get some basic familiarity with it. From the command line run vendor/bin/generate-deps-for-config-factory. After doing so, you’ll see the help message below printed out to the console.

Usage:

vendor/bin/generate-deps-for-config-factory [-h|--help|help] <configFile> <className>

Arguments:

  -h|--help|help    This usage message
  <configFile>      Path to a config file for which to generate configuration.
                    If the file does not exist, it will be created. If it does
                    exist, it must return an array, and the file will be
                    updated with new configuration.
  <className>       Name of the class to reflect and for which to generate
                    dependency configuration.

Reads the provided configuration file (creating it if it does not exist),
and injects it with ConfigAbstractFactory dependency configuration for
the provided class name, writing the changes back to the file.

To summarize, you provide the command with two things:

  1. A file to store the generated configuration in
  2. The fully-qualified namespace of the class to create the configuration for

If the configuration file doesn’t already exist, it will be created. If it does, it will be updated to include the newly generated configuration for the class provided, if it can be generated.

Creating A Basic Dependency Configuration

Let’s say that you want to generate a dependency configuration for the PingAction class which comes with projects generated with the Zend Expressive Skeleton Installer. And let’s say that you want to store the configuration in config/autoload/generated-dependencies.php.

Generating a configuration for it will be quite trivial, as it contains no constructor. Yes, it’s a mostly redundant example. But, it’s a good starting point. To do so, you would run the following:

vendor/bin/generate-deps-for-config-factory \
  config/autoload/generated-dependencies.php \
  App\\Action\\PingAction

And after the command completes you’ll see the following message printed to the console:

[DONE] Changes written to config/autoload/generated-dependencies.php

If we look in config/autoload/generated-dependencies.php, we’ll see that the file contains the following:

<?php
return [
    \Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory::class => [
        \App\Action\PingAction::class => [

        ],
    ],
];

All configurations generated by generate-deps-for-config-factory will be elements inside the ConfigAbstractFactory element. The reason for this is that ConfigAbstractFactory is what reads the configuration and loads the classes. We’re going to step through a few more examples first. Then we’ll look at what ConfigAbstractFactory is, and how to use it.

A More Complex Configuration

Now, let’s generate the configuration for a more sophisticated class, one which takes multiple constructor dependencies. Specifically, let’s generate a configuration for the other Action class that comes with Zend Expressive projects: HomePageAction.

As before, we’re going to store the generated configuration in config/autoload/generated-dependencies.php. Given that, the command we’ll run is:

vendor/bin/generate-deps-for-config-factory \
  config/autoload/generated-dependencies.php \
  App\\Action\\HomePageAction

After we do that, generated-dependencies.php will look like the following:

<?php
return [
    \Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory::class => [
        \App\Action\PingAction::class => [

        ],
        \App\Action\HomePageAction::class => [
            'Zend\\Expressive\\Router\\RouterInterface',
            'Zend\\Expressive\\Template\\TemplateRendererInterface',
            'App\\ServiceManager\\JournalServiceInterface',
        ],
    ],
];

What do you think? We’ve not needed to put in any legwork to create a factory for our class. It’s introspected it to find out what the dependencies are, and built a configuration for us.

It Doesn’t Work For Every Class

Before we learn about ConfigAbstractFactory and how to make use of the configurations which we’ve generated, I have to be honest and say that we can’t auto-generate configurations for every class. For example, take a look at the following class:

<?php
namespace App\ServiceManager\TableGateway;

use Zend\Db\TableGateway\TableGateway;

class JournalTable implements JournalServiceInterface
{
    protected $tableGateway;

    public function __construct(TableGateway $tableGateway)
    {
        $this->tableGateway = $tableGateway;
    }
}

If we were to attempt to generate a configuration for this class, we’d encounter the following error:

Unable to create config for "App\\ServiceManager\\TableGateway\\JournalTable": Cannot create config for constructor argument "table", it has no type hint, or non-class/interface type hint

At first glance, that might seem confusing, as there’s no constructor parameter called $table. That’s true. But, if here’s what TableGateway’s constructor signature looks like:

public function __construct(
  $table,
  AdapterInterface $adapter,
  $features = null,
  ResultSetInterface $resultSetPrototype = null,
  Sql $sql = null
);

Note the first constructor parameter? It has no type hint, so the tool has no way of knowing what to do. Now that perplexed me at first because there was an existing TableGateway configuration in my application’s setup.

So I had a chat with Gary about it. He said that, at the moment, the tool has no way of knowing because it doesn’t load the existing application. Effectively, it’s running blind to any configuration which you have in your configuration.

That is a bit disappointing. However, he shared some of the plans for the tool, and for other tooling in upcoming releases of Zend Framework with me. There’s not the space to cover it all here. Suffice to say, in time, when the tooling’s more mature, this situation won’t be a problem — plus loads more.

So stay tuned.

What Is ConfigAbstractFactory?

If this is your first time hearing about ConfigAbstractFactory, it is another new addition to ServiceManager, written by long-time ServiceManager contributor Gary Hockin (@geeh). Gary cites his motivation for writing it in a blog post on his blog back in September 2016, where he says:

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.

How does it work? Again, I’ll quote Gary’s blog post, where he says:

...Config Abstract Factory solves the problem [of creating a factory class for everything] 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.

How Do You Use The Generated Configuration?

Honestly, there’s not a lot to it. There’s, effectively, two steps:

  1. Register ConfigAbstractFactory as a ServiceManager dependency
  2. Register the generated configuration with the rest of our ServiceManager configuration

Gary does an excellent job of stepping us through how to setup ConfigAbstractFactory for Zend Mvc projects. But we need to configure our project’s slightly differently, as we’re using the generated configuration.

Firstly, ensure that ConfigAbstractFactory is listed in the abstract_factories section of your configuration. Here’s an example (thanks Gary) when using Zend\Mvc:

// module.config.php
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;

return [
    'service_manager' => [
        'abstract_factories' => [
            ConfigAbstractFactory::class,
        ],
    ],
];

Then, we have to load the generated configuration into the existing configuration. One way of doing so, is using a require_once on the file, as in the example below.

Note: the generated configuration needs to be a top-level configuration element.
<?php
// module.config.php
use Zend\ServiceManager\AbstractFactory\ConfigAbstractFactory;

$config = [
    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' => [
      // your other configuration
    ],
];

$dependencies = require_once(__DIR__ . '/generated-dependencies.php');

return array_merge($config, $dependencies);

In the example above, we read in the generated configuration and then merge it with the existing configuration. Doing so, we can now continue to update the generated configuration, as needed, and won’t need to do anything else.

What About Zend Expressive

At the moment, it doesn’t work out-of-the-box for Zend Expressive projects (those based on the Zend Expressive Skeleton Installer). This is because Zend Expressive’s configuration is provided as an ArrayObject and ConfigAbstractFactory expects an array.

I’m considering submitting a PR so that it works with both. But I’m, currently, not sure of the best place to do so. Regardless, I’m expecting to have submitted a PR by the end of the week.

In Conclusion

This brings us to the end of the two-part series on some of the new tooling support available in Zend ServiceManager from version 3.2.0 onwards. If you’d like to know more, check out the release notes, or read through the code from the two tools.

I know that it’s still early days. But after my talk with Gary last week, I’m excited about where the tooling support is heading. If, in the past, you’ve been frustrated with the amount of work required when working with Zend ServiceManager, know that it’s in the process of changing.

In time, it will all be a thing of the past.



About Matthew

Matthew Setter Matthew Setter is a PHP & Zend Framework specialist. If you're in need of a custom software application, need to migrate an existing legacy application, or want to know your current application's GPA - get in touch.

Want To Be A Zend Framework Guru?

Drop your email in the box below, and get awesome tutorialsjust like this one — straight to your inbox, PLUS exclusive content only available by email.