How To Simplify Zend Expressive Configuration with ConfigProviders

In any application there are a lot of moving parts, which can lead to a lot of complexity — especially when it comes to configuration. Given Zend Framework’s flexible nature, this can easily be the case — if we’re not careful — requiring a lot of supporting configuration. ConfigProvider classes, however, make managing application configuration very maintainable, even intuitive. This tutorial shows how to implement them.


In any application there are a lot of moving parts. This, if we’re not careful, can lead to a lot of complexity, making the code expensive to maintain.

Given Zend Framework’s design (and accompanying flexible nature), this can easily be the case if we’re not careful. If we’re not, we can end up with a hell of a lot of classes (think factories, abstract factories, view helpers, and so on) requiring a lot of accompanying configuration.

Whilst it is great that we can do this — making every single component that we write beautifully testable — if we’re not careful, we’re may end up with a sizeable amount of accompanying configuration.

Specifically, we’ll likely end up with a config/autoload directory polluted with a plethora of configuration, including for dependencies, routing, and middleware.

I had such a situation on a recent project, and speaking from personal experience, it became a pain to manage. It was one of the key things that I identified as needing to change in future projects.

As it turns out, this was something which was already identified by other developers, including the Zend Framework contributors. Specifically, team lead Matthew Weier O’Phinney put me on to said solution in his comments on the tutorial on enabling Zend\Form ViewHelpers in Expressive.

In there, he mentioned ConfigProvider classes as a simple way of enabling Zend\Form ViewHelpers, which aren’t enabled by default in Zend Expressive.

As I looked at the composition of the file, I realized that this was the answer I needed to solve the configuration issue I created for myself.

What is a ConfigProvider

ConfigProvider classes are PHP classes, with no inheritance hierarchy — plain and simple. As a result there is minimal, if any, complexity to them.

All they do is return an array which contains a set of configuration options. Specifically, any option which you can put in any one of the default provided configuration files, whether that be dependencies, route configurations, middleware, etc, can be contained there.

Then, to use the configuration, you need only create a file in config/autoload, which instantiates and invokes it, returning it’s configuration.

How To Create One

With the sales pitch out of the way, let’s see create and use one. To keep the example concise, I’m going to refactor the configuration from a basic application, based on the Zend Expressive Skeleton Project.

If you’ve not set one up before, check out the Zend Expressive starter guide. I’ll assume for the purposes of today’s tutorial, that you have already done that.

Under src/App, create a new file called ConfigProvider.php, which looks as follows:

namespace App;

use App\Action;

class ConfigProvider
{

}

Nothing special, just a simple PHP class. Next, add an __invoke() method, which returns an array, containing two keys; one for dependencies, one for routes, as I have below.

public function __invoke()
{
  return [
    'dependencies' => $this->getDependencyConfig(),
    'routes' => $this->getRouteConfig(),
  ];
}

Following on in the style of the existing ConfigProvider classes, the values for both keys will be provided through get*Config() methods. You can find existing ConfigProviders for: Zend\Db, Zend\Form, Zend\Filter, Zend\Hydrator, Zend\InputFilter, and Zend\I18n.

Refactoring The Dependency Configuration

For getDependencyConfig(), we’ll refactor the existing, App-related configuration settings from config/autoload/dependencies.global.php and config/autoload/routes.global.php, adjusting them for the new App namespace, defining it as follows:

public function getDependencyConfig()
{
  return [
    'invokables' => [
      PingAction::class => PingAction::class,
    ],
    'factories' => [
      HomePageAction::class      => HomePageFactory::class,
      ViewUrlsPageAction::class  => ViewUrlsPageFactory::class,
      ManageUrlPageAction::class => ManageUrlPageFactory::class,
    ],
  ];
}

Note: I’ve imported the App\Action namespace for better readability.

Refactoring The Route Configuration

Next, we’ll refactor the routes from config/autoload/routes.global.php, in to getRouteConfig(), so that it will be defined as follows:

private function getRouteConfig()
{
  return [
    [
      'name'            => 'url.add',
      'path'            => '/add-url',
      'middleware'      => ManageUrlPageAction::class,
      'allowed_methods' => ['GET', 'POST'],
    ],
    [
      'name'            => 'home',
      'path'            => '/',
      'middleware'      => ViewUrlsPageAction::class,
      'allowed_methods' => ['GET'],
    ],
  ];
}

Again, we take advantage of the App\Action namespace to make the file that much more compact (and readable).

Using The ConfigProvider

With the relevant configuration extracted from the global application configuration, we now need to make use of it. To do so, we create a new file, called config/autoload/app.global.php.

The is a simplistic naming convention I decided on, using the name of the module as the file prefix. In the file, we’ll instantiate and invoke the new ConfigProvider as follows:

use App\ConfigProvider;

return (new ConfigProvider())->__invoke();

With that done, when we reload any route in the skeleton application, we’ll see that it still works as before.

In Conclusion (tl;dr)

ConfigProvider’s are an excellent way of packaging up the configuration of a module so that it’s self-contained, easy to invoke, and avoids creating a bloated or polluted global application configuration setup.

What’s more, they’re ordinary PHP classes, so there’s no complicated setup nor inheritance hierarchy.

Zend Framework provides many ways to do just about anything; resulting in us utmost choice and flexibility. This can be both a blessing and a curse.

By following the ConfigProvider convention we have an elegant way to focus that flexibility, so that our code remains readable and maintainable.

If you’ve not already tried it, definitely give it a go. I’d love to hear how you get on in the comments.


You might also be interested in...


Want more tutorials like this?

If so, enter your email address in the field below and click subscribe.

You can unsubscribe at any time by clicking the link in the footer of the emails you'll receive. Here's my privacy policy, if you'd like to know more. I use Mailchimp to send emails. You can learn more about their privacy practices here.


Join the discussion

comments powered by Disqus