league/container
PSR-11–compliant dependency injection container from The PHP League. Register services, factories and shared instances, then resolve dependencies with autowiring support. Modern PHP (8.3+) with full docs, tests, and MIT license.
Definitions are how Container describes your dependency map internally. Each definition contains information on how to build your classes.
Generally, Container will handle everything that is required to build a definition for you. When you invoke add, a Definition is built and returned meaning any further interaction is actually with the Definition, not Container.
<?php declare(strict_types=1);
$container = new League\Container\Container;
$definition = $container->add(Acme\Foo::class);
var_dump($definition instanceof League\Container\Definition\Definition); // true
You can also extend a definition if needed.
<?php declare(strict_types=1);
$container = new League\Container\Container;
$container->add(Acme\Foo::class);
// ... some more code
$container->extend(Acme\Foo::class)->addArgument(Acme\Bar::class);
$container->add(Acme\Bar::class);
$foo = $container->get(Acme\Foo::class);
var_dump($foo instanceof Acme\Foo); // true
var_dump($foo->bar instanceof Acme\Bar); // true
Creating definitions manually and passing them to the container is also possible.
<?php declare(strict_types=1);
$container = new League\Container\Container;
$fooDefinition = (new Definition(Acme\Foo::class))->addArgument(Acme\Bar::class);
$barDefinition = (new Definition(Acme\Bar::class));
$container->add($fooDefinition->getAlias(), $fooDefinition);
$container->add($barDefinition->getAlias(), $barDefinition);
$foo = $container->get(Acme\Foo::class);
var_dump($foo instanceof Acme\Foo); // true
var_dump($foo->bar instanceof Acme\Bar); // true
Container uses an aggregate to store all definitions. This means that you can build your aggregate first and pass it off to the container.
<?php declare(strict_types=1);
$definitions = [
(new Definition(Acme\Foo::class))->addArgument(Acme\Bar::class),
(new Definition(Acme\Bar::class)),
];
$aggregate = new League\Container\Definition\DefinitionAggregate($definitions);
$container = new League\Container\Container($aggregate);
$foo = $container->get(Acme\Foo::class);
var_dump($foo instanceof Acme\Foo); // true
var_dump($foo->bar instanceof Acme\Bar); // true
Interfaces are provided for definitions and the aggregates meaning that you can build your own implementations and pass them to Container as above if you need more or less functionality defined for your dependency map.
Definitions provide several methods to define what behaviour is desired on resolution of the object you are defining. All of these methods can be chained.
Adding an argument to a definition will pass that argument to the constructor of the defined class on instantiation, invoking this multiple times will pass more arguments, in the order they were added.
<?php declare(strict_types=1);
$container = new League\Container\Container;
$container
->add(Acme\Foo::class)
->addArgument(Acme\Bar::class)
->addArgument(Acme\Baz::class)
;
We also have a proxy method to pass multiple arguments in one call.
<?php declare(strict_types=1);
$container = new League\Container\Container;
$container->add(Acme\Foo::class)->addArguments([
Acme\Bar::class,
Acme\Baz::class,
]);
We can define one or multiple method calls and the arguments to be passed to them, these arguments will be resolved via the container. (The same method call can be added multiple times for multiple invokations with the same or different arguments).
<?php declare(strict_types=1);
$container = new League\Container\Container;
$container
->add(Acme\Foo::class)
->addMethodCall('setBar', [Acme\Bar::class])
->addMethodCall('setBaz', [Acme\Baz::class])
;
We also have a convenience method here to add multiple method calls to the definition at once.
<?php declare(strict_types=1);
$container = new League\Container\Container;
$container
->add(Acme\Foo::class)
->addMethodCalls([
['setBar', [Acme\Bar::class]],
['setBaz', [Acme\Baz::class]],
])
;
We can tell a definition to only resolve once and return the same instance every time it is resolved.
<?php declare(strict_types=1);
$container = new League\Container\Container;
$container
->add(Acme\Foo::class)
->setShared()
;
We also have a shortcut method to do this with one method call.
<?php declare(strict_types=1);
$container = new League\Container\Container;
$container->share(Acme\Foo::class);
If you would like to make all your definitions to default to shared, you can define that on Container, meaning that the add method will default to setting your definitions as shared and multiple calls to get will return the same instance. Only definitions after this is set will default to shared.
<?php declare(strict_types=1);
$container = (new League\Container\Container)->defaultToShared();
$container->add(Acme\Foo::class);
To define a definition not to be shared when the container is set to default to shared, we can reverse the functionality of defining it as shared.
<?php declare(strict_types=1);
$container = (new League\Container\Container)->defaultToShared();
$container->add(Acme\Foo::class, Acme\Foo::class, $shared = false);
If we have a definition marked as shared and we want to force the retrieval of a new instance, we can pass a second argument to Container on get.
<?php declare(strict_types=1);
$container = new League\Container\Container;
$container
->add(Acme\Foo::class)
->setShared()
;
$container->get(Acme\Foo::class, $new = true);
We can tag definitions and retrieving the alias given to the tag will resolve all definitions using that tag in an indexed array. You can add multiple tags to each definition.
<?php declare(strict_types=1);
$container = new League\Container\Container;
$container->add(Acme\Foo::class)->addTag('foos');
$container->add(Acme\FooBar::class)->addTag('foos')->addTag('bars');
$container->add(Acme\Bar::class)->addTag('bars');
$foos = $container->get('foos'); // [Foo, FooBar]
$bars = $container->get('bars'); // [FooBar, Bar]
How can I help you explore Laravel packages today?