Shipping containers arranged in an arch

The fear of the new?

What’s so wrong with creating objects in our code? The new keyword is an essential part of object-oriented PHP and we wouldn’t get far without it. And yet, over the years, I’ve written as much about strategies to delegate, disguise, delay, or do away with object creation as I have about any other design problem. Why? Is it really such a problem to create an object?

Let’s begin with an example. Imagine a component, BusinessResults, which is responsible for presenting business directory search summaries.

class BusinessResults
{
    private ListingSet $listings;

    public function __construct()
    {
        $this->listings = new ListingSet();
    }

}


BusinessResults creates its own ListingSet property. ListingSet presumably acquires this data somehow (I’m waving my hands vaguely here) and BusinessResults works with it in some way (more hand waving – I’m not going to conjure or describe much actual functional logic in this article – we’re going to focus on creating and configuring objects).

So, my vagueness aside, what’s wrong with this picture? The problem is that we can’t vary the implementation of ListingSet. We’re stuck with the particular implementation we have instantiated. Want to run BusinessResults with a test version of ListingSet? Switch between database, JSON, or RESTful implementations? Tough. We can’t. We’ve made our choice.

That is unfortunate because one strength of object-oriented design lies in the ability it offers us to switch implementations dynamically. In order to create a flexible system, we should be able to drop in new concrete implementations as needed.

Ask a friend: registry and service locator

One answer is to delegate the creation to another component. Depending upon various niceties you’ll see such components described as factories, service locators, or registries. Such objects are often made available globally or passed around a system. Once a component has access to this helper, it can ask it for an object instance.

class BusinessResults
{
    private ListingSet $listings;

    public function __construct()
    {
        $reg = Registry::inst();
        $this->listings = $reg->getListings();
    }

}


For this example, I’ve used a registry object which I’ve made available using the venerable Singleton pattern.

This solves a lot of the problem in that the Registry class can be overloaded to provide different instances of ListingSet according to different circumstances. A configuration object might lodge different instances of objects with the Registry or we might have an inheritance hierarchy of Registry instances (see the also venerable abstract factory pattern).

The registry also creates a new problem. BusinessResults is now baked into the wider context that includes the Registry component. This isn’t solved either by asking for Registry to be passed in as an argument to the constructor. In both cases the component is now tied to a particular container implementation. What’s more, the dependency – in this case of BusinessResults upon ListingSet is implicit rather than declared.

NOTE I use the registry pattern a lot. I regard a registry implementation as a quick and handy part of a pragmatic programmer’s tool kit. Dependency injection can result in a cleaner design, though, allowing components greater independence and reusability.

Dependency injection: let the caller decide

In object-oriented design, a lot of work goes into maintaining the ignorance of components. We want a component to do a single job (or a set of tightly-related jobs) with as few side effects as possible. Such principles help make components more reusable and less likely to kick off unintended consequences as a result of their operations.

Both direct instantiation and (to a lesser extent) the use of a registry embed a component into the wider system. The component knows about an individual implementation (the object it creates with new) or it knows about a particular global tool (the Registry class).

So how about we have our component just wash its hands of the problem? It could just ask that a ListingSet object be passed to it.

class BusinessResults
{
    private ListingSet $listings;

    public function __construct(ListingSet $listings)
    {
        $this->listings = $listings;
    }

}


This pattern, which is really just a simple hinted argument in a constructor, is called dependency injection. BusinessResults is no longer responsible for creating the ListingSet object upon which it depends (or even for causing it to be created). Instead ListingSet is passed (injected) into it. This switch of responsibility has been given another fancy name – Inversion of Control (or IoC).

NOTE The service locator pattern is often also cited as IoC – but clearly the inversion is somewhat less pure than that engendered by dependency injection since the constructor initiates the query.

By injecting the dependency we:

  • ensure that the BusinessResults component is independent of the wider system and is therefore more interoperable.
  • make the relationship between BusinessResults and ListingSet clear and explicit.

So that’s the problem solved, right? Thanks for reading.

Except, here’s the thing with many mechanisms for creating objects – you almost always end up shifting the problem along somewhere else rather than solving it outright. We have made the BusinessResults class cleaner with dependency injection/IoC– but that really just begs the question – where the heck is ListingSet created, then?

Of course, we can easily instantiate BusinessResults:

$listingset = new ListingSet();
$bres = new BusinessResults($listingset);

But where should that instantiation go in our system? And, if we apply dependency injection across the board, what about all the other instantiations?

DI Containers to the rescue.

As you might expect, there is a software-based solution for this problem. This is the dependency injection container. Although the implementation can get hairy, the concept is simple. With some magic and some configuration, a DI container will handle instantiation for you. It will create your objects as well as any objects they require in their constructor signatures.

