ricotta / container
Dependency Injection Container for the Ricotta project
Requires
- php: >=8.4
- psr/container: ^2.0
Requires (Dev)
- codeception/codeception: ^5.1
- codeception/module-asserts: ^3.0
- phpstan/phpstan: ^2.1
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.