ricotta/container

Dependency Injection Container for the Ricotta project

0.1.2 2025-02-27 10:04 UTC

This package is auto-updated.

Last update: 2025-06-02 13:05:18 UTC


README

Dependency Injection Container for the Ricotta project.

Installation

The package can be installed via composer.

composer require ricotta/container

Usage

Lifecycle

The Ricotta is created by passing a Bootstrapping object to the Container constructor.

use Ricotta\Container\Bootstrapping;
use Ricotta\Container\Container;

$bootstrap = new Ricotta\Container\Bootstrapping();

$container = new Ricotta\Container\Container($bootstrap);

The Bootstrapping object is used to register components and configure the container.

Once the container is created, any changes to the Bootstrapping object will not be reflected in the container.

This secures a consistent state of the container and prevents changes to the container after it is created.

The Bootstrapping object

The Bootstrapping object is used to register components and configure the container.

To define a component, use the Bootstrapping object as an array, where the key is the component name.

$boostrap = new Ricotta\Container\Bootstrapping();

$bootstrap[MyService::class]->register();
$bootstrap[MyService::class]->configure(fn (MyService $service) => $service->setConfig('value'));

$container = new Ricotta\Container\Container($bootstrap);

$service = $container->get(MyService::class);

Registering components by their type as their type is the simplest way to register a component.

The container will automatically resolve dependencies by their type whenever possible.

Custom bootstrapping is done through the returned Registration object through a declarative API.

$bootstrap[MyService::class]
    ->register()
    ->callback(fn (MyRepository $repository) => new Service($repository))
    ->arguments([
        MyRepository::class => Bootstrapping['my.repository']->reference()
    ]);

$bootstrap[MyService::class]
    ->configure(fn (MyService $service) => $service->setConfig('value'));
$bootstrap[MyService::class]
    ->replace(fn (MyService $service) => new MyServiceDecorator($service));

$container = new Ricotta\Container\Container($bootstrap);

// Returns a MyServiceDecorator instance with MyService instance injected.
$service = $container->get(MyService::class);

Registering Components

All components should be registered with the Bootstrapping object. Autowiring is not enabled by default, but may be enabled for specific subtypes. See Boostrapping::allowAutowiring().

Registering by Type

Components can be registered by their type. The container will automatically resolve dependencies by their type.

$bootstrap[MyService::class]->register();

Registering by Name

Components can also be registered by a custom name.

$bootstrap['my.service']->register()->type(MyService::class);

Registering with a Callback

Components can be registered with a callback to create the component.

$bootstrap[MyService::class]->register();
$bootstrap[MyRepository::class]
    ->register()
    ->callback(
        fn (MyService $service) => new MyRepository($service, 'my_table')
    );

Registering with a type

Components can be registered with a type, which is handy for dependency inversion patterns.

$bootstrap[MyServiceInterface::class]
    ->register()
    ->type(MyService::class);

$container = new Ricotta\Container\Container($bootstrap);

// Returns an instance of MyService.
$service = $container->get(MyServiceInterface::class); 

Registering with argument map.

For components with dependencies that cannot be resolved by type, an argument map can be used.

Values can be injected directly or by using a bootstrapping reference to a component name.

References will not be resolved until the component is created.

$bootstrap['my.service']->register()->type(MyService::class);
$bootstrap[MyRepository::class]
    ->register()
    ->callback(function (MyService $service, string $table) {
        return new MyRepository($service, 'my_table')
    })
    ->arguments([
        'table' => 'my_table',
        'service' => Bootstrapping['my.service']->reference()
    ]);

The arguments can also be used to inject into the constructor when no callback is provided.

$bootstrap['my.service']
    ->register()
    ->type(MyService::class);
$bootstrap[MyRepository::class]
    ->register()
    ->arguments([
        'table' => 'my_table', 
        MyService::class => Bootstrapping['my.service']->reference()
    ]);

Alias names

Components can be aliased to a different name by using a callback method like this:

$bootstrap[MyService::class]->register();
$bootstrap[MyServiceInterface::class]
    ->register()
    ->callback(fn (MyService $service) => $service);

$container = new Ricotta\Container\Container($bootstrap);

// Returns an instance of MyService.
$service = $container->get(MyServiceInterface::class);
// Reference to the same instance. 
$service = $container->get(MyService::class); 

Configuring Components

Components can be configured after they are created by using the configure method.

$bootstrap[MyService::class]->register();
$bootstrap[MyService::class]
    ->configure(fn (MyService $service) => $service->setConfig('value'));
$bootstrap[MyService::class]
    ->configure(fn (MyService $service) => $service->setConfig('value 2'));

$container = new Ricotta\Container\Container($bootstrap);

$service = $container->get(MyService::class);
echo $service->getConfig(); // Outputs 'value 2'.

Replacing Components

Components can be replaced by using the replace method.

$bootstrap[MyService::class]->register();
$bootstrap[MyService::class]
    ->replace(fn (MyService $service) => new MyServiceDecorator($service));

$container = new Ricotta\Container\Container($bootstrap);

$service = $container->get(MyService::class); // Returns an instance of MyServiceDecorator.

Order of configure() and replace() calls

The order of configure() and replace() calls is important. The configure() callbacks and replace() callbacks are executed in the order they are registered.

$bootstrap[MyService::class]->register();

// Executed first.
$bootstrap[MyService::class]
    ->configure(fn (MyService $service) => $service->setConfig('value'));

// Executed second.
$bootstrap[MyService::class]
    ->replace(fn (MyService $service) => new MyServiceDecorator($service));

// Executed third.
$bootstrap[MyService::class]
    ->configure(fn (MyService $service) => $service->setConfig('value 2'));

$container = new Ricotta\Container\Container($bootstrap);

// Returns an instance of MyServiceDecorator.
$service = $container->get(MyService::class);
// Outputs 'value 2'. 
echo $service->getConfig(); 

Autowiring

Autowiring can be enabled for specific subtypes by using the allowAutowiring method.

class Controller implements ControllerInterface
{
    // ...
}

$bootstrap = new Ricotta\Container\Bootstrapping();

$bootstrap->allowAutowiring(ControllerInterface::class);

$container = new Ricotta\Container\Container($bootstrap);

// Because autowiring is enabled for ControllerInterface, the container will
// automatically resolve the Controller class without the need to register it.
// This is useful for usage that allows autowiring of certain types.

$controller = $container->get(Controller::class);

Bootstrapping References

Bootstrapping references can be used to reference components that are not yet created.

The reference will not be resolved before it is needed.

$bootstrap[MyService::class]->register();
$bootstrap[MyService::class]
    ->configure(fn (MyService $service) => $service->setConfig('value'));


$bootstrap[MyRepository::class]
    ->register()
    ->arguments(['parameterName' => $bootstrap[MyService::class]->reference()]);
]);

What's Not Here

  • The container does not support circular dependencies.
  • The container does not support autowiring by default.

Modularity support is not added. Creating a modular system is up to the user. A framework could define a module system that uses the container to create modules.

interface Module
{
    // Register components and configure the container for the module.
    public function register(Bootstrapping $bootstrap): void;
}

License

The Ricotta project is open-sourced software licensed under the MIT license.