.. | ||
BaseContainer.php | ||
ContainerBuilder.php | ||
ContainerCache.php | ||
ContainerServiceProvider.php | ||
ContainerServiceProviderInterface.php | ||
ContainerStarter.php | ||
LegacyServiceGateway.php | ||
ProtectedPhpFileLoader.php | ||
README.md | ||
ServiceOptions.php | ||
ServiceOptionsInterface.php |
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?
- 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.
- Add the new
Container Service Provider
to themanifest.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.
- 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);
- 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)
]
);
}
}
- 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
}
}