PHP-RN logo PHP-RN

Container Meta Document

1. Introduction

This document describes the process and discussions that led to the Container PSR. Its goal is to explain the reasons behind each decision.

2. Why bother?

There are dozens of dependency injection containers out there, and these DI containers have very different ways to store entries.

So when you look at the big picture, there is a very large number of ways in which the DI problem can be tackled, and therefore a big number of different implementations. However, all the DI containers out there are answering the same need: they offer a way for the application to retrieve a set of configured objects (usually services).

By standardizing the way entries are fetched from a container, frameworks and libraries using the Container PSR could work with any compatible container. That would allow end users to choose their own container based on their own preferences.

3. Scope

3.1. Goals

The goal set by the Container PSR is to standardize how frameworks and libraries make use of a container to obtain objects and parameters.

It is important to distinguish the two usages of a container:

Most of the time, those two sides are not used by the same party. While it is often end users who tend to configure entries, it is generally the framework that fetches entries to build the application.

This is why this interface focuses only on how entries can be fetched from a container.

3.2. Non-goals

How entries are set in the container and how they are configured is out of the scope of this PSR. This is what makes a container implementation unique. Some containers have no configuration at all (they rely on autowiring), others rely on PHP code defined via callback, others on configuration files… This standard only focuses on how entries are fetched.

Also, naming conventions used for entries are not part of the scope of this PSR. Indeed, when you look at naming conventions, there are 2 strategies:

Both strategies have their strengths and weaknesses. The goal of this PSR is not to choose one convention over the other. Instead, the user can simply use aliasing to bridge the gap between 2 containers with different naming strategies.

The PSR states that:

“users SHOULD NOT pass a container into an object, so the object can retrieve its own dependencies. Users doing so are using the container as a Service Locator. Service Locator usage is generally discouraged.”

// This is not OK, you are using the container as a service locator
class BadExample
{
    public function __construct(ContainerInterface $container)
    {
        $this->db = $container->get('db');
    }
}

// Instead, please consider injecting directly the dependencies
class GoodExample
{
    public function __construct($db)
    {
        $this->db = $db;
    }
}
// You can then use the container to inject the $db object into your $goodExample object.

In the BadExample you should not inject the container because:

Very often, the ContainerInterface will be used by other packages. As a end-user PHP developer using a framework, it is unlikely you will ever need to use containers or type-hint on the ContainerInterface directly.

Whether using the Container PSR into your code is considered a good practice or not boils down to knowing if the objects you are retrieving are dependencies of the object referencing the container or not. Here are a few more examples:

class RouterExample
{
    // ...

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    public function getRoute($request)
    {
        $controllerName = $this->getContainerEntry($request->getUrl());
        // This is OK, the router is finding the matching controller entry, the controller is
        // not a dependency of the router
        $controller = $this->container->get($controllerName);
        // ...
    }
}

In this example, the router is transforming the URL into a controller entry name, then fetches the controller from the container. A controller is not really a dependency of the router. As a rule of thumb, if your object is computing the entry name among a list of entries that can vary, your use case is certainly legitimate.

As an exception, factory objects whose only purpose is to create and return new instances may use the service locator pattern. The factory must then implement an interface so that it can itself be replaced by another factory using the same interface.

// ok: a factory interface + implementation to create an object
interface FactoryInterface
{
    public function newInstance();
}

class ExampleFactory implements FactoryInterface
{
    protected $container;

    public function __construct(ContainerInterface $container)
    {
        $this->container = $container;
    }

    public function newInstance()
    {
        return new Example($this->container->get('db'));
    }
}

5. History

Before submitting the Container PSR to the PHP-FIG, the ContainerInterface was first proposed in a project named container-interop. The goal of the project was to provide a test-bed for implementing the ContainerInterface, and to pave the way for the Container PSR.

In the rest of this meta document, you will see frequent references to container-interop.

6. Interface name

