Dependency Injection
With Habemus Container you can to decouple your class dependencies and inject them where they are needed.
Constructor Injection
Consider the scenario below:
<?php
interface FooInterface {}
class SimpleFoo implements FooInterface {}
class SpecialFoo implements FooInterface {}
class Bar {}
class MyClass
{
public $foo;
public $bar;
public function __construct(FooInterface $foo, Bar $bar)
{
$this->foo = $foo;
$this->bar = $bar;
}
}
As seen before, Habemus Container is able to resolve instances of objects and their dependencies by inspecting type hints in the constructors and no configuration is required. However, when dealing with an interface, the container is unable to resolve the dependency. In the example below, the container does not know how to resolve an instance of FooInterface and will throw an exception.
<?php
$container->get(FooInterface::class); // NotFoundException
$container->get(MyClass::class); // UnresolvableParameterException
In this case, you need specify how container will resolve instances of FooInterface:
<?php
// contanier will try to resolve with an instance of SimpleFoo
$container->add(FooInterface::class, SimpleFoo::class);
// or you can use a specify instance:
$container->add(FooInterface::class, new SimpleFoo() );
// or a factory
$container->add(FooInterface::class, fn() => new SimpleFoo());
$foo = $container->get(FooInterface::class);
var_dump($foo instanceof FooInterface); // true
var_dump($foo instanceof SimpleFoo); // true
$myClass = $container->get(MyClass::class);
var_dump($myClass->foo instanceof SimpleFoo); // true
var_dump($myClass->foo instanceof SpecialFoo); // false
Now, container will always resolve FooInterface instances with a SimpleFoo object, but you can use a specific instance in a particular class:
<?php
// a specific instance for MyClass:
$container->add(MyClass::class)
->constructor('foo', new SpecialFoo());
// or a reference to another service:
$container->add(MyClass::class)
->constructor('foo', Container::use(SpecialFoo::class));
$myClass = $container->get(MyClass::class);
var_dump($myClass->foo instanceof SimpleFoo); // false
var_dump($myClass->foo instanceof SpecialFoo); // true
$foo = $container->get(FooInterface::class);
var_dump($foo instanceof SimpleFoo); // true
As with interfaces, the container cannot resolve primitive types in the constructor.
<?php
class MyClass
{
public function __construct(int $min, int $max) {}
}
<?php
$container->get(MyClass::class); // UnresolvableParameterException
In this case, you need to specify constructor parameters for primitive types:
<?php
$container->add(MyClass::class)
->constructor('min', 1)
->constructor('max', 50);
// or a reference to another services in the container:
$container->add(MyClass::class)
->constructor('min', Container::use('config_min'))
->constructor('max', Container::use('config_max'));
Constructor Injection with Attributes
Hamebus allows you to use PHP 8 Attributes to inject dependencies in constructor parameters. You only need to pass the service identification as a parameter of the Inject
attribute. See how simple it is:
<?php
$container->add('config_min', 1);
$container->add('config_max', 50);
class MyClass
{
public function __construct(
#[Inject(SimpleFoo::class)]
public FooInterface $foo,
#[Inject('config_min')]
public int $min,
#[Inject('config_max')]
public int $max
) { }
}
All constructor dependencies will be resolved by the container.
<?php
//
$myClass = $container->get(MyClass::class);
var_dump($myClass->foo instanceof SimpleFoo); // true
var_dump($myClass->min); // 1
var_dump($myClass->max); // 50
Property Injection
Habemus property injection uses PHP 8 Attributes. You only need to pass the service identification as a parameter of the Inject
attribute. If identification is left empty, the container will attempt to use the type hint.
<?php
$container->add('config_min', 1);
class MyClass
{
#[Inject(SimpleFoo::class)]
protected $foo;
#[Inject]
protected SpecialFoo $specialFoo; // use type hint
#[Inject('config_min')]
private $min;
public function getSpecialFoo()
{
return $this->specialFoo;
}
public function getFoo()
{
return $this->foo;
}
public function getMin()
{
return $this->min;
}
}
<?php
$myClass = $container->get(MyClass::class);
var_dump($myClass->getFoo() instanceof SimpleFoo); // true
var_dump($myClass->getSpecialFoo() instanceof SpecialFoo); // true
var_dump($myClass->getMin()); // 1
Setter Injection
Habemus allows you to use setter injection.
class Foo {}
class MyClass
{
protected $foo;
public function setFoo(Foo $foo)
{
$this->foo = $foo;
}
public function getFoo()
{
return $this->foo;
}
}
<?php
// configuring setter method
$container->add(MyClass::class)
->addMethodCall('setFoo', [new SimpleFoo()]);
// configuring setter method, using a reference (parameter):
$container->add(MyClass::class)
->addMethodCall('setFoo', [Container::use(SimpleFoo::class)]);
$myClass = $container->get(MyClass::class);
var_dump($myClass->getFoo() instanceof SimpleFoo); // true
The container will resolve an instance of MyClass and then call the setter method.