Advanced
Delegate Containers
Delegates are a way to allow you to register one or multiple backup containers that will be used to attempt the resolution of services when they cannot be resolved via this container.
A delegate must be an implementation of the container-interop project and can be registered using the delegate
method.
<?php
namespace Acme\Container;
use Interop\Container\ContainerInterface;
class DelegateContainer implements ContainerInterface
{
// ..
}
<?php
$container = new League\Container\Container;
$delegate = new Acme\Container\DelegateContainer;
// this method can be invoked multiple times, each delegate
// is checked in the order that it was registered
$container->delegate($delegate);
Now that the delegate has been registered, if a service cannot be resolved, the container will resort to the has
and get
methods of the delegate to resolve the requested service.
Auto Wiring
Note: Auto wiring is turned off by default but can be turned on by registering the
ReflectionContainer
as a container delegate. Read below and see the documentation on delegates.
Container has the power to automatically resolve your objects and all of their dependencies recursively by inspecting the type hints of your constructor arguments. Unfortunately, this method of resolution has a few small limitations but is great for smaller apps. First of all, you are limited to constructor injection and secondly, all injections must be objects.
<?php
namespace Acme;
class Foo
{
public $bar;
public $baz;
public function __construct(Bar $bar, Baz $baz)
{
$this->bar = $bar;
$this->baz = $baz;
}
}
<?php
namespace Acme;
class Bar
{
public $bam;
public function __construct(Bam $bam)
{
$this->bam = $bam;
}
}
<?php
namespace Acme;
class Baz
{
// ..
}
<?php
namespace Acme;
class Bam
{
// ..
}
In the above code, Foo
has 2 dependencies Bar
and Baz
, Bar
has a further dependency of Bam
. Normally you would have to do the following to return a fully configured instance of Foo
.
<?php
$bam = new Acme\Bam;
$baz = new Acme\Baz;
$bar = new Acme\Bar($bam);
$foo = new Acme\Foo($bar, $baz);
With nested dependencies, this can become quite cumbersome and hard to keep track of. With the container, to return a fully configured instance of Foo
it is as simple as requesting Foo
from the container.
<?php
$container = new League\Container\Container;
// register the reflection container as a delegate to enable auto wiring
$container->delegate(
new League\Container\ReflectionContainer
);
$foo = $container->get('Acme\Foo');
var_dump($foo instanceof Acme\Foo); // true
var_dump($foo->bar instanceof Acme\Bar); // true
var_dump($foo->baz instanceof Acme\Baz); // true
var_dump($foo->bar->bam instanceof Acme\Bam); // true
Inflectors
Inflectors allow you to define the manipulation of an object of a specific type as the final step before it is returned by the container.
This is useful for example when you want to invoke a method on all objects that implement a specific interface.
Imagine that you have a LoggerAwareInterface
and would like to invoke the method called setLogger
passing in a logger every time a class is retrieved that implements this interface.
$container->add('Some\Logger');
$container->add('Some\LoggerAwareClass'); // implements LoggerAwareInterface
$container->add('Some\Other\LoggerAwareClass'); // implements LoggerAwareInterface
$container->inflector('LoggerAwareInterface')
->invokeMethod('setLogger', ['Some\Logger']); // Some\Logger will be resolved via the container
Now instead of adding a method call to each class individually we can simply define an inflector to invoke the method for every class of that type.
Service Providers
Service providers give the benefit of organising your container definitions along with an increase in performance for larger applications as definitions registered within a service provider are lazily registered.
To build a service provider it is as simple as extending the base service provider and defining what you would like to register.
<?php
namespace Acme\ServiceProvider;
use League\Container\ServiceProvider\AbstractServiceProvider;
class SomeServiceProvider extends AbstractServiceProvider
{
/**
* The provides array is a way to let the container
* know that a service is provided by this service
* provider. Every service that is registered via
* this service provider must have an alias added
* to this array or it will be ignored.
*
* @var array
*/
protected $provides = [
'key',
'Some\Controller',
'Some\Model',
'Some\Request'
];
/**
* This is where the magic happens, within the method you can
* access the container and register or retrieve anything
* that you need to, but remember, every alias registered
* within this method must be declared in the `$provides` array.
*/
public function register()
{
$this->getContainer()->add('key', 'value');
$this->getContainer()->add('Some\Controller')
->withArgument('Some\Request')
->withArgument('Some\Model');
$this->getContainer()->add('Some\Request');
$this->getContainer()->add('Some\Model');
}
}
To register this service provider with the container simply pass an instance of your provider or a fully qualified class name to the League\Container\Container::addServiceProvider
method.
<?php
$container = new League\Container\Container;
$container->addServiceProvider(new Acme\ServiceProvider\SomeServiceProvider);
$container->addServiceProvider('Acme\ServiceProvider\SomeServiceProvider');
The register method is not invoked until one of the aliases in the $provides
array is requested by the container, therefore, when we want to retrieve one of the items provided by the service provider, it will not actually be registered until it is needed, this improves performance for larger applications as your dependency map grows.
Bootable Service Providers
If there is functionality that needs to be run as the service provider is added to the container, for example, setting up inflectors, including config files etc, we can make the service provider bootable by implementing the BootableServiceProviderInterface
.
<?php
namespace Acme\ServiceProvider;
use League\Container\ServiceProvider\AbstractServiceProvider;
use League\Container\ServiceProvider\BootableServiceProviderInterface;
class SomeServiceProvider extends AbstractServiceProvider implements BootableServiceProviderInterface
{
/**
* @var array
*/
protected $provides = [
// ...
];
/**
* In much the same way, this method has access to the container
* itself and can interact with it however you wish, the difference
* is that the boot method is invoked as soon as you register
* the service provider with the container meaning that everything
* in this method is eagerly loaded.
*
* If you wish to apply inflectors or register further service providers
* from this one, it must be from a bootable service provider like
* this one, otherwise they will be ignored.
*/
public function boot()
{
$this->getContainer()
->inflector('SomeType')
->invokeMethod('someMethod', ['some_arg']);
}
/**
* {@inheritdoc}
*/
public function register()
{
// ...
}
}