A simple and fast PSR-11 compatible DI container with features to autowire, tag
and inject services. Instead of YAML or config files services are simply defined
at a class by adding i.e. a getMyService
method.
It is possible to extend the Container
class. All getXXX
methods are service
definitions which can be accessed through the get
method.
<?php
use PSX\Dependency\Attribute\Tag;
use PSX\Dependency\Container;
use PSX\Dependency\Tests\Playground\FooService;
use PSX\Dependency\Tests\Playground\BarService;
class MyContainer extends Container
{
public function getFooService(): FooService
{
return new FooService();
}
#[Tag('my_tag')]
public function getBarService(): BarService
{
return new BarService($this->get('foo_service'));
}
}
The following example shows how you can use the autowiring feature:
<?php
use PSX\Dependency\Inspector\ContainerInspector;
use PSX\Dependency\TypeResolver;
use PSX\Dependency\AutowireResolver;
use PSX\Dependency\Tests\Playground\MyContainer;
use PSX\Dependency\Tests\Playground\AutowireService;
$container = new MyContainer();
$inspector = new ContainerInspector($container);
$typeResolver = new TypeResolver($container, $inspector);
$autowireResolver = new AutowireResolver($typeResolver);
$service = $autowireResolver->getObject(AutowireService::class);
The autowire resolver checks all arguments of the constructor of the AutowireService
class and tries to resolve each type based on the return type of the method
definitions in the container. Please take a look at test cases to see a complete
example.
It is also possible to provide a factory resolver which allow to resolve i.e. repository classes:
<?php
use Psr\Container\ContainerInterface;
use PSX\Dependency\TypeResolver;
use PSX\Dependency\AutowireResolver;
use PSX\Dependency\Tests\Playground\RepositoryInterface;
$typeResolver = new TypeResolver(...);
$autowireResolver = new AutowireResolver($typeResolver);
$typeResolver->addFactoryResolver(RepositoryInterface::class, function (string $class, ContainerInterface $container): RepositoryInterface {
return $container->get('table_manager')->getRepository($class);
});
// this now allows to use the MyRepository class as a type-hint at a service and
// the autowire resolver injects the fitting service through the defined resolver
$repository = $autowireResolver->getObject(MyService::class);
The following example shows how to get services which are annotated by a specific tag:
<?php
use PSX\Dependency\Inspector\ContainerInspector;
use PSX\Dependency\TagResolver;
use PSX\Dependency\Tests\Playground\MyContainer;
$container = new MyContainer();
$inspector = new ContainerInspector($container);
$tagResolver = new TagResolver($container, $inspector);
$services = $tagResolver->getServicesByTag('my_tag');
To tag you service you need to add the #Tag
attribute to your service
definition method. Then it is possible to use the tag resolver to receive all
services which have added a specific tag.
If you have created a large container it is possible to compile this container into an optimized class which improves the performance.
use PSX\Dependency\Compiler\PhpCompiler;
use PSX\Dependency\Tests\Playground\MyContainer;
$compiler = new PhpCompiler('Container', __NAMESPACE__);
$container = new MyContainer();
// contains the compiled DI container
$code = $compiler->compile($container);
The object builder resolves properties with an #Inject
attribute and tries
to inject the fitting service to the property. If no explicit service name was
provided the property name is used. Note usually it is recommended to use simple
constructor injection, this class is designed for cases where this is not
feasible.
<?php
class MyController
{
#[Inject]
protected FooService $fooService;
#[Inject('bar_service')]
protected BarService $baz;
public function doSomething()
{
$this->fooService->power();
}
}
The object builder can use a cache instance to cache all defined service keys in production.
<?php
use PSX\Dependency\ObjectBuilder;
use PSX\Dependency\Tests\Playground\MyContainer;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
$container = new MyContainer();
$cache = new Pool(new ArrayCache());
$debug = false;
$builder = new ObjectBuilder(
$container,
$reader,
$cache,
$debug
);
$controller = $builder->getObject(MyController::class);
It is also possible to set services on a container in the "Pimple" way. Through this you can easily extend or overwrite existing containers. Note it is not possible to use theses services for autowiring. In general it is recommended to create a custom container and extend from the default container to add new services.
<?php
use Psr\Container\ContainerInterface;
$container = new \PSX\Dependency\Container();
$container->set('foo_service', function(ContainerInterface $c){
return new FooService();
});
$container->set('bar_service', function(ContainerInterface $c){
return new BarService($c->get('foo_service'));
});