A dependency injection container with autowiring
Installation
composer require avris/container
Usage
Basics
Container resolves dependencies between defined services, in order to simplify the development process, avoid duplication of code, facilitate interoperability and improve maintainability and testability. See: Dependency Injection pattern.
$parameterProvider = new SimpleParameterProvider(['ROOT_DIR' => __DIR__]);
// parameter provider is optional
$container = new Container($parameterProvider);
$container->set('number', 4);
$container->set(Foo::class, new Foo);
$container->setDefinition(Bar::class, [
'arguments' => [
'$foo' => '@' . Foo::class,
'$dir' => '%ROOT_DIR%/bar',
'$number' => '@number',
'$float' => 69.123,
],
'public' => true,
]);
$container->setDefinition(BarInterface::class, Bar::class); // alias
$container->get('number'); // 4
$container->get(Foo::class); // new Foo
$container->get(BarInterface::class); // new Bar(new Foo, __DIR__ . '/bar', 4, 69.123)
$container->getParameter('ROOT_DIR'); // __DIR__
Options
-
class
– the class of the service, will default to the service name if not given. If the given class implements theResolver
interface, it will be instantiated, and it’sresolve
method executed to provide an actual value to be put in the container. -
arguments
– constructor arguments. -
calls
– method calls to be executed right after constructing the service (setter injection etc.)'calls' => [ ['setLogger', ['@logger']], ['registerListener', ['@listenerA']], ['registerListener', ['@listenerB']], ],
-
tags
– an array of string that help group similar services together; tagged services can be injected with#tagName
:$container->setDefinition(HandlerA::class, ['tags' => 'handler']); $container->setDefinition(HandlerB::class, ['tags' => 'handler']); $container->setDefinition(HandlerC::class, ['tags' => 'handler']); $container->setDefinition(Manager::class, ['arguments' => ['$handlers' => '#handler']]);
-
factory
-- determines if eachget
should create a new service (true
), or should one service be reused (false
, default). -
resolve
– instead of usingclass
+arguments
to construct the service, you can useresolve
to define how it should be created:$container->setDefinition('foo', ['resolve' => 4]); // 4 $container->setDefinition('language', ['resolve' => '@Request.locale.language']); // $container->get('Request')->getLocale()->getLanguage()
-
public
– determines if the service should be accessible directly withget
, or can it only be injected into other services.
ContainerCompiler: autowiring and autoconfiguration
Usually it’s obvious, which service should be injected into another.
For instance when your service has a constructor argument Psr\Cache\CacheItemPoolInterface $cache
,
and the container does have a service named Psr\Cache\CacheItemPoolInterface
,
then explicitly writing ['arguments' => ['$cache' => '@Psr\Cache\CacheItemPoolInterface']
is redundant.
You can always specify the dependencies manually, then autowiring won't overwrite them.
Autowiring is not magic – it just follows simple rules to determine, which service should be injected into the constructor:
- if the argument is a class which is defined in the container, use this service,
- if the argument is a class which is not defined in the container, try to autowire that class and create a private service out of it,
- if the argument is an array and its name ends with
s
(e.g.array $helpers
), inject an array of services with a specific tag (#helper
). - if the argument is of type
Bag
, inject the config value with its name (e.g.Bag $localisation
->@config.localisation
), - if its name starts with
env
, inject a parameter: (e.g.string $envCacheDir
->%CACHE_DIR%
) - if none of the above is true, but there is a default value, just use it,
- if none of the above is true, throw an exception – this argument should be defined explicitly.
Autoconfiguration is another way to make your life simpler.
For instance, if you’re using Twig,
you might want all the classes in your code that extend Twig\Extension\AbstractExtension
to be automatically registered as twig extension.
Autoconfiguration lets you define what default config (tags, public etc.) should be added to them.
To use autowiring and autoconfiguration, run the ContainerCompiler
:
$container = new Container;
$services = [
'App\' => [
'dir' => '%MODULE_DIR%/src/',
'exclude' => ['#^Entity/#'],
],
'App\Foo' => [
'arguments' => [
'$bar' => 5,
],
],
'App\Bar' => [
'public' => true,
],
];
$autoconfiguration = [
'Twig\Extension\AbstractExtension' => [
'tags' => ['twigExtension'],
],
];
$definitions = new ContainerCompiler(
$container,
$services,
$autoconfiguration
))->compile();
/** @var ServiceDefinition $definition */
foreach ($definitions as $name => $definition) {
if (!$container->has($name)) {
$container->setDefinition($name, $definition);
}
}
In this example, the whole %MODULE_DIR%/src/
except for (the /src/Entity
dir) will be scanned for PHP files
and all the found classes will be autowired as private services. If some of them are not used and not public,
they will be removed from the container.
Compiling the container has no impact on performance on production environment,
as long as you cache the result of compile()
.
ServiceLocator
Service locator restricts access to services in the container only to a selected list of names:
$container = new Container();
$container->set('foo', 'abc');
$container->set('bar', 'def');
$container->set('secret', 'XYZ');
$locator = new ServiceLocator($container, ['foo', 'bar']);
$locator->get('foo'); // 'abc'
$locator->get('bar'); // 'def'
$locator->get('secret'); // Exception
ContainerAssistedBuilder
ContainerAssistedBuilder
can be used to join together a couple of ContainerBuilderExtension
s
which encapsulate a set of service definitions that form a library together.
For an example, see Avris Localisator.
Micrus
This container was originally built as a part of the Micrus framework.