TYPO3 v15 dev-main snapshot ()
This commit is contained in:
@@ -0,0 +1,50 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use TYPO3\CMS\Core\Security\AllowedCallableAssertion;
|
||||
|
||||
final readonly class AllowedCallablePass implements CompilerPassInterface
|
||||
{
|
||||
public function __construct(private string $tagName) {}
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->hasDefinition(AllowedCallableAssertion::class)) {
|
||||
return;
|
||||
}
|
||||
$definition = $container->findDefinition(AllowedCallableAssertion::class);
|
||||
$definition->setArgument('$items', $this->resolveItems($container));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return list<array{class-string, string}>
|
||||
*/
|
||||
private function resolveItems(ContainerBuilder $container): array
|
||||
{
|
||||
$items = [];
|
||||
foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) {
|
||||
foreach ($tags as $tag) {
|
||||
$items[] = [$id, $tag['method']];
|
||||
}
|
||||
}
|
||||
return $items;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use TYPO3\CMS\Core\Service\DependencyOrderingService;
|
||||
use TYPO3\CMS\Core\SystemResource\Publishing\DefaultSystemResourcePublisher;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final readonly class AssetFileSystemPublisherPass implements CompilerPassInterface
|
||||
{
|
||||
private DependencyOrderingService $orderingService;
|
||||
|
||||
public function __construct(private string $tagName)
|
||||
{
|
||||
$this->orderingService = new DependencyOrderingService();
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->hasDefinition(DefaultSystemResourcePublisher::class)) {
|
||||
// If there's no default system resource publisher registered to begin with, don't bother registering file system publishers with it.
|
||||
return;
|
||||
}
|
||||
$publishers = [];
|
||||
$unorderedPublishers = $this->collectPublishers($container);
|
||||
foreach ($this->orderingService->orderByDependencies($unorderedPublishers) as $publisher) {
|
||||
$publishers[] = $container->findDefinition($publisher['service']);
|
||||
}
|
||||
$publisherDefinition = $container->findDefinition(DefaultSystemResourcePublisher::class);
|
||||
$publisherDefinition->addArgument($publishers);
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all listeners from the container.
|
||||
*/
|
||||
private function collectPublishers(ContainerBuilder $container): array
|
||||
{
|
||||
$unorderedPublishers = [];
|
||||
foreach ($container->findTaggedServiceIds($this->tagName) as $serviceName => $tags) {
|
||||
foreach ($tags as $attributes) {
|
||||
$publisherIdentifier = $attributes['identifier'] ?? $serviceName;
|
||||
$unorderedPublishers[$publisherIdentifier] = [
|
||||
'service' => $serviceName,
|
||||
'before' => GeneralUtility::trimExplode(',', $attributes['before'] ?? '', true),
|
||||
'after' => GeneralUtility::trimExplode(',', $attributes['after'] ?? '', true),
|
||||
];
|
||||
}
|
||||
}
|
||||
return $unorderedPublishers;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,116 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\Compiler\AbstractRecursivePass;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use TYPO3\CMS\Core\Log\Logger;
|
||||
use TYPO3\CMS\Core\Log\LogManager;
|
||||
|
||||
/**
|
||||
* Looks for definitions with autowiring enabled and registers their corresponding "inject*" methods as setters.
|
||||
*/
|
||||
class AutowireInjectMethodsPass extends AbstractRecursivePass
|
||||
{
|
||||
/**
|
||||
* @param bool $isRoot
|
||||
*/
|
||||
protected function processValue($value, $isRoot = false): mixed
|
||||
{
|
||||
$value = parent::processValue($value, $isRoot);
|
||||
|
||||
if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
|
||||
return $value;
|
||||
}
|
||||
if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$alreadyCalledMethods = [];
|
||||
|
||||
foreach ($value->getMethodCalls() as [$method]) {
|
||||
$alreadyCalledMethods[strtolower($method)] = true;
|
||||
}
|
||||
|
||||
$addInitCall = false;
|
||||
|
||||
foreach ($reflectionClass->getMethods() as $reflectionMethod) {
|
||||
$r = $reflectionMethod;
|
||||
|
||||
if ($r->isConstructor() || isset($alreadyCalledMethods[strtolower($r->name)])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($reflectionMethod->isPublic() && str_starts_with($reflectionMethod->name, 'inject')) {
|
||||
$this->addInjectMethodCall($value, $reflectionMethod);
|
||||
}
|
||||
|
||||
if ($reflectionMethod->name === 'initializeObject' && $reflectionMethod->isPublic()) {
|
||||
$addInitCall = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($addInitCall) {
|
||||
// Add call to initializeObject() which is required by classes that need to perform
|
||||
// constructions tasks after the inject* method based injection of dependencies.
|
||||
$value->addMethodCall('initializeObject');
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
private function addInjectMethodCall(Definition $definition, \ReflectionMethod $reflectionMethod): void
|
||||
{
|
||||
$definition->addMethodCall(
|
||||
$reflectionMethod->name,
|
||||
$this->getRequiredInjectMethodArguments($definition, $reflectionMethod)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, Definition>
|
||||
*/
|
||||
private function getRequiredInjectMethodArguments(Definition $definition, \ReflectionMethod $reflectionMethod): array
|
||||
{
|
||||
$channelExtractor = new LogChannelExtractor();
|
||||
$arguments = [];
|
||||
foreach ($reflectionMethod->getParameters() as $parameter) {
|
||||
if (!$parameter->hasType()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $parameter->getType();
|
||||
if (!$type instanceof \ReflectionNamedType || $type->getName() !== LoggerInterface::class) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$channel = $channelExtractor->getParameterChannelName($parameter) ?? $channelExtractor->getClassChannelName($this->container->getReflectionClass($definition->getClass(), false)) ?? $definition->getClass();
|
||||
|
||||
$logger = new Definition(Logger::class);
|
||||
$logger->setFactory([new Reference(LogManager::class), 'getLogger']);
|
||||
$logger->setArguments([$channel]);
|
||||
$logger->setShared(false);
|
||||
|
||||
$name = '$' . $parameter->getName();
|
||||
$arguments[$name] = $logger;
|
||||
}
|
||||
return $arguments;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection\Cache;
|
||||
|
||||
use TYPO3\CMS\Core\Cache\Backend\SimpleFileBackend;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ContainerBackend extends SimpleFileBackend
|
||||
{
|
||||
public function flush(): void
|
||||
{
|
||||
// disable cache flushing
|
||||
}
|
||||
|
||||
public function forceFlush(): void
|
||||
{
|
||||
parent::flush();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Masterminds\HTML5;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
readonly class CommonFactory
|
||||
{
|
||||
public static function createHtml5Parser(): HTML5
|
||||
{
|
||||
return new HTML5([
|
||||
'disable_html_ns' => true,
|
||||
]);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use TYPO3\CMS\Core\Console\CommandRegistry;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ConsoleCommandPass implements CompilerPassInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $tagName;
|
||||
|
||||
public function __construct(string $tagName)
|
||||
{
|
||||
$this->tagName = $tagName;
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
if (!$container->hasDefinition(CommandRegistry::class)) {
|
||||
return;
|
||||
}
|
||||
$commandRegistryDefinition = $container->findDefinition(CommandRegistry::class);
|
||||
|
||||
foreach ($container->findTaggedServiceIds($this->tagName) as $serviceName => $tags) {
|
||||
$commandServiceDefinition = $container->findDefinition($serviceName)->setPublic(true);
|
||||
$commandName = null;
|
||||
$description = null;
|
||||
$hidden = false;
|
||||
$aliases = [];
|
||||
foreach ($tags as $attributes) {
|
||||
$command = $attributes['command'] ?? null;
|
||||
$description = $attributes['description'] ?? $description;
|
||||
$hidden = $attributes['hidden'] ?? $hidden;
|
||||
$schedulable = $attributes['schedulable'] ?? true;
|
||||
$aliasFor = null;
|
||||
if ($command === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$isAlias = $commandName !== null || ($attributes['alias'] ?? false);
|
||||
if (!$isAlias) {
|
||||
$commandName = $attributes['command'];
|
||||
} else {
|
||||
$aliasFor = $commandName;
|
||||
$aliases[] = $attributes['command'];
|
||||
}
|
||||
|
||||
$commandRegistryDefinition->addMethodCall('addLazyCommand', [
|
||||
$command,
|
||||
$serviceName,
|
||||
$description,
|
||||
$hidden,
|
||||
$schedulable,
|
||||
$aliasFor,
|
||||
]);
|
||||
}
|
||||
$commandServiceDefinition->addMethodCall('setName', [$commandName]);
|
||||
if ($description) {
|
||||
$commandServiceDefinition->addMethodCall('setDescription', [$description]);
|
||||
}
|
||||
if ($hidden) {
|
||||
$commandServiceDefinition->addMethodCall('setHidden', [true]);
|
||||
}
|
||||
if ($aliases) {
|
||||
$commandServiceDefinition->addMethodCall('setAliases', [$aliases]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,188 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\Config\FileLocator;
|
||||
use Symfony\Component\DependencyInjection\Compiler\PassConfig;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder as SymfonyContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Dumper\PhpDumper;
|
||||
use Symfony\Component\DependencyInjection\Loader\PhpFileLoader;
|
||||
use Symfony\Component\DependencyInjection\Loader\YamlFileLoader;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\FrontendInterface;
|
||||
use TYPO3\CMS\Core\Cache\Frontend\PhpFrontend;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
use TYPO3\CMS\Core\Package\Cache\PackageDependentCacheIdentifier;
|
||||
use TYPO3\CMS\Core\Package\PackageManager;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ContainerBuilder
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $cacheIdentifiers;
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
protected $defaultServices;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
protected $serviceProviderRegistryServiceName = 'service_provider_registry';
|
||||
|
||||
public function __construct(array $earlyInstances)
|
||||
{
|
||||
$this->defaultServices = $earlyInstances + [ self::class => $this ];
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
public function warmupCache(PackageManager $packageManager, FrontendInterface $cache): void
|
||||
{
|
||||
$registry = new ServiceProviderRegistry($packageManager);
|
||||
$containerBuilder = $this->buildContainer($packageManager, $registry);
|
||||
$cacheIdentifier = $this->getCacheIdentifier($packageManager);
|
||||
$this->dumpContainer($containerBuilder, $cache, $cacheIdentifier);
|
||||
}
|
||||
|
||||
public function createDependencyInjectionContainer(PackageManager $packageManager, FrontendInterface $cache, bool $failsafe = false): ContainerInterface
|
||||
{
|
||||
if (!$cache instanceof PhpFrontend) {
|
||||
throw new \RuntimeException('Cache must be instance of PhpFrontend', 1582022226);
|
||||
}
|
||||
$serviceProviderRegistry = new ServiceProviderRegistry($packageManager, $failsafe);
|
||||
|
||||
if ($failsafe) {
|
||||
return new FailsafeContainer($serviceProviderRegistry, $this->defaultServices);
|
||||
}
|
||||
|
||||
$cacheIdentifier = $this->getCacheIdentifier($packageManager);
|
||||
$containerClassName = $cacheIdentifier;
|
||||
|
||||
$hasCache = $cache->requireOnce($cacheIdentifier) !== false;
|
||||
if (!$hasCache) {
|
||||
$containerBuilder = $this->buildContainer($packageManager, $serviceProviderRegistry);
|
||||
$this->dumpContainer($containerBuilder, $cache, $cacheIdentifier);
|
||||
$cache->requireOnce($cacheIdentifier);
|
||||
}
|
||||
$container = new $containerClassName();
|
||||
|
||||
foreach ($this->defaultServices as $id => $service) {
|
||||
$container->set('_early.' . $id, $service);
|
||||
}
|
||||
|
||||
$container->set($this->serviceProviderRegistryServiceName, $serviceProviderRegistry);
|
||||
|
||||
return $container;
|
||||
}
|
||||
|
||||
protected function buildContainer(PackageManager $packageManager, ServiceProviderRegistry $registry): SymfonyContainerBuilder
|
||||
{
|
||||
$containerBuilder = new SymfonyContainerBuilder();
|
||||
|
||||
$containerBuilder->addCompilerPass(new ResolveClassPass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, 1000);
|
||||
$containerBuilder->addCompilerPass(new ServiceProviderCompilationPass($registry, $this->serviceProviderRegistryServiceName));
|
||||
|
||||
$globalConfigDir = Environment::getConfigPath();
|
||||
// If the config folder is outside of the document root, we allow further services per-project
|
||||
// This is usually the case in composer-based installations
|
||||
if (Environment::getPublicPath() !== Environment::getProjectPath()) {
|
||||
if (file_exists($globalConfigDir . '/system/services.php')) {
|
||||
$phpFileLoader = new PhpFileLoader($containerBuilder, new FileLocator($globalConfigDir . '/system'));
|
||||
$phpFileLoader->load('services.php');
|
||||
}
|
||||
if (file_exists($globalConfigDir . '/system/services.yaml')) {
|
||||
$yamlFileLoader = new YamlFileLoader($containerBuilder, new FileLocator($globalConfigDir . '/system'));
|
||||
$yamlFileLoader->load('services.yaml');
|
||||
}
|
||||
}
|
||||
|
||||
$packages = $packageManager->getActivePackages();
|
||||
foreach ($packages as $package) {
|
||||
$diConfigDir = $package->getPackagePath() . 'Configuration/';
|
||||
if (file_exists($diConfigDir . 'Services.php')) {
|
||||
$phpFileLoader = new PhpFileLoader($containerBuilder, new FileLocator($diConfigDir));
|
||||
$phpFileLoader->load('Services.php');
|
||||
}
|
||||
if (file_exists($diConfigDir . 'Services.yaml')) {
|
||||
$yamlFileLoader = new YamlFileLoader($containerBuilder, new FileLocator($diConfigDir));
|
||||
$yamlFileLoader->load('Services.yaml');
|
||||
}
|
||||
}
|
||||
// Store defaults entries in the DIC container
|
||||
// We need to use a workaround using aliases for synthetic services
|
||||
// But that's common in symfony (same technique is used to provide the
|
||||
// Symfony container interface as well).
|
||||
foreach (array_keys($this->defaultServices) as $id) {
|
||||
$syntheticId = '_early.' . $id;
|
||||
$containerBuilder->register($syntheticId)->setSynthetic(true)->setPublic(true);
|
||||
$containerBuilder->setAlias($id, $syntheticId)->setPublic(true);
|
||||
}
|
||||
|
||||
// Optional service, set by BootService as back reference to the original bootService
|
||||
$containerBuilder->register('_early.boot-service')->setSynthetic(true)->setPublic(true);
|
||||
|
||||
$containerBuilder->compile();
|
||||
|
||||
return $containerBuilder;
|
||||
}
|
||||
|
||||
protected function dumpContainer(SymfonyContainerBuilder $containerBuilder, FrontendInterface $cache, string $cacheIdentifier): string
|
||||
{
|
||||
$containerClassName = $cacheIdentifier;
|
||||
|
||||
$phpDumper = new PhpDumper($containerBuilder);
|
||||
$code = $phpDumper->dump(['class' => $containerClassName]);
|
||||
$code = str_replace('<?php', '', $code);
|
||||
// We need to patch the generated source code to use GeneralUtility::makeInstanceForDi() instead of `new`.
|
||||
// This is ugly, but has to stay, as long as we support SingletonInstances to be created/retrieved
|
||||
// through GeneralUtility::makeInstance.
|
||||
$code = preg_replace('/new ([^\(\s]+)\(/', '\\TYPO3\\CMS\\Core\\Utility\\GeneralUtility::makeInstanceForDi(\\1::class, ', $code);
|
||||
if ($code === null) {
|
||||
throw new \RuntimeException('Could not generate container code', 1599767133);
|
||||
}
|
||||
$code = str_replace(', )', ')', $code);
|
||||
|
||||
$cache->set($cacheIdentifier, $code);
|
||||
|
||||
return $code;
|
||||
}
|
||||
|
||||
/**
|
||||
* @internal may only be used in this class or in functional tests
|
||||
*/
|
||||
public function getCacheIdentifier(PackageManager $packageManager): string
|
||||
{
|
||||
$packageManagerCacheIdentifier = $packageManager->getCacheIdentifier() ?? '';
|
||||
return $this->cacheIdentifiers[$packageManagerCacheIdentifier] ?? $this->createCacheIdentifier($packageManager, $packageManagerCacheIdentifier);
|
||||
}
|
||||
|
||||
protected function createCacheIdentifier(PackageManager $packageManager, string $additionalIdentifier): string
|
||||
{
|
||||
return $this->cacheIdentifiers[$additionalIdentifier] = (new PackageDependentCacheIdentifier($packageManager))
|
||||
->withPrefix('DependencyInjectionContainer')
|
||||
->withAdditionalHashedIdentifier('PHP-' . PHP_MAJOR_VERSION . '-' . PHP_MINOR_VERSION)
|
||||
->toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Psr\Container\ContainerExceptionInterface;
|
||||
use TYPO3\CMS\Core\Exception;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ContainerException extends Exception implements ContainerExceptionInterface {}
|
||||
@@ -0,0 +1,61 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Attribute\AutoconfigureTag;
|
||||
use Symfony\Component\DependencyInjection\EnvVarProcessorInterface;
|
||||
use TYPO3\CMS\Core\Core\Environment;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
#[AutoconfigureTag('container.env_var_processor')]
|
||||
readonly class EnvVarProcessor implements EnvVarProcessorInterface
|
||||
{
|
||||
/**
|
||||
* @param string $prefix The namespace of the variable
|
||||
* @param string $name The name of the variable within the namespace
|
||||
* @param \Closure $getEnv A closure that allows fetching more env vars
|
||||
* @throws \RuntimeException on error
|
||||
*/
|
||||
public function getEnv(string $prefix, string $name, \Closure $getEnv): mixed
|
||||
{
|
||||
if (str_starts_with($name, 'is')) {
|
||||
$callable = [Environment::class, $name];
|
||||
} else {
|
||||
$callable = [Environment::class, 'get' . ucfirst($name)];
|
||||
if (!is_callable($callable)) {
|
||||
$callable = [Environment::class, 'is' . ucfirst($name)];
|
||||
}
|
||||
}
|
||||
if (!is_callable($callable)) {
|
||||
throw new \RuntimeException('Environment ' . $name . ' not available in ' . Environment::class, 1562314987);
|
||||
}
|
||||
return $callable();
|
||||
}
|
||||
|
||||
/**
|
||||
* @return string[] The PHP-types managed by getEnv(), keyed by prefixes
|
||||
*/
|
||||
public static function getProvidedTypes(): array
|
||||
{
|
||||
return [
|
||||
'TYPO3' => 'string|bool',
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class FailsafeContainer implements ContainerInterface
|
||||
{
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $entries = [];
|
||||
|
||||
/**
|
||||
* @var array
|
||||
*/
|
||||
private $factories = [];
|
||||
|
||||
/**
|
||||
* Instantiate the container.
|
||||
*
|
||||
* Objects and parameters can be passed as argument to the constructor.
|
||||
*
|
||||
* @param iterable $providers The service providers to register.
|
||||
* @param array $entries The default parameters or objects.
|
||||
*/
|
||||
public function __construct(iterable $providers = [], array $entries = [])
|
||||
{
|
||||
$this->entries = $entries;
|
||||
|
||||
$factories = [];
|
||||
foreach ($providers as $provider) {
|
||||
/** @var ServiceProviderInterface $provider */
|
||||
$factories = $provider->getFactories() + $factories;
|
||||
foreach ($provider->getExtensions() as $id => $extension) {
|
||||
// Decorate a previously defined extension or if that is not available,
|
||||
// create a lazy lookup to a factory from the list of vanilla factories.
|
||||
// Lazy because we currently can not know whether a factory will only
|
||||
// become available due to a subsequent provider.
|
||||
$innerFactory = $this->factories[$id] ?? static function (ContainerInterface $c) use (&$factories, $id) {
|
||||
return isset($factories[$id]) ? $factories[$id]($c) : null;
|
||||
};
|
||||
|
||||
$this->factories[$id] = static function (ContainerInterface $container) use ($extension, $innerFactory) {
|
||||
$previous = $innerFactory($container);
|
||||
return $extension($container, $previous);
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
// Add factories to the list of factories for services that were not extended.
|
||||
// (i.e those that have not been specified in getExtensions)
|
||||
$this->factories += $factories;
|
||||
}
|
||||
|
||||
public function has(string $id): bool
|
||||
{
|
||||
return array_key_exists($id, $this->entries) || array_key_exists($id, $this->factories);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
private function create(string $id)
|
||||
{
|
||||
$factory = $this->factories[$id] ?? null;
|
||||
|
||||
if ((bool)$factory) {
|
||||
// Remove factory as it is no longer required.
|
||||
// Set factory to false to be able to detect
|
||||
// cyclic dependency loops.
|
||||
$this->factories[$id] = false;
|
||||
|
||||
return $this->entries[$id] = $factory($this);
|
||||
}
|
||||
if (array_key_exists($id, $this->entries)) {
|
||||
// This condition is triggered in the unlikely case that the entry is null
|
||||
// Note: That is because the coalesce operator used in get() can not handle that
|
||||
return $this->entries[$id];
|
||||
}
|
||||
if ($factory === null) {
|
||||
throw new NotFoundException('Container entry "' . $id . '" is not available.', 1519978105);
|
||||
}
|
||||
// if ($factory === false)
|
||||
throw new ContainerException('Container entry "' . $id . '" is part of a cyclic dependency chain.', 1520175002);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return mixed
|
||||
*/
|
||||
public function get(string $id)
|
||||
{
|
||||
return $this->entries[$id] ?? $this->create($id);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use TYPO3\CMS\Core\Resource\Rendering\FileRendererInterface;
|
||||
|
||||
/**
|
||||
* Compiler pass to validate tagged file renderers at container compile time.
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class FileRendererPass implements CompilerPassInterface
|
||||
{
|
||||
public function __construct(private readonly string $tagName) {}
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) {
|
||||
$definition = $container->findDefinition($id);
|
||||
if ($definition->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
$className = $definition->getClass() ?? $id;
|
||||
if (!is_a($className, FileRendererInterface::class, true)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Service "' . $id . '" is tagged as "' . $this->tagName . '", but its class "' . $className . '" does not implement ' . FileRendererInterface::class . '.',
|
||||
1784818672
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,173 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||
use TYPO3\CMS\Core\EventDispatcher\ListenerProvider;
|
||||
use TYPO3\CMS\Core\Service\DependencyOrderingService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class ListenerProviderPass implements CompilerPassInterface
|
||||
{
|
||||
private ContainerBuilder $container;
|
||||
|
||||
private readonly DependencyOrderingService $orderer;
|
||||
|
||||
public function __construct(private readonly string $tagName)
|
||||
{
|
||||
$this->orderer = new DependencyOrderingService();
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$this->container = $container;
|
||||
|
||||
if (!$container->hasDefinition(ListenerProvider::class)) {
|
||||
// If there's no listener provider registered to begin with, don't bother registering listeners with it.
|
||||
return;
|
||||
}
|
||||
$listenerProviderDefinition = $container->findDefinition(ListenerProvider::class);
|
||||
|
||||
$unorderedEventListeners = $this->collectListeners($container);
|
||||
|
||||
foreach ($unorderedEventListeners as $eventName => $listeners) {
|
||||
// Configure ListenerProvider factory to include these listeners
|
||||
foreach ($this->orderer->orderByDependencies($listeners) as $listenerIdentifier => $listener) {
|
||||
$listenerProviderDefinition->addMethodCall('addListener', [
|
||||
$eventName,
|
||||
$listener['service'],
|
||||
$listener['method'],
|
||||
$listenerIdentifier,
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all listeners from the container.
|
||||
*/
|
||||
private function collectListeners(ContainerBuilder $container): array
|
||||
{
|
||||
$unorderedEventListeners = [];
|
||||
foreach ($container->findTaggedServiceIds($this->tagName) as $serviceName => $tags) {
|
||||
$service = $container->findDefinition($serviceName);
|
||||
$service->setPublic(true);
|
||||
foreach ($tags as $attributes) {
|
||||
$eventIdentifiers = $attributes['event'] ?? $this->getParameterType($serviceName, $service, $attributes['method'] ?? '__invoke');
|
||||
if ($eventIdentifiers === null || $eventIdentifiers === '' || $eventIdentifiers === []) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Service tag "event.listener" requires an event attribute to be defined or the listener method must declare a parameter type. Missing in: ' . $serviceName,
|
||||
1563217364
|
||||
);
|
||||
}
|
||||
if (is_string($eventIdentifiers)) {
|
||||
$eventIdentifiers = [$eventIdentifiers];
|
||||
}
|
||||
foreach ($eventIdentifiers as $eventIdentifier) {
|
||||
$listenerIdentifier = $attributes['identifier'] ?? $serviceName;
|
||||
$unorderedEventListeners[$eventIdentifier][$listenerIdentifier] = [
|
||||
'service' => $serviceName,
|
||||
'method' => $attributes['method'] ?? null,
|
||||
'before' => GeneralUtility::trimExplode(',', $attributes['before'] ?? '', true),
|
||||
'after' => GeneralUtility::trimExplode(',', $attributes['after'] ?? '', true),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $unorderedEventListeners;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the class type(s) of the first argument of a given method.
|
||||
* Supporting union types, this method returns the class type(s) as list.
|
||||
*
|
||||
* @return string[]|null A list of class types or NULL on failure
|
||||
*/
|
||||
private function getParameterType(string $serviceName, Definition $definition, string $method = '__invoke'): ?array
|
||||
{
|
||||
// A Reflection exception should never actually get thrown here, but linters want a try-catch just in case.
|
||||
try {
|
||||
if (!$definition->isAutowired()) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf('Service "%s" has event listeners defined but does not declare an event to listen to and is not configured to autowire it from the listener method. Set autowire: true to enable auto-detection of the listener event.', $serviceName),
|
||||
1623881314,
|
||||
);
|
||||
}
|
||||
$params = $this->getReflectionMethod($serviceName, $definition, $method)->getParameters();
|
||||
$rType = count($params) ? $params[0]->getType() : null;
|
||||
if ($rType instanceof \ReflectionNamedType) {
|
||||
return [$rType->getName()];
|
||||
}
|
||||
if ($rType instanceof \ReflectionUnionType) {
|
||||
$types = [];
|
||||
foreach ($rType->getTypes() as $type) {
|
||||
if ($type instanceof \ReflectionNamedType) {
|
||||
$types[] = $type->getName();
|
||||
}
|
||||
}
|
||||
if ($types === []) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf('Service "%s" registers method "%s" as an event listener, but does not specify an event type and the method\'s first parameter does not contain a valid class type. Declare valid class types for the method parameter or specify the event classes explicitly', $serviceName, $method),
|
||||
1688646662,
|
||||
);
|
||||
}
|
||||
return $types;
|
||||
}
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf('Service "%s" registers method "%s" as an event listener, but does not specify an event type and the method does not type a parameter. Declare a class type for the method parameter or specify an event class explicitly', $serviceName, $method),
|
||||
1623881315,
|
||||
);
|
||||
} catch (\ReflectionException $e) {
|
||||
// The collectListeners() method will convert this to an exception.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RuntimeException
|
||||
*
|
||||
* This method borrowed very closely from Symfony's AbstractRecurisvePass.
|
||||
*/
|
||||
private function getReflectionMethod(string $serviceName, Definition $definition, string $method): \ReflectionFunctionAbstract
|
||||
{
|
||||
if (!$class = $definition->getClass()) {
|
||||
throw new RuntimeException(sprintf('Invalid service "%s": the class is not set.', $serviceName), 1623881310);
|
||||
}
|
||||
|
||||
if (!$r = $this->container->getReflectionClass($class)) {
|
||||
throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $serviceName, $class), 1623881311);
|
||||
}
|
||||
|
||||
if (!$r->hasMethod($method)) {
|
||||
throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" does not exist.', $serviceName, $class !== $serviceName ? $class . '::' . $method : $method), 1623881312);
|
||||
}
|
||||
|
||||
$r = $r->getMethod($method);
|
||||
if (!$r->isPublic()) {
|
||||
throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" must be public.', $serviceName, $class !== $serviceName ? $class . '::' . $method : $method), 1623881313);
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,47 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use TYPO3\CMS\Core\Log\Channel;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final readonly class LogChannelExtractor
|
||||
{
|
||||
public function getClassChannelName(\ReflectionClass $class): ?string
|
||||
{
|
||||
$attributes = $class->getAttributes(Channel::class, \ReflectionAttribute::IS_INSTANCEOF);
|
||||
if ($attributes !== []) {
|
||||
return $attributes[0]->newInstance()->name;
|
||||
}
|
||||
if ($class->getParentClass() !== false) {
|
||||
return $this->getClassChannelName($class->getParentClass());
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public function getParameterChannelName(\ReflectionParameter $parameter): ?string
|
||||
{
|
||||
$attributes = $parameter->getAttributes(Channel::class, \ReflectionAttribute::IS_INSTANCEOF);
|
||||
if ($attributes !== []) {
|
||||
return $attributes[0]->newInstance()->name;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use TYPO3\CMS\Core\Log\Logger;
|
||||
use TYPO3\CMS\Core\Log\LogManager;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class LoggerAwarePass implements CompilerPassInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $tagName;
|
||||
|
||||
public function __construct(string $tagName)
|
||||
{
|
||||
$this->tagName = $tagName;
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$channelExtractor = new LogChannelExtractor();
|
||||
foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) {
|
||||
$definition = $container->findDefinition($id);
|
||||
if (!$definition->isAutowired() || $definition->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$channel = $id;
|
||||
if ($definition->getClass()) {
|
||||
$reflectionClass = $container->getReflectionClass($definition->getClass(), false);
|
||||
if ($reflectionClass) {
|
||||
$channel = $channelExtractor->getClassChannelName($reflectionClass) ?? $definition->getClass();
|
||||
}
|
||||
}
|
||||
|
||||
$logger = new Definition(Logger::class);
|
||||
$logger->setFactory([new Reference(LogManager::class), 'getLogger']);
|
||||
$logger->setArguments([$channel]);
|
||||
$logger->setShared(false);
|
||||
|
||||
$definition->addMethodCall('setLogger', [$logger]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,106 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Psr\Log\LoggerInterface;
|
||||
use Symfony\Component\DependencyInjection\Compiler\AbstractRecursivePass;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use TYPO3\CMS\Core\Log\Channel;
|
||||
use TYPO3\CMS\Core\Log\Logger;
|
||||
use TYPO3\CMS\Core\Log\LogManager;
|
||||
|
||||
/**
|
||||
* Looks for constructor arguments that request LoggerInterface and registers a lookup to the LogManager
|
||||
*/
|
||||
class LoggerInterfacePass extends AbstractRecursivePass
|
||||
{
|
||||
/**
|
||||
* @param mixed $value
|
||||
* @param bool $isRoot
|
||||
*/
|
||||
protected function processValue($value, $isRoot = false): mixed
|
||||
{
|
||||
$value = parent::processValue($value, $isRoot);
|
||||
|
||||
if (!$value instanceof Definition || !$value->isAutowired() || $value->isAbstract() || !$value->getClass()) {
|
||||
return $value;
|
||||
}
|
||||
if (!$reflectionClass = $this->container->getReflectionClass($value->getClass(), false)) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$constructor = $reflectionClass->getConstructor();
|
||||
if ($constructor === null) {
|
||||
return $value;
|
||||
}
|
||||
|
||||
$arguments = $value->getArguments();
|
||||
foreach ($reflectionClass->getConstructor()->getParameters() as $index => $parameter) {
|
||||
$name = '$' . $parameter->getName();
|
||||
|
||||
if (isset($arguments[$name]) || isset($arguments[$index])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (!$parameter->hasType()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$type = $parameter->getType();
|
||||
if (!($type instanceof \ReflectionNamedType && $type->getName() === LoggerInterface::class)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$channel = $this->getParameterChannelName($parameter) ?? $this->getClassChannelName($reflectionClass) ?? $value->getClass();
|
||||
|
||||
$logger = new Definition(Logger::class);
|
||||
$logger->setFactory([new Reference(LogManager::class), 'getLogger']);
|
||||
$logger->setArguments([$channel]);
|
||||
$logger->setShared(false);
|
||||
|
||||
$value->setArgument($name, $logger);
|
||||
}
|
||||
|
||||
return $value;
|
||||
}
|
||||
|
||||
protected function getParameterChannelName(\ReflectionParameter $parameter): ?string
|
||||
{
|
||||
$attributes = $parameter->getAttributes(Channel::class, \ReflectionAttribute::IS_INSTANCEOF);
|
||||
if ($attributes !== []) {
|
||||
return $attributes[0]->newInstance()->name;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
protected function getClassChannelName(\ReflectionClass $class): ?string
|
||||
{
|
||||
$attributes = $class->getAttributes(Channel::class, \ReflectionAttribute::IS_INSTANCEOF);
|
||||
if ($attributes !== []) {
|
||||
return $attributes[0]->newInstance()->name;
|
||||
}
|
||||
|
||||
if ($class->getParentClass() !== false) {
|
||||
return $this->getClassChannelName($class->getParentClass());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,170 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Exception\RuntimeException;
|
||||
use TYPO3\CMS\Core\Messenger\HandlersLocatorFactory;
|
||||
use TYPO3\CMS\Core\Service\DependencyOrderingService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class MessageHandlerPass implements CompilerPassInterface
|
||||
{
|
||||
private ContainerBuilder $container;
|
||||
|
||||
private readonly DependencyOrderingService $orderer;
|
||||
|
||||
public function __construct(private readonly string $tagName)
|
||||
{
|
||||
$this->orderer = new DependencyOrderingService();
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$this->container = $container;
|
||||
|
||||
if (!$container->hasDefinition(HandlersLocatorFactory::class)) {
|
||||
// If there's no listener provider registered to begin with, don't bother registering listeners with it.
|
||||
return;
|
||||
}
|
||||
|
||||
$handlersLocatorFactory = $container->findDefinition(HandlersLocatorFactory::class);
|
||||
|
||||
foreach ($this->collectHandlers($container) as $message => $handlers) {
|
||||
foreach ($this->orderer->orderByDependencies($handlers) as $handler) {
|
||||
$handlersLocatorFactory->addMethodCall('addHandler', [
|
||||
$message,
|
||||
$handler['service'],
|
||||
$handler['method'] ?? '__invoke',
|
||||
]);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all handlers from the container.
|
||||
*/
|
||||
private function collectHandlers(ContainerBuilder $container): array
|
||||
{
|
||||
$unorderedHandlers = [];
|
||||
foreach ($container->findTaggedServiceIds($this->tagName) as $serviceName => $tags) {
|
||||
$service = $container->findDefinition($serviceName);
|
||||
$service->setPublic(true);
|
||||
foreach ($tags as $attributes) {
|
||||
$messageHandlers = $attributes['message'] ?? $this->getParameterType($serviceName, $service, $attributes['method'] ?? '__invoke');
|
||||
if ($messageHandlers === null || $messageHandlers === '' || $messageHandlers === []) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Service tag "messenger.message_handler" requires a message attribute to be defined or the method must declare a parameter type. Missing in: ' . $serviceName,
|
||||
1606732015
|
||||
);
|
||||
}
|
||||
if (is_string($messageHandlers)) {
|
||||
$messageHandlers = [$messageHandlers];
|
||||
}
|
||||
foreach ($messageHandlers as $messageHandler) {
|
||||
$messageIdentifier = sprintf('%s->%s', $serviceName, $attributes['method'] ?? '__invoke');
|
||||
$unorderedHandlers[$messageHandler][$messageIdentifier] = [
|
||||
'service' => $serviceName,
|
||||
'method' => $attributes['method'] ?? null,
|
||||
'before' => GeneralUtility::trimExplode(',', $attributes['before'] ?? '', true),
|
||||
'after' => GeneralUtility::trimExplode(',', $attributes['after'] ?? '', true),
|
||||
];
|
||||
}
|
||||
}
|
||||
}
|
||||
return $unorderedHandlers;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derives the class type(s) of the first argument of a given method.
|
||||
* Supporting union types, this method returns the class type(s) as list.
|
||||
*
|
||||
* @return string[]|null A list of class types or NULL on failure
|
||||
*/
|
||||
private function getParameterType(string $serviceName, Definition $definition, string $method = '__invoke'): ?array
|
||||
{
|
||||
// A Reflection exception should never actually get thrown here, but linters want a try-catch just in case.
|
||||
try {
|
||||
if (!$definition->isAutowired()) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf('Service "%s" has message handlers defined but does not declare a message to handle to and is not configured to autowire it from the handle method. Set autowire: true to enable auto-detection of the handled message.', $serviceName),
|
||||
1606732016,
|
||||
);
|
||||
}
|
||||
$params = $this->getReflectionMethod($serviceName, $definition, $method)->getParameters();
|
||||
$rType = count($params) ? $params[0]->getType() : null;
|
||||
if ($rType instanceof \ReflectionNamedType) {
|
||||
return [$rType->getName()];
|
||||
}
|
||||
if ($rType instanceof \ReflectionUnionType) {
|
||||
$types = [];
|
||||
foreach ($rType->getTypes() as $type) {
|
||||
if ($type instanceof \ReflectionNamedType) {
|
||||
$types[] = $type->getName();
|
||||
}
|
||||
}
|
||||
if ($types === []) {
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf('Service "%s" registers method "%s" as a message handler, but does not specify a message type and the method\'s first parameter does not contain a valid class type. Declare a valid class type for the method parameter or specify a message class explicitly', $serviceName, $method),
|
||||
1606732017,
|
||||
);
|
||||
}
|
||||
return $types;
|
||||
}
|
||||
throw new \InvalidArgumentException(
|
||||
sprintf('Service "%s" registers method "%s" as a message handler, but does not specify a message type and the method does not type a parameter. Declare a class type for the method parameter or specify a event message explicitly', $serviceName, $method),
|
||||
1606732022,
|
||||
);
|
||||
} catch (\ReflectionException $e) {
|
||||
// The collectHandlers() method will convert this to an exception.
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @throws RuntimeException
|
||||
* This method borrowed very closely from Symfony's AbstractRecursivePass (and the ListenerProviderPass).
|
||||
* @see \TYPO3\CMS\Core\DependencyInjection\ListenerProviderPass::getReflectionMethod()
|
||||
*/
|
||||
private function getReflectionMethod(string $serviceName, Definition $definition, string $method): \ReflectionFunctionAbstract
|
||||
{
|
||||
if (!$class = $definition->getClass()) {
|
||||
throw new RuntimeException(sprintf('Invalid service "%s": the class is not set.', $serviceName), 1606732018);
|
||||
}
|
||||
|
||||
if (!$r = $this->container->getReflectionClass($class)) {
|
||||
throw new RuntimeException(sprintf('Invalid service "%s": class "%s" does not exist.', $serviceName, $class), 1606732019);
|
||||
}
|
||||
|
||||
if (!$r->hasMethod($method)) {
|
||||
throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" does not exist.', $serviceName, $class !== $serviceName ? $class . '::' . $method : $method), 1606732020);
|
||||
}
|
||||
|
||||
$r = $r->getMethod($method);
|
||||
if (!$r->isPublic()) {
|
||||
throw new RuntimeException(sprintf('Invalid service "%s": method "%s()" must be public.', $serviceName, $class !== $serviceName ? $class . '::' . $method : $method), 1606732021);
|
||||
}
|
||||
|
||||
return $r;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use TYPO3\CMS\Core\Messenger\BusFactory;
|
||||
use TYPO3\CMS\Core\Service\DependencyOrderingService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class MessengerMiddlewarePass implements CompilerPassInterface
|
||||
{
|
||||
private string $tagName;
|
||||
|
||||
private DependencyOrderingService $orderer;
|
||||
|
||||
public function __construct(string $tagName)
|
||||
{
|
||||
$this->tagName = $tagName;
|
||||
$this->orderer = new DependencyOrderingService();
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$busFactory = $container->findDefinition(BusFactory::class);
|
||||
$groupedMiddlewares = $this->collectMiddlewares($container);
|
||||
$middlewares = [];
|
||||
foreach ($groupedMiddlewares as $bus => $unorderedMiddlewares) {
|
||||
$middlewares[$bus] = [];
|
||||
foreach ($this->orderer->orderByDependencies($unorderedMiddlewares) as $middleware) {
|
||||
$middlewares[$bus][] = new Reference($middleware['service']);
|
||||
}
|
||||
}
|
||||
$busFactory->setArgument('$middlewares', array_map(
|
||||
static fn(array $busMiddlewares): IteratorArgument => new IteratorArgument($busMiddlewares),
|
||||
$middlewares
|
||||
));
|
||||
}
|
||||
|
||||
/**
|
||||
* Collects all messenger middlewares from the container and prepares them for ordering
|
||||
*/
|
||||
private function collectMiddlewares(ContainerBuilder $container): array
|
||||
{
|
||||
$unorderedMiddlewares = [];
|
||||
foreach ($container->findTaggedServiceIds($this->tagName) as $serviceName => $tags) {
|
||||
foreach ($tags as $attributes) {
|
||||
$bus = $attributes['bus'] ?? 'default';
|
||||
$unorderedMiddlewares[$bus][$serviceName] = [
|
||||
'service' => $serviceName,
|
||||
'before' => GeneralUtility::trimExplode(',', $attributes['before'] ?? '', true),
|
||||
'after' => GeneralUtility::trimExplode(',', $attributes['after'] ?? '', true),
|
||||
];
|
||||
}
|
||||
}
|
||||
return $unorderedMiddlewares;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,89 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderManifest;
|
||||
use TYPO3\CMS\Core\Authentication\Mfa\MfaProviderRegistry;
|
||||
use TYPO3\CMS\Core\Service\DependencyOrderingService;
|
||||
use TYPO3\CMS\Core\Utility\GeneralUtility;
|
||||
|
||||
/**
|
||||
* Compiler pass to register tagged MFA providers
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class MfaProviderPass implements CompilerPassInterface
|
||||
{
|
||||
private string $tagName;
|
||||
|
||||
public function __construct(string $tagName)
|
||||
{
|
||||
$this->tagName = $tagName;
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$mfaProviderRegistryDefinition = $container->findDefinition(MfaProviderRegistry::class);
|
||||
$providers = [];
|
||||
|
||||
foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) {
|
||||
$definition = $container->findDefinition($id);
|
||||
if (!$definition->isAutoconfigured() || $definition->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$definition->setPublic(true);
|
||||
|
||||
foreach ($tags as $attributes) {
|
||||
$identifier = $attributes['identifier'] ?? $id;
|
||||
$providers[$identifier] = [
|
||||
'title' => $attributes['title'] ?? '',
|
||||
'description' => $attributes['description'] ?? '',
|
||||
'setupInstructions' => $attributes['setupInstructions'] ?? '',
|
||||
'iconIdentifier' => $attributes['icon'] ?? '',
|
||||
'isDefaultProviderAllowed' => (bool)($attributes['defaultProviderAllowed'] ?? true),
|
||||
'before' => GeneralUtility::trimExplode(',', $attributes['before'] ?? '', true),
|
||||
'after' => GeneralUtility::trimExplode(',', $attributes['after'] ?? '', true),
|
||||
'serviceName' => $id,
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
foreach ((new DependencyOrderingService())->orderByDependencies($providers) as $identifier => $properties) {
|
||||
$manifest = new Definition(MfaProviderManifest::class);
|
||||
$manifest->setArguments([
|
||||
$identifier,
|
||||
$properties['title'],
|
||||
$properties['description'],
|
||||
$properties['setupInstructions'],
|
||||
$properties['iconIdentifier'],
|
||||
$properties['isDefaultProviderAllowed'],
|
||||
$properties['serviceName'],
|
||||
new Reference(ContainerInterface::class),
|
||||
]);
|
||||
$manifest->setShared(false);
|
||||
|
||||
$mfaProviderRegistryDefinition->addMethodCall('registerProvider', [$manifest]);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Psr\Container\NotFoundExceptionInterface;
|
||||
use TYPO3\CMS\Core\Exception;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class NotFoundException extends Exception implements NotFoundExceptionInterface {}
|
||||
@@ -0,0 +1,53 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class PublicServicePass implements CompilerPassInterface
|
||||
{
|
||||
private string $tagName;
|
||||
|
||||
private bool $stateful;
|
||||
|
||||
public function __construct(string $tagName, bool $stateful = false)
|
||||
{
|
||||
$this->tagName = $tagName;
|
||||
$this->stateful = $stateful;
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) {
|
||||
$definition = $container->findDefinition($id);
|
||||
if (!$definition->isAutoconfigured() || $definition->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$definition->setPublic(true);
|
||||
|
||||
if ($this->stateful) {
|
||||
$definition->setShared(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\ChildDefinition;
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
|
||||
|
||||
/**
|
||||
* This is an overwrite for the upstream symfony/dependency-injection
|
||||
* class resolution that introduced an incompatibility with existing
|
||||
* TYPO3 services.
|
||||
*
|
||||
* This is a copy of the compiler pass from symfony/dependency-injection:v7.3.7, see:
|
||||
* https://github.com/symfony/symfony/blob/v7.3.7/src/Symfony/Component/DependencyInjection/Compiler/ResolveClassPass.php
|
||||
*/
|
||||
class ResolveClassPass implements CompilerPassInterface
|
||||
{
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
foreach ($container->getDefinitions() as $id => $definition) {
|
||||
if ($definition->isSynthetic() || $definition->getClass() !== null) {
|
||||
continue;
|
||||
}
|
||||
if (preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+(?:\\\\[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*+)++$/', $id)) {
|
||||
if ($definition instanceof ChildDefinition && !class_exists($id)) {
|
||||
throw new InvalidArgumentException(sprintf('Service definition "%s" has a parent but no class, and its name looks like an FQCN. Either the class is missing or you want to inherit it from the parent service. To resolve this ambiguity, please rename this service to a non-FQCN (e.g. using dots), or create the missing class.', $id), 1764317265);
|
||||
}
|
||||
$definition->setClass($id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,255 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class ServiceProviderCompilationPass implements CompilerPassInterface
|
||||
{
|
||||
/**
|
||||
* @var ServiceProviderRegistry
|
||||
*/
|
||||
private $registry;
|
||||
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $registryServiceName;
|
||||
|
||||
public function __construct(ServiceProviderRegistry $registry, string $registryServiceName = 'service_provider_registry')
|
||||
{
|
||||
$this->registry = $registry;
|
||||
$this->registryServiceName = $registryServiceName;
|
||||
}
|
||||
|
||||
/**
|
||||
* You can modify the container here before it is dumped to PHP code.
|
||||
*
|
||||
* @param ContainerBuilder $container
|
||||
*/
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
// Now, let's store the registry in the container (an empty version of it... it has to be added dynamically at runtime):
|
||||
$this->registerRegistry($container);
|
||||
|
||||
foreach ($this->registry as $serviceProviderKey => $serviceProvider) {
|
||||
$this->registerFactories($container, $serviceProviderKey);
|
||||
}
|
||||
|
||||
foreach ($this->registry as $serviceProviderKey => $serviceProvider) {
|
||||
$this->registerExtensions($container, $serviceProviderKey);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContainerBuilder $container
|
||||
*/
|
||||
private function registerRegistry(ContainerBuilder $container): void
|
||||
{
|
||||
$definition = new Definition(ServiceProviderRegistry::class);
|
||||
$definition->setSynthetic(true);
|
||||
$definition->setPublic(true);
|
||||
|
||||
$container->setDefinition($this->registryServiceName, $definition);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContainerBuilder $container
|
||||
*/
|
||||
private function registerFactories(ContainerBuilder $container, string $serviceProviderKey): void
|
||||
{
|
||||
$serviceFactories = $this->registry->getFactories($serviceProviderKey);
|
||||
|
||||
foreach ($serviceFactories as $serviceName => $callable) {
|
||||
$this->registerService($container, $serviceName, $serviceProviderKey, $callable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContainerBuilder $container
|
||||
*/
|
||||
private function registerExtensions(ContainerBuilder $container, string $serviceProviderKey): void
|
||||
{
|
||||
$serviceExtensions = $this->registry->getExtensions($serviceProviderKey);
|
||||
|
||||
foreach ($serviceExtensions as $serviceName => $callable) {
|
||||
$this->extendService($container, $serviceName, $serviceProviderKey, $callable);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContainerBuilder $container
|
||||
*/
|
||||
private function registerService(
|
||||
ContainerBuilder $container,
|
||||
string $serviceName,
|
||||
string $serviceProviderKey,
|
||||
callable $callable
|
||||
): void {
|
||||
if (!$container->hasDefinition($serviceName)) {
|
||||
// Create a new definition
|
||||
$factoryDefinition = new Definition();
|
||||
$container->setDefinition($serviceName, $factoryDefinition);
|
||||
} else {
|
||||
// Merge into an existing definition to keep possible addMethodCall/properties configurations
|
||||
// (which act like a service extension)
|
||||
// Retrieve the existing factory and overwrite it.
|
||||
$factoryDefinition = $container->getDefinition($serviceName);
|
||||
if ($factoryDefinition->isAutowired()) {
|
||||
$factoryDefinition->setAutowired(false);
|
||||
}
|
||||
}
|
||||
|
||||
$className = $this->getReturnType($this->getReflection($callable), $serviceName) ?? 'object';
|
||||
$factoryDefinition->setClass($className);
|
||||
$factoryDefinition->setPublic(true);
|
||||
|
||||
$staticallyCallable = $this->getStaticallyCallable($callable);
|
||||
if ($staticallyCallable !== null) {
|
||||
$factoryDefinition->setFactory($staticallyCallable);
|
||||
$factoryDefinition->setArguments([
|
||||
new Reference('service_container'),
|
||||
]);
|
||||
} else {
|
||||
$factoryDefinition->setFactory([ new Reference($this->registryServiceName), 'createService' ]);
|
||||
$factoryDefinition->setArguments([
|
||||
$serviceProviderKey,
|
||||
$serviceName,
|
||||
new Reference('service_container'),
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContainerBuilder $container
|
||||
*/
|
||||
private function extendService(ContainerBuilder $container, string $serviceName, string $serviceProviderKey, callable $callable): void
|
||||
{
|
||||
$finalServiceName = $serviceName;
|
||||
$innerName = null;
|
||||
|
||||
$reflection = $this->getReflection($callable);
|
||||
$previousClass = $container->hasDefinition($serviceName) ? $container->getDefinition($serviceName)->getClass() : null;
|
||||
$className = $this->getReturnType($reflection, $serviceName) ?? $previousClass ?? 'object';
|
||||
|
||||
$factoryDefinition = new Definition($className);
|
||||
$factoryDefinition->setClass($className);
|
||||
$factoryDefinition->setPublic(true);
|
||||
|
||||
if ($container->has($serviceName)) {
|
||||
[$finalServiceName, $previousServiceName] = $this->getDecoratedServiceName($container, $serviceName);
|
||||
$innerName = $finalServiceName . '.inner';
|
||||
|
||||
$factoryDefinition->setDecoratedService($previousServiceName, $innerName);
|
||||
} elseif ($reflection->getNumberOfRequiredParameters() > 1) {
|
||||
throw new \Exception('A registered extension for the service "' . $serviceName . '" requires the service to be available, which is missing.', 1550092654);
|
||||
}
|
||||
|
||||
$staticallyCallable = $this->getStaticallyCallable($callable);
|
||||
if ($staticallyCallable !== null) {
|
||||
$factoryDefinition->setFactory($staticallyCallable);
|
||||
$factoryDefinition->setArguments([
|
||||
new Reference('service_container'),
|
||||
]);
|
||||
} else {
|
||||
$factoryDefinition->setFactory([ new Reference($this->registryServiceName), 'extendService' ]);
|
||||
$factoryDefinition->setArguments([
|
||||
$serviceProviderKey,
|
||||
$serviceName,
|
||||
new Reference('service_container'),
|
||||
]);
|
||||
}
|
||||
|
||||
if ($innerName !== null) {
|
||||
$factoryDefinition->addArgument(new Reference($innerName));
|
||||
}
|
||||
|
||||
$container->setDefinition($finalServiceName, $factoryDefinition);
|
||||
}
|
||||
|
||||
private function getStaticallyCallable(callable $callable): ?callable
|
||||
{
|
||||
if (is_string($callable)) {
|
||||
return $callable;
|
||||
}
|
||||
if (is_array($callable) && is_string($callable[0])) {
|
||||
return $callable;
|
||||
}
|
||||
|
||||
if ($callable instanceof \Closure) {
|
||||
$reflection = new \ReflectionFunction($callable);
|
||||
$reflectionScope = $reflection->getClosureScopeClass();
|
||||
$usedVariables = count($reflection->getClosureUsedVariables());
|
||||
|
||||
if ($reflection->isStatic() && !$reflection->isAnonymous() && $reflectionScope !== null && $usedVariables === 0) {
|
||||
return [
|
||||
$reflectionScope->getName(),
|
||||
$reflection->getName(),
|
||||
];
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getReturnType(\ReflectionFunctionAbstract $reflection, string $serviceName): ?string
|
||||
{
|
||||
if ($reflection->getReturnType() instanceof \ReflectionNamedType) {
|
||||
return $reflection->getReturnType()->getName();
|
||||
}
|
||||
|
||||
if (class_exists($serviceName, true) || interface_exists($serviceName, true)) {
|
||||
return $serviceName;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private function getReflection(callable $callable): \ReflectionFunctionAbstract
|
||||
{
|
||||
if (is_array($callable) && count($callable) === 2) {
|
||||
return new \ReflectionMethod($callable[0], $callable[1]);
|
||||
}
|
||||
if (is_object($callable) && !$callable instanceof \Closure) {
|
||||
return new \ReflectionMethod($callable, '__invoke');
|
||||
}
|
||||
|
||||
return new \ReflectionFunction($callable);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param ContainerBuilder $container
|
||||
*/
|
||||
private function getDecoratedServiceName(ContainerBuilder $container, string $serviceName): array
|
||||
{
|
||||
$counter = 1;
|
||||
while ($container->has($serviceName . '_decorated_' . $counter)) {
|
||||
$counter++;
|
||||
}
|
||||
return [
|
||||
$serviceName . '_decorated_' . $counter,
|
||||
$counter === 1 ? $serviceName : $serviceName . '_decorated_' . ($counter - 1),
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
/**
|
||||
* A service provider provides entries to a container. Inspired by:
|
||||
* https://github.com/container-interop/service-provider/blob/v0.4.0/src/ServiceProviderInterface.php
|
||||
* @internal
|
||||
*/
|
||||
interface ServiceProviderInterface
|
||||
{
|
||||
/**
|
||||
* Returns a list of all container entries registered by this service provider.
|
||||
*
|
||||
* - the key is the entry name
|
||||
* - the value is a callable that will return the entry, aka the **factory**
|
||||
*
|
||||
* Factories have the following signature:
|
||||
* function(\Psr\Container\ContainerInterface $container)
|
||||
*
|
||||
* @return callable[]
|
||||
*/
|
||||
public function getFactories();
|
||||
|
||||
/**
|
||||
* Returns a list of all container entries extended by this service provider.
|
||||
*
|
||||
* - the key is the entry name
|
||||
* - the value is a callable that will return the modified entry
|
||||
*
|
||||
* Callables have the following signature:
|
||||
* function(Psr\Container\ContainerInterface $container, $previous)
|
||||
* or function(Psr\Container\ContainerInterface $container, $previous = null)
|
||||
*
|
||||
* About factories parameters:
|
||||
*
|
||||
* - the container (instance of `Psr\Container\ContainerInterface`)
|
||||
* - the entry to be extended. If the entry to be extended does not exist and the parameter is nullable, `null` will be passed.
|
||||
*
|
||||
* @return callable[]
|
||||
*/
|
||||
public function getExtensions();
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Psr\Container\ContainerInterface;
|
||||
use TYPO3\CMS\Core\Package\Package;
|
||||
use TYPO3\CMS\Core\Package\PackageManager;
|
||||
|
||||
/**
|
||||
* A class that holds the list of service providers of a project.
|
||||
* This class is designed so that service provider do not need to be instantiated each time the registry is filled.
|
||||
* They can be lazily instantiated if needed.
|
||||
* @internal
|
||||
*/
|
||||
class ServiceProviderRegistry implements \IteratorAggregate
|
||||
{
|
||||
/**
|
||||
* @var PackageManager
|
||||
*/
|
||||
private $packageManager;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
private $failsafe;
|
||||
|
||||
/**
|
||||
* The array with constructed values.
|
||||
*
|
||||
* @var array An array<packageKey, ServiceProviderInterface>
|
||||
*/
|
||||
private $instances;
|
||||
|
||||
/**
|
||||
* An array of service factories (the result of the call to 'getFactories'),
|
||||
* indexed by service provider.
|
||||
*
|
||||
* @var array An array<packageKey, array<servicename, callable>>
|
||||
*/
|
||||
private $serviceFactories = [];
|
||||
|
||||
/**
|
||||
* An array of service extensions (the result of the call to 'getExtensions'),
|
||||
* indexed by service provider.
|
||||
*
|
||||
* @var array An array<packageKey, array<servicename, callable>>
|
||||
*/
|
||||
private $serviceExtensions = [];
|
||||
|
||||
/**
|
||||
* Initializes the registry from a list of service providers.
|
||||
* This list of service providers can be passed as ServiceProvider instances, class name string,
|
||||
* or an array of ['class name', [constructor params...]].
|
||||
*/
|
||||
public function __construct(PackageManager $packageManager, bool $failsafe = false)
|
||||
{
|
||||
$this->packageManager = $packageManager;
|
||||
$this->failsafe = $failsafe;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an id exists.
|
||||
*
|
||||
* @param string $packageKey Key of the service provider in the registry
|
||||
* @return bool true on success or false on failure.
|
||||
*/
|
||||
public function has(string $packageKey): bool
|
||||
{
|
||||
if (isset($this->instances[$packageKey])) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if ($this->packageManager->isPackageActive($packageKey)) {
|
||||
if ($this->failsafe && $this->packageManager->getPackage($packageKey)->isPartOfMinimalUsableSystem() === false) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns service provider by id.
|
||||
*
|
||||
* @param string $packageKey Key of the service provider in the registry
|
||||
*/
|
||||
public function get(string $packageKey): ServiceProviderInterface
|
||||
{
|
||||
return $this->instances[$packageKey] ?? $this->create($packageKey);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns service provider by id.
|
||||
*
|
||||
* @param string $packageKey Key of the service provider in the registry
|
||||
* @param Package|null $package
|
||||
*/
|
||||
private function create(string $packageKey, ?Package $package = null): ServiceProviderInterface
|
||||
{
|
||||
if ($package === null) {
|
||||
if (!$this->packageManager->isPackageActive($packageKey)) {
|
||||
throw new \InvalidArgumentException('Package ' . $packageKey . ' is not active', 1550351445);
|
||||
}
|
||||
$package = $this->packageManager->getPackage($packageKey);
|
||||
}
|
||||
$serviceProviderClassName = $package->getServiceProvider();
|
||||
$instance = new $serviceProviderClassName($package);
|
||||
|
||||
if (!$instance instanceof ServiceProviderInterface) {
|
||||
throw new \InvalidArgumentException('Service providers need to implement ' . ServiceProviderInterface::class, 1550302554);
|
||||
}
|
||||
|
||||
return $this->instances[$packageKey] = $instance;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result of the getFactories call on service provider whose key in the registry is $packageKey.
|
||||
* The result is cached in the registry so two successive calls will trigger `getFactories` only once.
|
||||
*
|
||||
* @param string $packageKey Key of the service provider in the registry
|
||||
*/
|
||||
public function getFactories(string $packageKey): array
|
||||
{
|
||||
return $this->serviceFactories[$packageKey] ?? ($this->serviceFactories[$packageKey] = $this->get($packageKey)->getFactories());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the result of the getExtensions call on service provider whose key in the registry is $packageKey.
|
||||
* The result is cached in the registry so two successive calls will trigger `getExtensions` only once.
|
||||
*
|
||||
* @param string $packageKey Key of the service provider in the registry
|
||||
*/
|
||||
public function getExtensions(string $packageKey): array
|
||||
{
|
||||
return $this->serviceExtensions[$packageKey] ?? ($this->serviceExtensions[$packageKey] = $this->get($packageKey)->getExtensions());
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageKey Key of the service provider in the registry
|
||||
* @param string $serviceName Name of the service to fetch
|
||||
* @return mixed
|
||||
*/
|
||||
public function createService(string $packageKey, string $serviceName, ContainerInterface $container)
|
||||
{
|
||||
$factory = $this->getFactories($packageKey)[$serviceName];
|
||||
return $factory($container);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param string $packageKey Key of the service provider in the registry
|
||||
* @param string $serviceName Name of the service to fetch
|
||||
* @param mixed $previous
|
||||
* @return mixed
|
||||
*/
|
||||
public function extendService(string $packageKey, string $serviceName, ContainerInterface $container, $previous = null)
|
||||
{
|
||||
$extension = $this->getExtensions($packageKey)[$serviceName];
|
||||
return $extension($container, $previous);
|
||||
}
|
||||
|
||||
public function getIterator(): \Generator
|
||||
{
|
||||
foreach ($this->packageManager->getActivePackages() as $package) {
|
||||
if ($this->failsafe && $package->isPartOfMinimalUsableSystem() === false) {
|
||||
continue;
|
||||
}
|
||||
$packageKey = $package->getPackageKey();
|
||||
yield $packageKey => ($this->instances[$packageKey] ?? $this->create($packageKey, $package));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
final class SingletonPass implements CompilerPassInterface
|
||||
{
|
||||
/**
|
||||
* @var string
|
||||
*/
|
||||
private $tagName;
|
||||
|
||||
public function __construct(string $tagName)
|
||||
{
|
||||
$this->tagName = $tagName;
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) {
|
||||
$definition = $container->findDefinition($id);
|
||||
if (!$definition->isAutoconfigured() || $definition->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Singletons need to be shared (that's symfony's configuration for singletons)
|
||||
// They need to be public to be available for legacy makeInstance usage.
|
||||
$definition->setShared(true)->setPublic(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
<?php
|
||||
|
||||
declare(strict_types=1);
|
||||
|
||||
/*
|
||||
* This file is part of the TYPO3 CMS project.
|
||||
*
|
||||
* It is free software; you can redistribute it and/or modify it under
|
||||
* the terms of the GNU General Public License, either version 2
|
||||
* of the License, or any later version.
|
||||
*
|
||||
* For the full copyright and license information, please read the
|
||||
* LICENSE.txt file that was distributed with this source code.
|
||||
*
|
||||
* The TYPO3 project - inspiring people to share!
|
||||
*/
|
||||
|
||||
namespace TYPO3\CMS\Core\DependencyInjection;
|
||||
|
||||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use TYPO3\CMS\Core\DataHandling\SoftReference\SoftReferenceParserFactory;
|
||||
|
||||
/**
|
||||
* Compiler pass to register tagged softreference parsers
|
||||
*
|
||||
* @internal
|
||||
*/
|
||||
final class SoftReferenceParserPass implements CompilerPassInterface
|
||||
{
|
||||
private string $tagName;
|
||||
|
||||
public function __construct(string $tagName)
|
||||
{
|
||||
$this->tagName = $tagName;
|
||||
}
|
||||
|
||||
public function process(ContainerBuilder $container): void
|
||||
{
|
||||
$parserFactoryDefinition = $container->findDefinition(SoftReferenceParserFactory::class);
|
||||
foreach ($container->findTaggedServiceIds($this->tagName) as $id => $tags) {
|
||||
$definition = $container->findDefinition($id);
|
||||
if (!$definition->isAutoconfigured() || $definition->isAbstract()) {
|
||||
continue;
|
||||
}
|
||||
$definition->setPublic(true);
|
||||
foreach ($tags as $attributes) {
|
||||
if (!($attributes['parserKey'] ?? false)) {
|
||||
throw new \InvalidArgumentException(
|
||||
'Service tag "softreference.parser" requires the attribute "parserKey" to be set. Missing in: ' . $id,
|
||||
1628736154
|
||||
);
|
||||
}
|
||||
$parserFactoryDefinition->addMethodCall('addParser', [$definition, $attributes['parserKey']]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user