Documentation/Container/Container structure

Container

The Container in the HLEB2 framework is a collection of so-called services, which can be retrieved from or added to the container.
Services are logically self-contained structures with a specific purpose.

In the HLEB2 framework, the initialization of services in the container is streamlined without unnecessary abstraction.
Services are not initialized by the framework from configuration, as is typically implemented, but rather within a special class App\Bootstrap\BaseContainer, which is accessible for editing by the developer using the framework. (Most often, you'll use the App\Bootstrap\ContainerFactory class, where services are defined as singletons.)
All the files for these classes are located in the /app/Bootstrap/ directory of the project.

This structure allows a significant number of services to be added to the container without a major impact on performance.


#BaseContainer Class

This class represents the container that will be used to retrieve services.

If a service needs to be a new instance of the class each time it's requested from the container, it should be specified here within a match() expression.

<?php
// File /app/Bootstrap/BaseContainer.php

namespace App\Bootstrap;

use 
Hleb\Constructor\Containers\CoreContainer;

final class 
BaseContainer extends CoreContainer implements ContainerInterface
{
    #[\Override]
    final public function 
get(string $id): mixed
    
{
        return 
ContainerFactory::getSingleton($id) ?? match ($id) {

            
// ... //

            
default => parent::get($id),
        };
    }
}

Adding a service is similar to adding it in the ContainerFactory class.


#ContainerFactory Class

A factory for creating services as singletons, with the ability to override the framework's default services. It's used to add custom services, which are initialized only once per request.

For example, we might need to add a RequestIdService that returns a unique ID for the current request. This is a demonstration example of a service; in general, services represent more complex structures. Let's add its creation to the ContainerFactory class:

<?php
// File /app/Bootstrap/ContainerFactory.php

namespace App\Bootstrap;

use 
App\Bootstrap\Services\RequestIdService;
use 
App\Bootstrap\Services\RequestIdInterface;
use 
Hleb\Constructor\Containers\BaseContainerFactory;

final class 
ContainerFactory extends BaseContainerFactory
{
    public static function 
getSingleton(string $id): mixed
    
{
        
self::has($id) or self::$singletons[$id] = match ($id) {

            
// New service controller.
            
RequestIdInterface::class => new RequestIdService(),

            default => 
null
        
};
        return 
self::$singletons[$id];
    }

    #[\Override]
    public static function 
rollback(): void
    
{
        
self::$singletons = [];
    }
}

Now, when the RequestIdInterface is requested from the container, it will return an instance of RequestIdService, stored as a singleton.
The key for retrieval can be defined not only as an interface but also as the base class RequestIdService, as it will be utilized in DI (Dependency Injection).

Despite the fact that the match() expression can contain multiple keys to a value, to avoid duplicating services (and consequently violating the singleton principle), only one should be assigned.


#Creating a Method in the Container

To simplify working with the new service keyed by RequestIdInterface, let's add a new method in the container. This will make it easier to find in the container through the IDE.
The new method requestId is added to the container class (BaseContainer). Now the class looks like this:

<?php
// File /app/Bootstrap/BaseContainer.php

namespace App\Bootstrap;

use 
App\Bootstrap\Services\RequestIdInterface;
use 
Hleb\Constructor\Containers\CoreContainer;

final class 
BaseContainer extends CoreContainer implements ContainerInterface
{
    #[\Override]
    final public function 
get(string $id): mixed
    
{
        return 
ContainerFactory::getSingleton($id) ?? match ($id) {

            
// ... //

            
default => parent::get($id),
        };
    }

    
// New method.
    
#[\Override]
    final public function 
requestId(): RequestIdInterface
    
{
        return 
$this->get(RequestIdInterface::class);
    }
}

Important! For this to work, the requestId method must also be added to the App\Bootstrap\ContainerInterface interface.

In the example, the service is assigned by interface, allowing the service class in the container to change while maintaining the interface linkage. For your own internal application classes, you can also omit the interface here and specify the class mapping directly.

For the framework's standard services, all these actions have already been done; you can retrieve them through the corresponding controller method.

The process of creating a new service is detailed in the example of adding a real library.

Creating interdependent services is described in the section non-standard container usage.


#rollback() Function of the Container

You have probably noticed the rollback() function in the ContainerFactory class. This function is necessary for resetting the states of services during asynchronous use of the framework, for example, when used with RoadRunner.

Here is how it works:
When the framework completes an asynchronous request, it resets the state of standard services.
Then, it calls the rollback() function to execute the code it contains to reset the state of manually added services.

Therefore, if the framework is used in asynchronous mode, you can initialize the service state reset (as well as that of any other module) here.

Console Commands Using the Container

Page translated: chatgpt 4-o
Back to top