A PSR-11 container implementation with optional autowiring.
- PHP 8.1+
composer require selective/container
use Selective\Container\Container;
$container = new Container();
// ...
$myService = $container->get(MyService::class);
The container is able to automatically create and inject dependencies for you. This is called "autowiring".
To enable autowiring you have to add the ConstructorResolver
:
<?php
use Selective\Container\Container;
use Selective\Container\Resolver\ConstructorResolver;
$container = new Container();
// Enable autowiring
$container->addResolver(new ConstructorResolver($container));
//...
You can use a factories (closures) to define injections.
<?php
use App\Service\MyService;
use Selective\Container\Container;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
$container = new Container();
// Add definition
$container->factory(MyService::class, function (ContainerInterface $container) {
return new MyService();
});
use Psr\Container\ContainerInterface;
// ...
$entries = [
MyService::class => function (ContainerInterface $container) {
return new MyService();
},
PDO::class => function (ContainerInterface $container) {
return new PDO('sqlite:example.db');
},
// and so on...
];
$container->factories($entries);
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 at the point where a service is retrieved.
To build a service provider create a invokable class and return the definitions (factories) you would like to register.
<?php
use App\Service\MyService;
use Selective\Container\Container;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
final class MyServiceFactoryProvider
{
/**
* @return array<string, callable>
*/
public function __invoke(): array
{
return [
MyService::class => function (ContainerInterface $container) {
return new MyService($container->get(LoggerInterface::class));
},
];
}
}
$container->factories((new MyServiceFactoryProvider())());
In addition to defining entries in an array of factories / callbacks, you can also set the value directly as shown below:
$container->set(\App\Domain\MyService::class, new \App\Domain\MyService());
To fetch a value use the get
method:
$pdo = $container->get(PDO::class);
- Make sure that your container will be recreated for each test. You may use the phpunit
setUp()
method to initialize the container definitions. - You can use the
set()
method to overwrite existing container entries.
The set
method can also be used to set mocked objects directly into the container.
This example requires phpunit:
<?php
$class = \App\Domain\User\Repository\UserRepository::class;
$mock = $this->getMockBuilder($class)
->disableOriginalConstructor()
->getMock();
$mock->method('methodToMock1')->willReturn('foo');
$mock->method('methodToMock2')->willReturn('bar');
$container->set($class, $mock);
Example to boostrap a Slim 4 application using the container:
<?php
use Selective\Container\Container;
use Selective\Container\Resolver\ConstructorResolver;
use Slim\App;
use Slim\Factory\AppFactory;
require_once __DIR__ . '/../vendor/autoload.php';
$container = new Container();
// Enable autowiring
$container->addResolver(new ConstructorResolver($container));
// Load container definitions
$container->factories(require __DIR__ . '/container.php');
// Create slim app instance
AppFactory::setContainer($container);
$app = AppFactory::create();
// Add routes, middleware etc...
$app->run();
The container.php
file must return an array of factories (closures):
<?php
use Monolog\Logger;
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
return [
'settings' => function () {
return require __DIR__ . '/settings.php';
},
LoggerInterface::class => function (ContainerInterface $container) {
$logger = new Logger('name');
// ...
return $logger;
},
// Add more definitions here...
]
If you use PhpStorm, then create a new file .phpstorm.meta.php
in your project root directory and copy/paste the following content:
<?php
namespace PHPSTORM_META;
override(\Psr\Container\ContainerInterface::get(0), map(['' => '@']));
selective/container
is about:
- 11% faster then
php-di/php-di
. - 5.4% faster then
league/container
.
All tests where made with enabled autowiring.
This PSR-11 container implementation mimics the behavior of PHP-DI.
If you already use factories for your container definitions, the switch should be very simple.
Replace this:
<?php
use DI\ContainerBuilder;
// ...
$containerBuilder = new ContainerBuilder();
$containerBuilder->addDefinitions(__DIR__ . '/container.php');
$container = $containerBuilder->build();
... with this:
<?php
use Selective\Container\Container;
use Selective\Container\Resolver\ConstructorResolver;
// ...
$container = new Container();
// Enable auto-wiring
$container->addResolver(new ConstructorResolver($container));
// Add definitions
$container->factories(require __DIR__ . '/container.php');
That's it.
- Dominik Zogg (chubbyphp)
- https://github.com/chubbyphp/chubbyphp-container
- http://php-di.org/
- https://container.thephpleague.com/
The MIT License (MIT). Please see License File for more information.