/caplet

A minimal autowiring DI container.

Primary LanguagePHPMIT LicenseMIT

Caplet

Caplet is a minimal autowiring dependency injection container to handle basic constructor injection and object factories.

Getting Started

Instantiate Caplet like so:

use Caplet\Caplet;

$caplet = new Caplet();

You can then call one of these PSR-11 methods:

  • get(string $class) : object to get a shared object instance of $class.

  • has(string $class) : bool to see if an instance is available. (This means either a class definition exists; or, in the case of an interface, the interface definition exists and has a factory() entry -- see below for the factory() method.)

Caplet offers this non-PSR-11 method:

  • new(string $class) : object to get a new object instance of $class. (This method is not part of PSR-11.)

Configuration

Configure non-object constructor arguments by passing an array with the structure $config['ClassName']['parameterName'] at Caplet construction time. For example, given the following class ...

namespace Foo;

class Bar
{
    public function __construct(
        protected string $bar,
        protected string $baz
    ) {
    }
}

... you would configure the arguments for its parameters like so:

use Caplet\Caplet;
use Foo\Bar;

$caplet = new Caplet([
    Bar::class => [
        'bar' => 'bar-value',
        'baz' => 'baz-value',
    ];
]);

$bar = $caplet->get(Bar::class);

Alternatively, extend Caplet and override __construct() to accept your own environment or configuration values, then call the parent::__construct() with the $config['ClassName']['parameterName'] structure.

namespace Project;

use Caplet\Caplet;

class ProjectCaplet extends Caplet
{
    public function __construct(array $env)
    {
        parent::__construct([
            Foo::class => [
                'bar' => $env['BAR_VALUE'],
                'baz' => $env['BAZ_VALUE'],
            ],
        ]);
    }
}

Factories

Extending Caplet also allows you to call the protected factory() method inside the constructor to define the object-creation logic for a given type. This allows you to specify concrete classes for instantiation in place of abstracts or interfaces. For example:

namespace Project;

use Caplet\Caplet;
use Project\Log\Logger;
use Psr\Log\LoggerInterface;

class ProjectCaplet extends Caplet
{
    public function __construct(
        string $bar,
        string $baz,
    ) {
        parent::__construct([
            Foo::class => [
                'bar' => 'bar-value',
                'baz' => 'baz-value',
            ],
        ]);

        $this->factory(
            LoggerInterface::class,
            fn (Caplet $caplet) => $caplet->get(Logger::class)
        );
    }
}

As seen above, the callable factory logic must have the signature function (Caplet $caplet), and may specify a return type.

Constructor Parameter Resolution

Caplet will attempt to resolve constructor parameters in this order:

  • First, use an argument from $config, if one is available.
  • Next, try to get() an object of the parameter type.
  • Last, use the default parameter value, if one is defined.

If none of these work, Caplet will throw Exception\NotInstantiated, with a previous exception of Exception\NotResolved.