TYPO3 v15 dev-main snapshot ()

This commit is contained in:
2026-08-10 22:31:09 +02:00
commit af8cc155b5
6818 changed files with 642608 additions and 0 deletions
@@ -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),
];
}
}