<?php
namespace Symfony\Component\DependencyInjection\Compiler;
use Symfony\Component\DependencyInjection\ContainerBuilder;
use Symfony\Component\DependencyInjection\Definition;
use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
use Symfony\Component\DependencyInjection\Reference;
class CheckFactoryBuilderCircularReferencePass implements CompilerPassInterface
{
private ContainerBuilder $container;
private array $visited;
private array $path;
private string $currentId;
public function process(ContainerBuilder $container): void
{
$this->container = $container;
try {
foreach ($container->getDefinitions() as $id => $definition) {
$factory = $definition->getFactory();
if (!\is_array($factory) || !$factory[0] instanceof Definition) {
continue;
}
$builder = $factory[0];
if (!$builder->getMethodCalls() && !$builder->getProperties() && null === $builder->getConfigurator()) {
continue;
}
$this->currentId = $id;
$this->visited = [$id => true];
$this->path = [$id];
$setup = [$builder->getProperties(), $builder->getMethodCalls(), $builder->getConfigurator()];
if ($this->setupReferencesCurrent($setup)) {
$this->path[] = $id;
throw new ServiceCircularReferenceException($id, $this->path);
}
}
} finally {
unset($this->container, $this->visited, $this->path, $this->currentId);
}
}
private function setupReferencesCurrent(mixed $value): bool
{
if (\is_array($value)) {
foreach ($value as $v) {
if ($this->setupReferencesCurrent($v)) {
return true;
}
}
return false;
}
if ($value instanceof Reference) {
$id = (string) $value;
while ($this->container->hasAlias($id)) {
$id = (string) $this->container->getAlias($id);
}
if ($id === $this->currentId) {
return true;
}
if (isset($this->visited[$id]) || !$this->container->hasDefinition($id)) {
return false;
}
$def = $this->container->getDefinition($id);
if ($def->isLazy() || $def->isSynthetic()) {
return false;
}
$this->visited[$id] = true;
$this->path[] = $id;
if ($this->setupReferencesCurrent([$def->getArguments(), $def->getFactory()])) {
return true;
}
array_pop($this->path);
return false;
}
if ($value instanceof Definition) {
return $this->setupReferencesCurrent([
$value->getArguments(),
$value->getFactory(),
$value->getProperties(),
$value->getMethodCalls(),
$value->getConfigurator(),
]);
}
return false;
}
}