The interface name is the same as the one discussed for container-interop (only the namespace is changed to match the other PSRs). It has been thoroughly discussed on container-interop [4] and was decided by a vote [5].

The list of options considered with their respective votes are:

7. Interface methods

The choice of which methods the interface would contain was made after a statistical analysis of existing containers. [6].

The summary of the analysis showed that:

The question of whether to include methods to define entries has been discussed at the very start of the container-interop project [4]. It has been judged that such methods do not belong in the interface described here because it is out of its scope (see the “Goal” section).

As a result, the ContainerInterface contains two methods:

7.1. Number of parameters in get() method

While ContainerInterface only defines one mandatory parameter in get(), it is not incompatible with existing containers that have additional optional parameters. PHP allows an implementation to offer more parameters as long as they are optional, because the implementation does satisfy the interface.

Difference with container-interop: The container-interop spec stated that:

While ContainerInterface only defines one mandatory parameter in get(), implementations MAY accept additional optional parameters.

This sentence was removed from PSR-11 because:

However, some implementations have extra optional parameters; that’s technically legal. Such implementations are compatible with PSR-11. [11]

7.2. Type of the $id parameter

The type of the $id parameter in get() and has() has been discussed in the container-interop project.

While string is used in all the containers that were analyzed, it was suggested that allowing anything (such as objects) could allow containers to offer a more advanced query API.

An example given was to use the container as an object builder. The $id parameter would then be an object that would describe how to create an instance.

The conclusion of the discussion [7] was that this was beyond the scope of getting entries from a container without knowing how the container provided them, and it was more fit for a factory.

7.3. Exceptions thrown

This PSR provides 2 interfaces meant to be implemented by container exceptions.

7.3.1 Base exception

The Psr\Container\ContainerExceptionInterface is the base interface. It SHOULD be implemented by custom exceptions thrown directly by the container.

It is expected that any exception that is part of the domain of the container implements the ContainerExceptionInterface. A few examples:

However, if the exception is thrown by some code out of the container’s scope (for instance an exception thrown while instantiating an entry), the container is not required to wrap this exception in a custom exception implementing the ContainerExceptionInterface.

The usefulness of the base exception interface was questioned: it is not an exception one would typically catch [8].

However, most PHP-FIG members considered it to be a best practice. Base exception interface are implemented in previous PSRs and several member projects. The base exception interface was therefore kept.

7.3.2 Not found exception

A call to the get method with a non-existing id must throw an exception implementing the Psr\Container\NotFoundExceptionInterface.

For a given identifier:

Therefore, when a user catches the Psr\Container\NotFoundExceptionInterface, it has 2 possible meanings [9]:

The user can however easily make a distinction with a call to has.

In pseudo-code:

if (!$container->has($id)) {
    // The requested instance does not exist
    return;
}
try {
    $entry = $container->get($id);
} catch (NotFoundExceptionInterface $e) {
    // Since the requested entry DOES exist, a NotFoundExceptionInterface means that the container is misconfigured and a dependency is missing.
}

8. Implementations

At the time of writing, the following projects already implement and/or consume the container-interop version of the interface.

Implementors

Middleware

Consumers

This list is not comprehensive and should be only taken as an example showing that there is considerable interest in the PSR.

9. People

9.1 Editors

9.2 Sponsors

9.3 Contributors

Are listed here all people that contributed in the discussions or votes (on container-interop and during migration to PSR-11), by alphabetical order:

  1. Discussion about the container PSR and the service locator
  2. Container-interop’s ContainerInterface.php
  3. List of all issues
  4. Discussion about the interface name and container-interop scope
  5. Vote for the interface name
  6. Statistical analysis of existing containers method names
  7. Discussion about the method names and parameters
  8. Discussion about the usefulness of the base exception
  9. Discussion about the NotFoundExceptionInterface
  10. Discussion about get optional parameters in container-interop and on the PHP-FIG mailing list