A PSR-11 container implementation with optional autowiring.
- PHP 8.1 - 8.4
composer require selective/containeruse 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.