There are a bunch of PHP implementations (I even included one in my book for fun). Perhaps the most famous is the unpleasantly named Pimple which is part of the Silex framework. Neither Pimple nor Silex are under active development any longer. For this article I will look at PHP-DI. PHP-DI is well-featured, nicely documented and implements the PSR-11 dependency injection standard – making it a good choice. As a bonus, it works well with Slim, a micro-framework of which I am currently fond.

NOTE Although I don’t intend to survey all PHP DI container options, The PHP League’s Container may also be worth investigating if you are assessing candidates. The PSR-11 Standard Meta Document provides a good list too.

So, let’s look at PHP-DI in action.

PHP-DI: getting to know you

First of all you need to get it installed. The easiest way to do that is to add it to your composer.json file

{
    "require": {
        "php-di/php-di": "^6.4"
    }
}

Then you can acquire it by running composer update on the command line.

NOTE Installation and autoloading with composer are beyond the scope of this article. The project provides good documentation for getting started, however.

Once we have PHP-DI, we are ready to start using it.

$container = new \DI\Container();
$bres = $container->get(BusinessResults::class);

$bres will now contain an instance of the BusinessResults class – fully and and automatically loaded with a ListingSet object! So, how was this magic achieved? Thanks to PHP’s reflection extension, PHP-DI was able to read the type hinting in the BusinessResults constructor. It then created the required instance of ListingSet and passed it to the BusinessResults class’s constructor.

Of course, despite all that automation we still need to call \DI\Container::get() somewhere along the line to start the ball rolling. This is not something we should do often, if at all, within the business logic of a system. Some frameworks, notably Silex, pass a container around the system and allow components to use it directly to acquire objects. This is another instance of the service locator pattern described above. It is discouraged for the reasons discussed.

But we may create factory classes that work directly with containers. A framework such as Slim may work with the container directly when routing the URL associated with an incoming request to a controller method or anonymous function.

The container interface is pretty simple. In fact, we’ve met the most significant element.

Working directly with containers

The PSR-11 standard specifies two methods for ContainerInterface, the class that a compliant DI container should implement.

We have already seen get() which accepts a string identifier (often a class name) and returns a mixed value – usually an object, string or a callable.

The standard also specifies has(). This accepts a string identifier and returns a Boolean – true if the referenced entity is stored or can be found.

$container = new \DI\Container();
if ($container->has(BusinessResults::class)) {
    return true;
}
return false;

PHP-DI caches the data it contains – which is good for performance but may not always be what you want. PHP-DI provides the make() method, which behaves like get() but will retrieve its return value afresh every time it is called.

$container = new \DI\Container();
$bres = $container->make(BusinessResults::class);

NOTE This is not an exhaustive list. For example, PHP-DI also supports a method named injectOn() which will perform dependency injection upon an existing object by invoking setter methods or setting properties.

Autowiring – it just works. Sometimes.

Autowiring lies at the heart of many dependency injection container implementations. In fact we have already seen it in action. The PHP-DI container found and instantiated both BusinessResults and ListingSet without any configuration from us. That’s because both classes were discoverable and the BusinessResults constructor provided unambiguous hinting.

Of course, things can’t always work out of the box. Consider this case:

class BusinessResults
{
    private string $title;
    private ListingSet $listings;

    public function __construct(string $title, ListingSet $listings)
    {
        $this->title = $title;
        $this->listings = $listings;
    }
}


Now running the container as before – $container->get(BusinessResults::class) – will result in an exception:

DI\Definition\Exception\InvalidDefinition: Entry "gi\lazy\phpdi\batch04\BusinessResults" cannot be resolved: Parameter $title of __construct() has no value defined or guessable
Full definition:
Object (
    class = gi\lazy\phpdi\batch04\BusinessResults
    lazy = false
    __construct(
        $title = #UNDEFINED#
        $listings = get(gi\lazy\phpdi\batch01\ListingSet)
    )
)


This makes sense. There is no way that the container can know about the $title parameter. So, when things won’t work automatically, it’s time to apply some configuration.

Configuring the container

PHP-DI can be configured via a ContainerBuilder object. Through the addDefinitions() method – which can be called multiple times – you can build up a set of rules for the container’s operation. When you’re ready to go, you can then call DI\ContainerBuilder::build() to generate the container.

$builder = new \DI\ContainerBuilder();
$builder->addDefinitions(
    [
        // ...
    ]
);
$container = $builder->build();

Or a file path that resolves to a PHP file.

$builder = new \DI\ContainerBuilder();
$builder->addDefinitions(
    __DIR__ . "/config.php"
);
$container = $builder->build();

The referenced file should return a configuration array.

return [
    // ...
];


So, if we configure PHP-DI with a series of PHP arrays, what do we put in them?

Values

