Capsule

Clean, concise, composable dependency injection for PHP 8.

Installation

Install Capsule via Composer:

composer require capsule/di ^3.0

The Github repository is at capsulephp/di.

Autowiring Container

Capsule will auto-inject typehinted constructor parameters.

use Capsule\Di\Container;
use Capsule\Di\Definitions;

class Foo implements FooInterface
{
    public function __construct(
        protected Bar $bar,
        protected string $baz = 'dib'
    ) {
    }

    public function getBaz()
    {
        return $this->baz;
    }

    public function setBaz(string $baz)
    {
        $this->baz = $baz;
    }

    // ... other methods ...
}

$def = new Definitions();
$container = new Container($def);

$foo = $container->get(Foo::CLASS);
echo $foo->getBaz(); // 'dib'

Learn more about Definitions and the Container.

Initial Construction

Capsule definitions are written separately from the Container itself.

You can configure constructor arguments by position, name, or type ...

$def = new Definitions();

$def->{Foo::CLASS}
    ->arguments([
        'baz' => 'zim'
    ]);

$container = new Container($def);

$foo = $container->get(Foo::CLASS);
echo $foo->getBaz(); // zim

... or you can take over construction completely using a factory callable:

$def->{Foo::CLASS}
    ->factory(function (Container $container) {
        return new Foo(new Bar(), 'irk');
    });

$container = new Container($def);

$foo = $container->get(Foo::CLASS);
echo $foo->getBaz(); // irk

Learn more about class definitions.

Extended Construction

You can call setter methods after construction ...

$def->{Foo::CLASS}
    ->method('setBaz', 'baz-setter');

... modify the object yourself via a callable ...

$def->{Foo::CLASS}
    ->modify(function (Container $container, Foo $foo) {
        $foo->doSomething();
    });

... inject properties on the object ...

$def->{Foo::CLASS}
    ->property('propertyName', 'injectedValue');

... or decorate it via another callable, as you see fit:

$def->{Foo::CLASS}
    ->decorate(function (Container $container, Foo $foo) {
        return new DecoratedFoo($foo);
    });

These post-construction methods can be combined in any order any number of times.

Learn more about class definitions.

Interface Implementation

Binding an interface to an implementation is trivial ...

$def->{FooInterface::CLASS}
    ->class(Foo::CLASS);

... or you can specify a factory callable over the interface:

$def->{FooInterface::CLASS}
    ->factory(function (Container $container) {
        return new Foo(new Bar())
    });

Learn more about interface definitions.

Lazy Resolution

Anywhere you need a parameter argument, you can use a Defintions method to specify lazy-resolution for that argument.

For example, reading from the environment at construction ...

$def->{Foo::CLASS}
    ->argument('bar', $def->env('FOO_BAR'));

... as well as retrieving shared or new objects from the container, making calls to those objects after retriving them, making function or static method calls, invoking any other callable, or capturing the return of an included or required file.

Lazy resolution can be used almost anywhere in a definition, including in argument() and method() calls, as well as for primitive values.

Learn more about lazy resolution.

Named Objects and Values

Defining named objects and primitive values is trivial:

$def->{'db.host'} = $def->env('DB_HOST');
$def->{'db.user'} = $def->env('DB_USER');
$def->{'db.pass'} = $def->env('DB_PASS');

$def->db = $def->newDefinition(Database::CLASS)
    ->arguments([
        'host' => $def->get('db.host'),
        'user' => $def->get('db.user'),
        'pass' => $def->get('db.pass')
    ]);

Learn more about value definitions.

Providing Capsule Definitions

A Capsule container can receive any number of defintion providers, each of which can be implemented at any layer of your system. Writing a provider implementation is straightforward:

use Capsule\Di\Provider;
use Capsule\Di\Definitions;

class MyProvider implements Provider
{
    public function provide(Definitions $def) : void
    {
        // work with $def to add definitions
    }
}

You can then compose any series of providers as you like, in any iterable you like, when creating the Capsule container. For example, in production, you might do this ...

$providers = [
    new DomainProvider(),
    new ApplicationProvider(),
    new InfrastructureProvider(),
    new HttpProvider(),
];

$def = new Definitions();

$container = new Container($def, $providers);

... but in testing, you might alternatively compose your providers this way instead:

$providers = [
    new DomainProvider(),
    new ApplicationProvider(),
    new IntegrationTestingProvider(),
];

Learn more about definition providers.


Using a previous version of Capsule? Upgrade!