tao-test/app/generis/core/DependencyInjection
2022-08-29 20:14:13 +02:00
..
BaseContainer.php Code, before instalation. 2022-08-29 20:14:13 +02:00
ContainerBuilder.php Code, before instalation. 2022-08-29 20:14:13 +02:00
ContainerCache.php Code, before instalation. 2022-08-29 20:14:13 +02:00
ContainerServiceProvider.php Code, before instalation. 2022-08-29 20:14:13 +02:00
ContainerServiceProviderInterface.php Code, before instalation. 2022-08-29 20:14:13 +02:00
ContainerStarter.php Code, before instalation. 2022-08-29 20:14:13 +02:00
LegacyServiceGateway.php Code, before instalation. 2022-08-29 20:14:13 +02:00
ProtectedPhpFileLoader.php Code, before instalation. 2022-08-29 20:14:13 +02:00
README.md Code, before instalation. 2022-08-29 20:14:13 +02:00
ServiceOptions.php Code, before instalation. 2022-08-29 20:14:13 +02:00
ServiceOptionsInterface.php Code, before instalation. 2022-08-29 20:14:13 +02:00

Dependency Injection

We are deprecating the current service registry (oat\oatbox\service\ServiceManager) in favor of a faster and more modern PSR-11 compliant solution based on Symfony Dependency Injection documentation.

  • For now on, all the new services MUST be created as explained bellow.
  • Whenever is possible, old services SHOULD be migrated to the new container.

How to add new services to container?

  1. In any tao extension, create a Container Service Provider. Example:
<?php
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;
use function Symfony\Component\DependencyInjection\Loader\Configurator\env;
use function Symfony\Component\DependencyInjection\Loader\Configurator\param;

class MyContainerServiceProvider implements ContainerServiceProviderInterface
{
    public function __invoke(ContainerConfigurator $configurator): void
    {
        $services = $configurator->services();
        $parameters = $configurator->parameters();
        
        $parameters->set('someParam', 'someValue');

        $services->set(MyService::class, MyService::class)
            ->public()
            ->args(
                [
                    service(MyOtherService::class),
                    service(MyLegacyService::SERVICE_ID),
                    env('MY_ENV_VAR'),
                    param('someParam'),
                ]
            );
    }
}

RECOMMENDATION: Avoid inflating the ContainerServiceProvider with too many services/params/etc. Be wise and use common sense to group your services within different Container Service Providers classes.

For more information read the Symfony Dependency Injection documentation.

  1. Add the new Container Service Provider to the manifest.php file of the extension.
<?php
return [
    // other manifest configs
    'containerServiceProviders' => [
        MyContainerServiceProvider::class
    ]
];

How is the container started?

To start the container, we need to use the ContainerBuilder. Example:

use oat\oatbox\service\ServiceManager;
use oat\generis\model\DependencyInjection\ContainerStarter;

$container = (new ContainerStarter(ServiceManager::getServiceManager()))->getContainer();

IMPORTANT: This is already done on ServiceManager->getContainer(), so you do not need to do it.

Accessing the container inside a legacy controller

You just need to use this method $this->getPsrContainer().

use oat\tao\model\http\Controller;
use Zend\ServiceManager\ServiceLocatorAwareInterface;
use Zend\ServiceManager\ServiceLocatorAwareTrait;

class MyController extends Controller implements ServiceLocatorAwareInterface
{
    use ServiceLocatorAwareTrait;

    public function myMethod(): void
    {
        $service = $this->getPsrContainer()->get(MyService::class);
        // Other logic...
    }
}

Using legacy configuration on new services

ATTENTION! It is NOT RECOMMENDED to use the ServiceOptions class. New services on container should rely on ENVIRONMENT VARIABLES, but when this is really not possible and ObjectOriented techniques such as Proxy, Factory, Strategy, etc cannot solve the issue, then maybe you should consider using it.

  1. Registering new configs

In a new migrations file or installation script, register the new options. Example:

use oat\generis\model\DependencyInjection\ServiceOptions;

$serviceOptions = $serviceLocator->get(ServiceOptions::SERVICE_ID);
$serviceOptions->save(MyService::class, 'foo', 'bar');

$serviceLocator->register(ServiceOptions::SERVICE_ID, $serviceOptions);
  1. Inject the ServiceOptions in your service through the ServiceProvider:
<?php
use Symfony\Component\DependencyInjection\Loader\Configurator\ContainerConfigurator;
use oat\generis\model\DependencyInjection\ServiceOptions;
use function Symfony\Component\DependencyInjection\Loader\Configurator\service;

class MyContainerServiceProvider implements ContainerServiceProviderInterface
{
    public function __invoke(ContainerConfigurator $configurator): void
    {
        $configurator->services()->set(MyService::class, MyService::class)->args(
            [
                service(ServiceOptions::SERVICE_ID)
            ]
        );
    }
}
  1. Call it inside the new service like:
use oat\generis\model\DependencyInjection\ServiceOptionsInterface;

class MyService
{
    /** @var ServiceOptionsInterface */
    private $options;

    public function __construct(ServiceOptionsInterface $options)
    {
        $this->options = $options;
    }

    public function foo()
    {
        $bar = $this->options->get(self::class, 'foo'); // Will get "bar" as response
    }
}

Warming up container cache

The cache warmup happens when we run taoUpdate.php, but if you need to warmup the cache only, run:

php index.php 'oat\generis\scripts\tools\ContainerCacheWarmup'

Avoid caching / Debug mode

To avoid container caching (useful on dev mode), please add the following variable on your .env file.

DI_CONTAINER_DEBUG=true

Testing container inside Legacy Services (ConfigurableService)

While in a legacy class (extending ConfigurableService) and calling the container inside, you can mock the services with getServiceLocatorMock (The same way we do for ServiceManager). Example:

Having a class:

use oat\oatbox\service\ConfigurableService;
use oat\generis\persistence\PersistenceManager;

class MyLegacyServiceTest extends ConfigurableService
{
    public function something()
    {
        return get_class($this->getServiceLocator()->getContainer()->get(PersistenceManager::SERVICE_ID));
    }
}

In your test:

use oat\generis\persistence\PersistenceManager;
use oat\generis\test\TestCase;

class MyLegacyServiceTest extends TestCase
{
    public function testSomething(): void
    {
        $sut = new MyLegacyService();
        $sut->setServiceLocator(
            $this->getServiceLocatorMock(
                [
                    PersistenceManager::SERVICE_ID => $this->createMock(PersistenceManager::class),
                ]
            )
        );
        
        echo $sut->something(); // Will return the $persistenceManager class
    }
}