Each definition array we pass to ContainerBuilder via addDefinitions() should be indexed by identifier key strings, each one representing an entry to be fetched. The array’s values will resolve to DI\Definition objects which will describe how the container should do its job. We don’t have to worry about creating any such objects since PHP-DI provides a set of functions for the purpose.

Remember the BusinessResults constructor’s new $title argument? We can specify a value we wish to inject for it. The helper function DI\value() will define a value for us (using, behind the scenes, a ValueDefinition object).

$builder = new \DI\ContainerBuilder();
$builder->addDefinitions(
    [
        "results.title" => \DI\value("hats!"),
    ]
);
$container = $builder->build();
print $container->get("results.title"); // hats!

So now the container has an identifier, results.title, which will resolve to a value: hats!. That’s useful, but there’s nothing in what we’ve done so far that will tell the container that results.title should be injected into the BusinessResults class’s constructor.

The DI\create() and DI\get() helper functions

We need to thread the needle so that the value of results.title is passed to BusinessResults. There are a number of ways of doing this but perhaps the most obvious is by using DI\create(). This function stores instructions about how to create a target in a CreateDefinitionHelper object. DI\create() returns an instance of CreateDefinitionHelper and we can call methods on it to describe how the target object should be created. One of these, CreateDefinitionHelper::constructor(), describes how a target’s constructor should be called. Let’s try it out:

$builder = new \DI\ContainerBuilder();
$builder->addDefinitions(
    [
        "results.title" => \DI\value("hats!"),
        BusinessResults::class => \DI\create(BusinessResults::class)
            ->constructor(\DI\get("results.title"), \DI\get(ListingSet::class))
    ]
);
$container = $builder->build();
$bizres = $container->get(BusinessResults::class);
var_dump($bizres);

Here is the output:

object(gi\lazy\phpdi\batch04\BusinessResults)#152 (2) {
  ["title":"gi\lazy\phpdi\batch04\BusinessResults":private]=>
  string(5) "hats!"
  ["listings":"gi\lazy\phpdi\batch04\BusinessResults":private]=>
  object(gi\lazy\phpdi\batch01\ListingSet)#155 (0) {
  }
}


So CreateDefinitionHelper::constructor() accepts a list of references to definitions (as returned by the DI\get() helper function). Where an identifier does not yet exist in configuration, (as is the case for ListingSet above) the container will attempt to resolve the element through autowiring.

By default, a definition is not run unless its target is called for – so you can set up all sorts of actions which will only be lazily invoked as needed.

the DI\autowire() function

For another approach to object creation, you can give the container’s built-in support for autowiring a helping hand by using the DI\autowire() helper function. This returns an AutowireDefinitionHelper object which provides a constructorParameter() method. You can call this with a string representing the parameter name and a definition reference (again, DI\get() is usually the way to go here).

The point here is that you only need to provide a reference for those parameters that the container will not be able to resolve automatically using reflection. Where there are many parameters to a constructor this can save you the bother of specifying them all.

    [
        "results.title" => \DI\value("hats!"),
        BusinessResults::class => \DI\autowire(BusinessResults::class)
            ->constructorParameter("title", \DI\get("results.title"))
    ]
);

Here, I have specfied the $title parameter but I have left the resolution of ListingSet implicit.

NOTE DefinitionHelper methods are fluent – that is, you can chain them together. This allows you to add multiple calls to to constructorParameter() for example.

DI\factory()

Occasionally you might need to do some work beyond a straightforward instantiation. You may need to process some data before creating an object instance or you might want to return a closure rather than an object. The DI\factory() helper function expects a callable argument. When invoked, this will be passed an instance of Psr\Container\ContainerInterface. (in fact it will be passed a DI\Container object but it is recommended that you type hint at the interface level for the sake of interoperability).

Within that function you can then perform any task you want. In this example, I simply specify a $title and then pass it and a ListingSet object (itself acquired using the provided container reference) to the BusinessResults constructor.

$builder->addDefinitions(
    [
        BusinessResults::class => \DI\factory(
            function (ContainerInterface $container) {
                $title = "cheese!";
                return new BusinessResults($title, $container->get(ListingSet::class));
            }
        )
    ]
);

Apart from substituting “hats!” for “cheese!”, my example is functionally the same as the DI\create() and DI\autowire() cases. But where more involved work is needed to construct an object, or if you need to return a callable function rather than an object, the DI\factory() is a good option.

Conclusion

The PHP-DI environment has a bunch of features this introduction did not have room for. In particular, I have not covered the package’s support for phpdoc annotations. I hope that in future PHP-DI will support PHP’s native attributes instead of, or in addition to, annotations. Also worth investigating further are support for environment variables, string expressions, non-constructor method calls and property setting.

Hopefully, though, this article has provided a half-decent introduction to dependency injection, containers, and to PHP-DI. In future articles I’ll look at some practical uses for PHP-DI.

Photo by ines mills on Unsplash