Dependency injection with League's new Container
The PHP League just released version 2 of their dependency injection container, and this is why you should be excited.
Container Interoperability
League's container now implements the ContainerInterface
, which is defined by the Container Interoperability group. This is really great as there are a few projects and frameworks which support this interface. For example, the Slim 3 micro framework already allows you to choose your own container flavour, as long as it implements the interface:
$container = new \League\Container\Container;
$app = new \Slim\App($container);
I hope that this change will stimulate more frameworks to adopt the interface so that we can use our favourite container implementation across all our projects.
Auto Wiring
This isn't a new feature, but it is the best feature in my opinion (even though it is not enabled by default). Auto wiring automatically resolves your objects and all of their dependencies recursively by inspecting the type hints of your constructor arguments.
Trust me, it's amazing. It stimulates you as a developer to use Inversion of control, which reduces code duplication and makes it easier to unit test your classes. I'll show you the power of auto wiring using a real world example which I have used in different projects. First things first, this is how you create a container with auto wiring enabled:
$container = new \League\Container\Container;
// Required to enable auto wiring.
$container->delegate(
new \League\Container\ReflectionContainer
);
Let's say our imaginary project needs to talk to different API endpoints using the Guzzle HTTP client and SDK classes. Because the SDK class depends on the Guzzle client to talk to the remote API, you add it as a dependency to the constructor so it can be injected:
namespace Acme;
class SDK
{
protected $http;
public function __constructor(\GuzzleHttp\Client $http)
{
$this->http = $http;
}
}
When constructing an instance of the SDK class using the container, the container will use reflection to figure out which dependencies are required. In this case, the container will first create an instance of GuzzleHttp\Client
and pass it to the constructor of the SDK class.
$sdk = $container->get('Acme\SDK');
Because we didn't tell the container how to create GuzzleHttp\Client
objects, it will just create a "plain" instance without passing anything to the constructor. But the cool thing is that we can! We can tell the container how to construct them, however we want:
$container->add('GuzzleHttp\Client', function() {
return new \GuzzleHttp\Client([
'timeout' => 2,
'connect_timeout' => 2,
]);
});
If you ask the container for an SDK instance now, it will create it using a Guzzle client with default the options that we defined above. This is great for adding default parameters to classes that are used often. And if you would like to add additional logging of all the request and responses for debugging, you can easily add a custom handler to every Guzzle instance by adding it to the container definition.
This makes testing the SDK classes a lot easier as well. In your unit or integration tests, you can tell the container to create a mocked instance of the Guzzle client or a dummy client, so that you are not hitting the actual API during your test.
That's it! I tried explaining the auto wiring feature as good as possible. If anything is unclear, ping me on Twitter and I will revisit my explanation :)