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 Mezzio’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 Mezzio’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 Mezzio core contributors. Project 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 laminas-form ViewHelpers, which aren’t enabled by default in Mezzio.
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 do you create them?
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 Mezzio Skeleton.
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 many of the laminas projects, including laminas-db, laminas-form, laminas-filter, laminas-hydrator, and laminas-inputfilter.
Refactor the DI 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.
Refactor 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).
Enable 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.
That’s how To simplify Mezzio application configuration with ConfigProviders
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.
Mezzio 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.
Join the discussion
comments powered by Disqus