diff --git a/Argument/LazyClosure.php b/Argument/LazyClosure.php index 3e8718643..3dcc34e4f 100644 --- a/Argument/LazyClosure.php +++ b/Argument/LazyClosure.php @@ -12,7 +12,6 @@ namespace Symfony\Component\DependencyInjection\Argument; use Symfony\Component\DependencyInjection\ContainerBuilder; -use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Reference; diff --git a/Attribute/AsAlias.php b/Attribute/AsAlias.php index 2f03e5fcd..0839afa48 100644 --- a/Attribute/AsAlias.php +++ b/Attribute/AsAlias.php @@ -20,12 +20,20 @@ final class AsAlias { /** - * @param string|null $id The id of the alias - * @param bool $public Whether to declare the alias public + * @var list + */ + public array $when = []; + + /** + * @param string|null $id The id of the alias + * @param bool $public Whether to declare the alias public + * @param string|list $when The environments under which the class will be registered as a service (i.e. "dev", "test", "prod") */ public function __construct( public ?string $id = null, public bool $public = false, + string|array $when = [], ) { + $this->when = (array) $when; } } diff --git a/Attribute/AsTaggedItem.php b/Attribute/AsTaggedItem.php index 6b1a94dd3..de751213a 100644 --- a/Attribute/AsTaggedItem.php +++ b/Attribute/AsTaggedItem.php @@ -16,7 +16,7 @@ * * @author Nicolas Grekas */ -#[\Attribute(\Attribute::TARGET_CLASS)] +#[\Attribute(\Attribute::TARGET_CLASS | \Attribute::IS_REPEATABLE)] class AsTaggedItem { /** diff --git a/CHANGELOG.md b/CHANGELOG.md index 4287747ec..df3486a9d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ CHANGELOG ========= +7.3 +--- + + * Make `#[AsTaggedItem]` repeatable + * Support `@>` as a shorthand for `!service_closure` in yaml files + * Don't skip classes with private constructor when autodiscovering + * Add `Definition::addResourceTag()` and `ContainerBuilder::findTaggedResourceIds()` + for auto-configuration of classes excluded from the service container + * Accept multiple auto-configuration callbacks for the same attribute class + * Leverage native lazy objects when possible for lazy services + * Add `when` argument to `#[AsAlias]` + 7.2 --- diff --git a/Compiler/AbstractRecursivePass.php b/Compiler/AbstractRecursivePass.php index dd8c9cd29..55f8ee7e9 100644 --- a/Compiler/AbstractRecursivePass.php +++ b/Compiler/AbstractRecursivePass.php @@ -181,7 +181,7 @@ protected function getConstructor(Definition $definition, bool $required): ?\Ref throw new RuntimeException(\sprintf('Invalid service "%s": class%s has no constructor.', $this->currentId, \sprintf($class !== $this->currentId ? ' "%s"' : '', $class))); } } elseif (!$r->isPublic()) { - throw new RuntimeException(\sprintf('Invalid service "%s": ', $this->currentId).\sprintf($class !== $this->currentId ? 'constructor of class "%s"' : 'its constructor', $class).' must be public.'); + throw new RuntimeException(\sprintf('Invalid service "%s": ', $this->currentId).\sprintf($class !== $this->currentId ? 'constructor of class "%s"' : 'its constructor', $class).' must be public. Did you miss configuring a factory or a static constructor? Try using the "#[Autoconfigure(constructor: ...)]" attribute for the latter.'); } return $r; diff --git a/Compiler/AttributeAutoconfigurationPass.php b/Compiler/AttributeAutoconfigurationPass.php index 9c3b98eab..bbf341913 100644 --- a/Compiler/AttributeAutoconfigurationPass.php +++ b/Compiler/AttributeAutoconfigurationPass.php @@ -31,49 +31,51 @@ final class AttributeAutoconfigurationPass extends AbstractRecursivePass public function process(ContainerBuilder $container): void { - if (!$container->getAutoconfiguredAttributes()) { + if (!$container->getAttributeAutoconfigurators()) { return; } - foreach ($container->getAutoconfiguredAttributes() as $attributeName => $callable) { - $callableReflector = new \ReflectionFunction($callable(...)); - if ($callableReflector->getNumberOfParameters() <= 2) { - $this->classAttributeConfigurators[$attributeName] = $callable; - continue; - } + foreach ($container->getAttributeAutoconfigurators() as $attributeName => $callables) { + foreach ($callables as $callable) { + $callableReflector = new \ReflectionFunction($callable(...)); + if ($callableReflector->getNumberOfParameters() <= 2) { + $this->classAttributeConfigurators[$attributeName][] = $callable; + continue; + } - $reflectorParameter = $callableReflector->getParameters()[2]; - $parameterType = $reflectorParameter->getType(); - $types = []; - if ($parameterType instanceof \ReflectionUnionType) { - foreach ($parameterType->getTypes() as $type) { - $types[] = $type->getName(); + $reflectorParameter = $callableReflector->getParameters()[2]; + $parameterType = $reflectorParameter->getType(); + $types = []; + if ($parameterType instanceof \ReflectionUnionType) { + foreach ($parameterType->getTypes() as $type) { + $types[] = $type->getName(); + } + } elseif ($parameterType instanceof \ReflectionNamedType) { + $types[] = $parameterType->getName(); + } else { + throw new LogicException(\sprintf('Argument "$%s" of attribute autoconfigurator should have a type, use one or more of "\ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter|\Reflector" in "%s" on line "%d".', $reflectorParameter->getName(), $callableReflector->getFileName(), $callableReflector->getStartLine())); } - } elseif ($parameterType instanceof \ReflectionNamedType) { - $types[] = $parameterType->getName(); - } else { - throw new LogicException(\sprintf('Argument "$%s" of attribute autoconfigurator should have a type, use one or more of "\ReflectionClass|\ReflectionMethod|\ReflectionProperty|\ReflectionParameter|\Reflector" in "%s" on line "%d".', $reflectorParameter->getName(), $callableReflector->getFileName(), $callableReflector->getStartLine())); - } - try { - $attributeReflector = new \ReflectionClass($attributeName); - } catch (\ReflectionException) { - continue; - } + try { + $attributeReflector = new \ReflectionClass($attributeName); + } catch (\ReflectionException) { + continue; + } - $targets = $attributeReflector->getAttributes(\Attribute::class)[0] ?? 0; - $targets = $targets ? $targets->getArguments()[0] ?? -1 : 0; + $targets = $attributeReflector->getAttributes(\Attribute::class)[0] ?? 0; + $targets = $targets ? $targets->getArguments()[0] ?? -1 : 0; - foreach (['class', 'method', 'property', 'parameter'] as $symbol) { - if (['Reflector'] !== $types) { - if (!\in_array('Reflection'.ucfirst($symbol), $types, true)) { - continue; - } - if (!($targets & \constant('Attribute::TARGET_'.strtoupper($symbol)))) { - throw new LogicException(\sprintf('Invalid type "Reflection%s" on argument "$%s": attribute "%s" cannot target a '.$symbol.' in "%s" on line "%d".', ucfirst($symbol), $reflectorParameter->getName(), $attributeName, $callableReflector->getFileName(), $callableReflector->getStartLine())); + foreach (['class', 'method', 'property', 'parameter'] as $symbol) { + if (['Reflector'] !== $types) { + if (!\in_array('Reflection'.ucfirst($symbol), $types, true)) { + continue; + } + if (!($targets & \constant('Attribute::TARGET_'.strtoupper($symbol)))) { + throw new LogicException(\sprintf('Invalid type "Reflection%s" on argument "$%s": attribute "%s" cannot target a '.$symbol.' in "%s" on line "%d".', ucfirst($symbol), $reflectorParameter->getName(), $attributeName, $callableReflector->getFileName(), $callableReflector->getStartLine())); + } } + $this->{$symbol.'AttributeConfigurators'}[$attributeName][] = $callable; } - $this->{$symbol.'AttributeConfigurators'}[$attributeName] = $callable; } } @@ -94,13 +96,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed $instanceof = $value->getInstanceofConditionals(); $conditionals = $instanceof[$classReflector->getName()] ?? new ChildDefinition(''); - if ($this->classAttributeConfigurators) { - foreach ($classReflector->getAttributes() as $attribute) { - if ($configurator = $this->findConfigurator($this->classAttributeConfigurators, $attribute->getName())) { - $configurator($conditionals, $attribute->newInstance(), $classReflector); - } - } - } + $this->callConfigurators($this->classAttributeConfigurators, $conditionals, $classReflector); if ($this->parameterAttributeConfigurators) { try { @@ -111,11 +107,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed if ($constructorReflector) { foreach ($constructorReflector->getParameters() as $parameterReflector) { - foreach ($parameterReflector->getAttributes() as $attribute) { - if ($configurator = $this->findConfigurator($this->parameterAttributeConfigurators, $attribute->getName())) { - $configurator($conditionals, $attribute->newInstance(), $parameterReflector); - } - } + $this->callConfigurators($this->parameterAttributeConfigurators, $conditionals, $parameterReflector); } } } @@ -126,22 +118,10 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed continue; } - if ($this->methodAttributeConfigurators) { - foreach ($methodReflector->getAttributes() as $attribute) { - if ($configurator = $this->findConfigurator($this->methodAttributeConfigurators, $attribute->getName())) { - $configurator($conditionals, $attribute->newInstance(), $methodReflector); - } - } - } + $this->callConfigurators($this->methodAttributeConfigurators, $conditionals, $methodReflector); - if ($this->parameterAttributeConfigurators) { - foreach ($methodReflector->getParameters() as $parameterReflector) { - foreach ($parameterReflector->getAttributes() as $attribute) { - if ($configurator = $this->findConfigurator($this->parameterAttributeConfigurators, $attribute->getName())) { - $configurator($conditionals, $attribute->newInstance(), $parameterReflector); - } - } - } + foreach ($methodReflector->getParameters() as $parameterReflector) { + $this->callConfigurators($this->parameterAttributeConfigurators, $conditionals, $parameterReflector); } } } @@ -152,11 +132,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed continue; } - foreach ($propertyReflector->getAttributes() as $attribute) { - if ($configurator = $this->findConfigurator($this->propertyAttributeConfigurators, $attribute->getName())) { - $configurator($conditionals, $attribute->newInstance(), $propertyReflector); - } - } + $this->callConfigurators($this->propertyAttributeConfigurators, $conditionals, $propertyReflector); } } @@ -168,19 +144,37 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed return parent::processValue($value, $isRoot); } + /** + * Call all the configurators for the given attribute. + * + * @param array $configurators + */ + private function callConfigurators(array &$configurators, ChildDefinition $conditionals, \ReflectionClass|\ReflectionMethod|\ReflectionParameter|\ReflectionProperty $reflector): void + { + if (!$configurators) { + return; + } + + foreach ($reflector->getAttributes() as $attribute) { + foreach ($this->findConfigurators($configurators, $attribute->getName()) as $configurator) { + $configurator($conditionals, $attribute->newInstance(), $reflector); + } + } + } + /** * Find the first configurator for the given attribute name, looking up the class hierarchy. */ - private function findConfigurator(array &$configurators, string $attributeName): ?callable + private function findConfigurators(array &$configurators, string $attributeName): array { if (\array_key_exists($attributeName, $configurators)) { return $configurators[$attributeName]; } if (class_exists($attributeName) && $parent = get_parent_class($attributeName)) { - return $configurators[$attributeName] = self::findConfigurator($configurators, $parent); + return $configurators[$attributeName] = $this->findConfigurators($configurators, $parent); } - return $configurators[$attributeName] = null; + return $configurators[$attributeName] = []; } } diff --git a/Compiler/CheckCircularReferencesPass.php b/Compiler/CheckCircularReferencesPass.php index 7adb0b4d8..8a7c11383 100644 --- a/Compiler/CheckCircularReferencesPass.php +++ b/Compiler/CheckCircularReferencesPass.php @@ -62,7 +62,7 @@ private function checkOutEdges(array $edges): void continue; } - $isLeaf = !!$node->getValue(); + $isLeaf = (bool) $node->getValue(); $isConcrete = !$edge->isLazy() && !$edge->isWeak(); // Skip already checked lazy services if they are still lazy. Will not gain any new information. diff --git a/Compiler/PriorityTaggedServiceTrait.php b/Compiler/PriorityTaggedServiceTrait.php index e3a4eba27..8c6b5b582 100644 --- a/Compiler/PriorityTaggedServiceTrait.php +++ b/Compiler/PriorityTaggedServiceTrait.php @@ -64,6 +64,7 @@ private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagNam $definition = $container->getDefinition($serviceId); $class = $definition->getClass(); $class = $container->getParameterBag()->resolveValue($class) ?: null; + $reflector = null !== $class ? $container->getReflectionClass($class) : null; $checkTaggedItem = !$definition->hasTag($definition->isAutoconfigured() ? 'container.ignore_attributes' : $tagName); foreach ($attributes as $attribute) { @@ -71,8 +72,8 @@ private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagNam if (isset($attribute['priority'])) { $priority = $attribute['priority']; - } elseif (null === $defaultPriority && $defaultPriorityMethod && $class) { - $defaultPriority = PriorityTaggedServiceUtil::getDefault($container, $serviceId, $class, $defaultPriorityMethod, $tagName, 'priority', $checkTaggedItem); + } elseif (null === $defaultPriority && $defaultPriorityMethod && $reflector) { + $defaultPriority = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultPriorityMethod, $tagName, 'priority', $checkTaggedItem); } $priority ??= $defaultPriority ??= 0; @@ -84,13 +85,28 @@ private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagNam if (null !== $indexAttribute && isset($attribute[$indexAttribute])) { $index = $parameterBag->resolveValue($attribute[$indexAttribute]); } - if (null === $index && null === $defaultIndex && $defaultPriorityMethod && $class) { - $defaultIndex = PriorityTaggedServiceUtil::getDefault($container, $serviceId, $class, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute, $checkTaggedItem); + if (null === $index && null === $defaultIndex && $defaultPriorityMethod && $reflector) { + $defaultIndex = PriorityTaggedServiceUtil::getDefault($serviceId, $reflector, $defaultIndexMethod ?? 'getDefaultName', $tagName, $indexAttribute, $checkTaggedItem); } $index ??= $defaultIndex ??= $definition->getTag('container.decorator')[0]['id'] ?? $serviceId; $services[] = [$priority, ++$i, $index, $serviceId, $class]; } + + if ($reflector) { + $attributes = $reflector->getAttributes(AsTaggedItem::class); + $attributeCount = \count($attributes); + + foreach ($attributes as $attribute) { + $instance = $attribute->newInstance(); + + if (!$instance->index && 1 < $attributeCount) { + throw new InvalidArgumentException(\sprintf('Attribute "%s" on class "%s" cannot have an empty index when repeated.', AsTaggedItem::class, $class)); + } + + $services[] = [$instance->priority ?? 0, ++$i, $instance->index ?? $serviceId, $serviceId, $class]; + } + } } uasort($services, static fn ($a, $b) => $b[0] <=> $a[0] ?: $a[1] <=> $b[1]); @@ -121,9 +137,11 @@ private function findAndSortTaggedServices(string|TaggedIteratorArgument $tagNam */ class PriorityTaggedServiceUtil { - public static function getDefault(ContainerBuilder $container, string $serviceId, string $class, string $defaultMethod, string $tagName, ?string $indexAttribute, bool $checkTaggedItem): string|int|null + public static function getDefault(string $serviceId, \ReflectionClass $r, string $defaultMethod, string $tagName, ?string $indexAttribute, bool $checkTaggedItem): string|int|null { - if (!($r = $container->getReflectionClass($class)) || (!$checkTaggedItem && !$r->hasMethod($defaultMethod))) { + $class = $r->getName(); + + if (!$checkTaggedItem && !$r->hasMethod($defaultMethod)) { return null; } diff --git a/Compiler/ResolveBindingsPass.php b/Compiler/ResolveBindingsPass.php index 4fca2081b..b2c6f6ef7 100644 --- a/Compiler/ResolveBindingsPass.php +++ b/Compiler/ResolveBindingsPass.php @@ -228,7 +228,7 @@ protected function processValue(mixed $value, bool $isRoot = false): mixed foreach ($names as $key => $name) { if (\array_key_exists($name, $arguments) && (0 === $key || \array_key_exists($key - 1, $arguments))) { - if (!array_key_exists($key, $arguments)) { + if (!\array_key_exists($key, $arguments)) { $arguments[$key] = $arguments[$name]; } unset($arguments[$name]); diff --git a/Compiler/ResolveInstanceofConditionalsPass.php b/Compiler/ResolveInstanceofConditionalsPass.php index 90d4569c4..8b0a804dc 100644 --- a/Compiler/ResolveInstanceofConditionalsPass.php +++ b/Compiler/ResolveInstanceofConditionalsPass.php @@ -149,6 +149,11 @@ private function processDefinition(ContainerBuilder $container, string $id, Defi ->setAbstract(true); } + if ($definition->isSynthetic()) { + // Ignore container.excluded tag on synthetic services + $definition->clearTag('container.excluded'); + } + return $definition; } diff --git a/ContainerBuilder.php b/ContainerBuilder.php index e978cac41..4d2b7f96b 100644 --- a/ContainerBuilder.php +++ b/ContainerBuilder.php @@ -129,7 +129,7 @@ class ContainerBuilder extends Container implements TaggedContainerInterface private array $autoconfiguredInstanceof = []; /** - * @var array + * @var array */ private array $autoconfiguredAttributes = []; @@ -717,12 +717,11 @@ public function merge(self $container): void $this->autoconfiguredInstanceof[$interface] = $childDefinition; } - foreach ($container->getAutoconfiguredAttributes() as $attribute => $configurator) { - if (isset($this->autoconfiguredAttributes[$attribute])) { - throw new InvalidArgumentException(\sprintf('"%s" has already been autoconfigured and merge() does not support merging autoconfiguration for the same attribute.', $attribute)); - } - - $this->autoconfiguredAttributes[$attribute] = $configurator; + foreach ($container->getAttributeAutoconfigurators() as $attribute => $configurators) { + $this->autoconfiguredAttributes[$attribute] = array_merge( + $this->autoconfiguredAttributes[$attribute] ?? [], + $configurators) + ; } } @@ -1175,7 +1174,7 @@ private function createService(Definition $definition, array &$inlineServices, b if (!$definition->isDeprecated() && \is_array($factory) && \is_string($factory[0])) { $r = new \ReflectionClass($factory[0]); - if (0 < strpos($r->getDocComment(), "\n * @deprecated ")) { + if (0 < strpos($r->getDocComment() ?: '', "\n * @deprecated ")) { trigger_deprecation('', '', 'The "%s" service relies on the deprecated "%s" factory class. It should either be deprecated or its factory upgraded.', $id, $r->name); } } @@ -1192,7 +1191,7 @@ private function createService(Definition $definition, array &$inlineServices, b $service = $r->getConstructor() ? $r->newInstanceArgs($arguments) : $r->newInstance(); } - if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment(), "\n * @deprecated ")) { + if (!$definition->isDeprecated() && 0 < strpos($r->getDocComment() ?: '', "\n * @deprecated ")) { trigger_deprecation('', '', 'The "%s" service relies on the deprecated "%s" class. It should either be deprecated or its implementation upgraded.', $id, $r->name); } } @@ -1353,6 +1352,38 @@ public function findTaggedServiceIds(string $name, bool $throwOnAbstract = false return $tags; } + /** + * Returns service ids for a given tag, asserting they have the "container.excluded" tag. + * + * Example: + * + * $container->register('foo')->addResourceTag('my.tag', ['hello' => 'world']) + * + * $serviceIds = $container->findTaggedResourceIds('my.tag'); + * foreach ($serviceIds as $serviceId => $tags) { + * foreach ($tags as $tag) { + * echo $tag['hello']; + * } + * } + * + * @return array An array of tags with the tagged service as key, holding a list of attribute arrays + */ + public function findTaggedResourceIds(string $tagName): array + { + $this->usedTags[] = $tagName; + $tags = []; + foreach ($this->getDefinitions() as $id => $definition) { + if ($definition->hasTag($tagName)) { + if (!$definition->hasTag('container.excluded')) { + throw new InvalidArgumentException(\sprintf('The resource "%s" tagged "%s" is missing the "container.excluded" tag.', $id, $tagName)); + } + $tags[$id] = $definition->getTag($tagName); + } + } + + return $tags; + } + /** * Returns all tags the defined services use. * @@ -1418,7 +1449,7 @@ public function registerForAutoconfiguration(string $interface): ChildDefinition */ public function registerAttributeForAutoconfiguration(string $attributeClass, callable $configurator): void { - $this->autoconfiguredAttributes[$attributeClass] = $configurator; + $this->autoconfiguredAttributes[$attributeClass][] = $configurator; } /** @@ -1459,9 +1490,30 @@ public function getAutoconfiguredInstanceof(): array } /** - * @return array + * @return array + * + * @deprecated Use {@see getAttributeAutoconfigurators()} instead */ public function getAutoconfiguredAttributes(): array + { + trigger_deprecation('symfony/dependency-injection', '7.3', 'The "%s()" method is deprecated, use "getAttributeAutoconfigurators()" instead.', __METHOD__); + + $autoconfiguredAttributes = []; + foreach ($this->autoconfiguredAttributes as $attribute => $configurators) { + if (\count($configurators) > 1) { + throw new LogicException(\sprintf('The "%s" attribute has %d configurators. Use "getAttributeAutoconfigurators()" to get all of them.', $attribute, \count($configurators))); + } + + $autoconfiguredAttributes[$attribute] = $configurators[0]; + } + + return $autoconfiguredAttributes; + } + + /** + * @return array + */ + public function getAttributeAutoconfigurators(): array { return $this->autoconfiguredAttributes; } diff --git a/ContainerInterface.php b/ContainerInterface.php index 39fd080c3..6d6f6d3bf 100644 --- a/ContainerInterface.php +++ b/ContainerInterface.php @@ -33,11 +33,13 @@ interface ContainerInterface extends PsrContainerInterface public function set(string $id, ?object $service): void; /** + * @template C of object * @template B of self::*_REFERENCE * - * @param B $invalidBehavior + * @param string|class-string $id + * @param B $invalidBehavior * - * @psalm-return (B is self::EXCEPTION_ON_INVALID_REFERENCE|self::RUNTIME_EXCEPTION_ON_INVALID_REFERENCE ? object : object|null) + * @return ($id is class-string ? (B is 0|1 ? C|object : C|object|null) : (B is 0|1 ? object : object|null)) * * @throws ServiceCircularReferenceException When a circular reference is detected * @throws ServiceNotFoundException When the service is not defined diff --git a/Definition.php b/Definition.php index 0abdc5d56..61cc0b9d6 100644 --- a/Definition.php +++ b/Definition.php @@ -455,6 +455,20 @@ public function addTag(string $name, array $attributes = []): static return $this; } + /** + * Adds a "resource" tag to the definition and marks it as excluded. + * + * These definitions should be processed using {@see ContainerBuilder::findTaggedResourceIds()} + * + * @return $this + */ + public function addResourceTag(string $name, array $attributes = []): static + { + return $this->addTag($name, $attributes) + ->addTag('container.excluded', ['source' => \sprintf('by tag "%s"', $name)]) + ->setAbstract(true); + } + /** * Whether this definition has a tag with the given name. */ diff --git a/Dumper/PhpDumper.php b/Dumper/PhpDumper.php index 9568ad26b..fb2d45f92 100644 --- a/Dumper/PhpDumper.php +++ b/Dumper/PhpDumper.php @@ -527,7 +527,7 @@ private function collectLineage(string $class, array &$lineage): void return; } $file = $r->getFileName(); - if (str_ends_with($file, ') : eval()\'d code')) { + if ($file && str_ends_with($file, ') : eval()\'d code')) { $file = substr($file, 0, strrpos($file, '(', -17)); } if (!$file || $this->doExport($file) === $exportedFile = $this->export($file)) { @@ -574,12 +574,13 @@ private function generateProxyClasses(): array continue; } do { - $file = $r->getFileName(); - if (str_ends_with($file, ') : eval()\'d code')) { - $file = substr($file, 0, strrpos($file, '(', -17)); - } - if (is_file($file)) { - $this->container->addResource(new FileResource($file)); + if ($file = $r->getFileName()) { + if (str_ends_with($file, ') : eval()\'d code')) { + $file = substr($file, 0, strrpos($file, '(', -17)); + } + if (is_file($file)) { + $this->container->addResource(new FileResource($file)); + } } $r = $r->getParentClass() ?: null; } while ($r?->isUserDefined()); diff --git a/ExpressionLanguage.php b/ExpressionLanguage.php index 84d45dbdd..79de5b049 100644 --- a/ExpressionLanguage.php +++ b/ExpressionLanguage.php @@ -27,8 +27,12 @@ */ class ExpressionLanguage extends BaseExpressionLanguage { - public function __construct(?CacheItemPoolInterface $cache = null, array $providers = [], ?callable $serviceCompiler = null, ?\Closure $getEnv = null) + public function __construct(?CacheItemPoolInterface $cache = null, iterable $providers = [], ?callable $serviceCompiler = null, ?\Closure $getEnv = null) { + if (!\is_array($providers)) { + $providers = iterator_to_array($providers, false); + } + // prepend the default provider to let users override it easily array_unshift($providers, new ExpressionLanguageProvider($serviceCompiler, $getEnv)); diff --git a/LazyProxy/Instantiator/LazyServiceInstantiator.php b/LazyProxy/Instantiator/LazyServiceInstantiator.php index f5e7dead5..107482562 100644 --- a/LazyProxy/Instantiator/LazyServiceInstantiator.php +++ b/LazyProxy/Instantiator/LazyServiceInstantiator.php @@ -29,10 +29,19 @@ public function instantiateProxy(ContainerInterface $container, Definition $defi throw new InvalidArgumentException(\sprintf('Cannot instantiate lazy proxy for service "%s".', $id)); } - if (!class_exists($proxyClass = $dumper->getProxyClass($definition, $asGhostObject), false)) { + if (\PHP_VERSION_ID >= 80400 && $asGhostObject) { + return (new \ReflectionClass($definition->getClass()))->newLazyGhost(static function ($ghost) use ($realInstantiator) { $realInstantiator($ghost); }); + } + + $class = null; + if (!class_exists($proxyClass = $dumper->getProxyClass($definition, $asGhostObject, $class), false)) { eval($dumper->getProxyCode($definition, $id)); } - return $asGhostObject ? $proxyClass::createLazyGhost($realInstantiator) : $proxyClass::createLazyProxy($realInstantiator); + if ($definition->getClass() === $proxyClass) { + return $class->newLazyProxy($realInstantiator); + } + + return \PHP_VERSION_ID < 80400 && $asGhostObject ? $proxyClass::createLazyGhost($realInstantiator) : $proxyClass::createLazyProxy($realInstantiator); } } diff --git a/LazyProxy/PhpDumper/LazyServiceDumper.php b/LazyProxy/PhpDumper/LazyServiceDumper.php index b335fa378..c534630e3 100644 --- a/LazyProxy/PhpDumper/LazyServiceDumper.php +++ b/LazyProxy/PhpDumper/LazyServiceDumper.php @@ -56,9 +56,21 @@ public function isProxyCandidate(Definition $definition, ?bool &$asGhostObject = } } + if (\PHP_VERSION_ID < 80400) { + try { + $asGhostObject = (bool) ProxyHelper::generateLazyGhost(new \ReflectionClass($class)); + } catch (LogicException) { + } + + return true; + } + try { - $asGhostObject = (bool) ProxyHelper::generateLazyGhost(new \ReflectionClass($class)); - } catch (LogicException) { + $asGhostObject = (bool) (new \ReflectionClass($class))->newLazyGhost(static fn () => null); + } catch (\Error $e) { + if (__FILE__ !== $e->getFile()) { + throw $e; + } } return true; @@ -76,6 +88,16 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $ $proxyClass = $this->getProxyClass($definition, $asGhostObject); if (!$asGhostObject) { + if ($definition->getClass() === $proxyClass) { + return <<newLazyProxy(static fn () => $factoryCode); + } + + + EOF; + } + return <<createProxy('$proxyClass', static fn () => \\$proxyClass::createLazyProxy(static fn () => $factoryCode)); @@ -85,11 +107,23 @@ public function getProxyFactoryCode(Definition $definition, string $id, string $ EOF; } - $factoryCode = \sprintf('static fn ($proxy) => %s', $factoryCode); + if (\PHP_VERSION_ID < 80400) { + $factoryCode = \sprintf('static fn ($proxy) => %s', $factoryCode); + + return <<createProxy('$proxyClass', static fn () => \\$proxyClass::createLazyGhost($factoryCode)); + } + + + EOF; + } + + $factoryCode = \sprintf('static function ($proxy) use ($container) { %s; }', $factoryCode); return <<createProxy('$proxyClass', static fn () => \\$proxyClass::createLazyGhost($factoryCode)); + $instantiation new \ReflectionClass('$proxyClass')->newLazyGhost($factoryCode); } @@ -104,12 +138,21 @@ public function getProxyCode(Definition $definition, ?string $id = null): string $proxyClass = $this->getProxyClass($definition, $asGhostObject, $class); if ($asGhostObject) { + if (\PHP_VERSION_ID >= 80400) { + return ''; + } + try { return ($class?->isReadOnly() ? 'readonly ' : '').'class '.$proxyClass.ProxyHelper::generateLazyGhost($class); } catch (LogicException $e) { throw new InvalidArgumentException(\sprintf('Cannot generate lazy ghost for service "%s".', $id ?? $definition->getClass()), 0, $e); } } + + if ($definition->getClass() === $proxyClass) { + return ''; + } + $interfaces = []; if ($definition->hasTag('proxy')) { @@ -144,8 +187,28 @@ public function getProxyClass(Definition $definition, bool $asGhostObject, ?\Ref $class = 'object' !== $definition->getClass() ? $definition->getClass() : 'stdClass'; $class = new \ReflectionClass($class); - return preg_replace('/^.*\\\\/', '', $definition->getClass()) - .($asGhostObject ? 'Ghost' : 'Proxy') + if (\PHP_VERSION_ID < 80400) { + return preg_replace('/^.*\\\\/', '', $definition->getClass()) + .($asGhostObject ? 'Ghost' : 'Proxy') + .ucfirst(substr(hash('xxh128', $this->salt.'+'.$class->name.'+'.serialize($definition->getTag('proxy'))), -7)); + } + + if ($asGhostObject) { + return $class->name; + } + + if (!$definition->hasTag('proxy') && !$class->isAbstract()) { + $parent = $class; + do { + $extendsInternalClass = $parent->isInternal(); + } while (!$extendsInternalClass && $parent = $parent->getParentClass()); + + if (!$extendsInternalClass) { + return $class->name; + } + } + + return preg_replace('/^.*\\\\/', '', $definition->getClass()).'Proxy' .ucfirst(substr(hash('xxh128', $this->salt.'+'.$class->name.'+'.serialize($definition->getTag('proxy'))), -7)); } } diff --git a/Loader/FileLoader.php b/Loader/FileLoader.php index a4d812e37..bc38767bc 100644 --- a/Loader/FileLoader.php +++ b/Loader/FileLoader.php @@ -126,7 +126,7 @@ public function registerClasses(Definition $prototype, string $namespace, string $autoconfigureAttributes = new RegisterAutoconfigureAttributesPass(); $autoconfigureAttributes = $autoconfigureAttributes->accept($prototype) ? $autoconfigureAttributes : null; - $classes = $this->findClasses($namespace, $resource, (array) $exclude, $autoconfigureAttributes, $source); + $classes = $this->findClasses($namespace, $resource, (array) $exclude, $source); $getPrototype = static fn () => clone $prototype; $serialized = serialize($prototype); @@ -188,39 +188,48 @@ public function registerClasses(Definition $prototype, string $namespace, string } } - if (interface_exists($class, false)) { - $this->interfaces[] = $class; - } else { - $this->setDefinition($class, $definition = $getPrototype()); - if (null !== $errorMessage) { - $definition->addError($errorMessage); - - continue; + $r = null === $errorMessage ? $this->container->getReflectionClass($class) : null; + if ($r?->isAbstract() || $r?->isInterface()) { + if ($r->isInterface()) { + $this->interfaces[] = $class; } - $definition->setClass($class); + $autoconfigureAttributes?->processClass($this->container, $r); + continue; + } - $interfaces = []; - foreach (class_implements($class, false) as $interface) { - $this->singlyImplemented[$interface] = ($this->singlyImplemented[$interface] ?? $class) !== $class ? false : $class; - $interfaces[] = $interface; - } + $this->setDefinition($class, $definition = $getPrototype()); + if (null !== $errorMessage) { + $definition->addError($errorMessage); - if (!$autoconfigureAttributes) { - continue; + continue; + } + $definition->setClass($class); + + $interfaces = []; + foreach (class_implements($class, false) as $interface) { + $this->singlyImplemented[$interface] = ($this->singlyImplemented[$interface] ?? $class) !== $class ? false : $class; + $interfaces[] = $interface; + } + + if (!$autoconfigureAttributes) { + continue; + } + $r = $this->container->getReflectionClass($class); + $defaultAlias = 1 === \count($interfaces) ? $interfaces[0] : null; + foreach ($r->getAttributes(AsAlias::class) as $attr) { + /** @var AsAlias $attribute */ + $attribute = $attr->newInstance(); + $alias = $attribute->id ?? $defaultAlias; + $public = $attribute->public; + if (null === $alias) { + throw new LogicException(\sprintf('Alias cannot be automatically determined for class "%s". If you have used the #[AsAlias] attribute with a class implementing multiple interfaces, add the interface you want to alias to the first parameter of #[AsAlias].', $class)); } - $r = $this->container->getReflectionClass($class); - $defaultAlias = 1 === \count($interfaces) ? $interfaces[0] : null; - foreach ($r->getAttributes(AsAlias::class) as $attr) { - /** @var AsAlias $attribute */ - $attribute = $attr->newInstance(); - $alias = $attribute->id ?? $defaultAlias; - $public = $attribute->public; - if (null === $alias) { - throw new LogicException(\sprintf('Alias cannot be automatically determined for class "%s". If you have used the #[AsAlias] attribute with a class implementing multiple interfaces, add the interface you want to alias to the first parameter of #[AsAlias].', $class)); - } + + if (!$attribute->when || \in_array($this->env, $attribute->when, true)) { if (isset($this->aliases[$alias])) { throw new LogicException(\sprintf('The "%s" alias has already been defined with the #[AsAlias] attribute in "%s".', $alias, $this->aliases[$alias])); } + $this->aliases[$alias] = new Alias($class, $public); } } @@ -304,7 +313,7 @@ protected function setDefinition(string $id, Definition $definition): void } } - private function findClasses(string $namespace, string $pattern, array $excludePatterns, ?RegisterAutoconfigureAttributesPass $autoconfigureAttributes, ?string $source): array + private function findClasses(string $namespace, string $pattern, array $excludePatterns, ?string $source): array { $parameterBag = $this->container->getParameterBag(); @@ -356,13 +365,9 @@ private function findClasses(string $namespace, string $pattern, array $excludeP throw new InvalidArgumentException(\sprintf('Expected to find class "%s" in file "%s" while importing services from resource "%s", but it was not found! Check the namespace prefix used with the resource.', $class, $path, $pattern)); } - if ($r->isInstantiable() || $r->isInterface()) { + if (!$r->isTrait()) { $classes[$class] = null; } - - if ($autoconfigureAttributes && !$r->isInstantiable()) { - $autoconfigureAttributes->processClass($this->container, $r); - } } // track only for new & removed files diff --git a/Loader/XmlFileLoader.php b/Loader/XmlFileLoader.php index f59698066..eed874cd9 100644 --- a/Loader/XmlFileLoader.php +++ b/Loader/XmlFileLoader.php @@ -339,7 +339,11 @@ private function parseDefinition(\DOMElement $service, string $file, Definition } foreach ($this->getChildren($service, 'call') as $call) { - $definition->addMethodCall($call->getAttribute('method'), $this->getArgumentsAsPhp($call, 'argument', $file), XmlUtils::phpize($call->getAttribute('returns-clone'))); + $definition->addMethodCall( + $call->getAttribute('method'), + $this->getArgumentsAsPhp($call, 'argument', $file), + XmlUtils::phpize($call->getAttribute('returns-clone')) ?: false + ); } $tags = $this->getChildren($service, 'tag'); diff --git a/Loader/YamlFileLoader.php b/Loader/YamlFileLoader.php index a4a93f63a..c3b1bf255 100644 --- a/Loader/YamlFileLoader.php +++ b/Loader/YamlFileLoader.php @@ -923,6 +923,11 @@ private function resolveServices(mixed $value, string $file, bool $isParameter = return new Expression(substr($value, 2)); } elseif (\is_string($value) && str_starts_with($value, '@')) { + if (str_starts_with($value, '@>')) { + $argument = $this->resolveServices(substr_replace($value, '', 1, 1), $file, $isParameter); + + return new ServiceClosureArgument($argument); + } if (str_starts_with($value, '@@')) { $value = substr($value, 1); $invalidBehavior = null; diff --git a/Tests/Argument/LazyClosureTest.php b/Tests/Argument/LazyClosureTest.php index 9652a86fd..e1b5c2a90 100644 --- a/Tests/Argument/LazyClosureTest.php +++ b/Tests/Argument/LazyClosureTest.php @@ -11,7 +11,6 @@ namespace Symfony\Component\DependencyInjection\Tests\Argument; -use InvalidArgumentException; use PHPUnit\Framework\TestCase; use Symfony\Component\DependencyInjection\Argument\LazyClosure; use Symfony\Component\DependencyInjection\ContainerBuilder; @@ -23,7 +22,7 @@ public function testMagicGetThrows() { $closure = new LazyClosure(fn () => null); - $this->expectException(InvalidArgumentException::class); + $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage('Cannot read property "foo" from a lazy closure.'); $closure->foo; @@ -34,7 +33,7 @@ public function testThrowsWhenNotUsingInterface() $this->expectException(\RuntimeException::class); $this->expectExceptionMessage('Cannot create adapter for service "foo" because "Symfony\Component\DependencyInjection\Tests\Argument\LazyClosureTest" is not an interface.'); - LazyClosure::getCode('foo', [new \stdClass(), 'bar'], LazyClosureTest::class, new ContainerBuilder(), 'foo'); + LazyClosure::getCode('foo', [new \stdClass(), 'bar'], self::class, new ContainerBuilder(), 'foo'); } public function testThrowsOnNonFunctionalInterface() @@ -62,5 +61,6 @@ public function foo(); interface NonFunctionalInterface { public function foo(); + public function bar(); } diff --git a/Tests/Compiler/AutowirePassTest.php b/Tests/Compiler/AutowirePassTest.php index f6aafdec9..114d514ad 100644 --- a/Tests/Compiler/AutowirePassTest.php +++ b/Tests/Compiler/AutowirePassTest.php @@ -174,7 +174,7 @@ public function testPrivateConstructorThrowsAutowireException() $pass->process($container); $this->fail('AutowirePass should have thrown an exception'); } catch (AutowiringFailedException $e) { - $this->assertSame('Invalid service "private_service": constructor of class "Symfony\Component\DependencyInjection\Tests\Compiler\PrivateConstructor" must be public.', (string) $e->getMessage()); + $this->assertSame('Invalid service "private_service": constructor of class "Symfony\Component\DependencyInjection\Tests\Compiler\PrivateConstructor" must be public. Did you miss configuring a factory or a static constructor? Try using the "#[Autoconfigure(constructor: ...)]" attribute for the latter.', (string) $e->getMessage()); } } diff --git a/Tests/Compiler/DecoratorServicePassTest.php b/Tests/Compiler/DecoratorServicePassTest.php index 48ed32df6..9858a10e7 100644 --- a/Tests/Compiler/DecoratorServicePassTest.php +++ b/Tests/Compiler/DecoratorServicePassTest.php @@ -197,7 +197,7 @@ public function testProcessMovesTagsFromDecoratedDefinitionToDecoratingDefinitio $this->process($container); - $this->assertEmpty($container->getDefinition('baz.inner')->getTags()); + $this->assertSame([], $container->getDefinition('baz.inner')->getTags()); $this->assertEquals(['bar' => ['attr' => 'baz'], 'foobar' => ['attr' => 'bar'], 'container.decorator' => [['id' => 'foo', 'inner' => 'baz.inner']]], $container->getDefinition('baz')->getTags()); } @@ -220,7 +220,7 @@ public function testProcessMovesTagsFromDecoratedDefinitionToDecoratingDefinitio $this->process($container); - $this->assertEmpty($container->getDefinition('deco1')->getTags()); + $this->assertSame([], $container->getDefinition('deco1')->getTags()); $this->assertEquals(['bar' => ['attr' => 'baz'], 'container.decorator' => [['id' => 'foo', 'inner' => 'deco1.inner']]], $container->getDefinition('deco2')->getTags()); } diff --git a/Tests/Compiler/PassConfigTest.php b/Tests/Compiler/PassConfigTest.php index 8001c5401..66718b7bb 100644 --- a/Tests/Compiler/PassConfigTest.php +++ b/Tests/Compiler/PassConfigTest.php @@ -45,10 +45,10 @@ public function testPassOrderingWithoutPasses() $config->setOptimizationPasses([]); $config->setRemovingPasses([]); - $this->assertEmpty($config->getBeforeOptimizationPasses()); - $this->assertEmpty($config->getAfterRemovingPasses()); - $this->assertEmpty($config->getBeforeRemovingPasses()); - $this->assertEmpty($config->getOptimizationPasses()); - $this->assertEmpty($config->getRemovingPasses()); + $this->assertSame([], $config->getBeforeOptimizationPasses()); + $this->assertSame([], $config->getAfterRemovingPasses()); + $this->assertSame([], $config->getBeforeRemovingPasses()); + $this->assertSame([], $config->getOptimizationPasses()); + $this->assertSame([], $config->getRemovingPasses()); } } diff --git a/Tests/Compiler/PriorityTaggedServiceTraitTest.php b/Tests/Compiler/PriorityTaggedServiceTraitTest.php index aac1a2e1a..3f767257d 100644 --- a/Tests/Compiler/PriorityTaggedServiceTraitTest.php +++ b/Tests/Compiler/PriorityTaggedServiceTraitTest.php @@ -218,6 +218,9 @@ public function testTaggedItemAttributes() $container->register('service5', HelloNamedService2::class) ->setAutoconfigured(true) ->addTag('my_custom_tag'); + $container->register('service6', MultiTagHelloNamedService::class) + ->setAutoconfigured(true) + ->addTag('my_custom_tag'); (new ResolveInstanceofConditionalsPass())->process($container); @@ -226,14 +229,33 @@ public function testTaggedItemAttributes() $tag = new TaggedIteratorArgument('my_custom_tag', 'foo', 'getFooBar', exclude: ['service4', 'service5']); $expected = [ 'service3' => new TypedReference('service3', HelloNamedService2::class), + 'multi_hello_2' => new TypedReference('service6', MultiTagHelloNamedService::class), 'hello' => new TypedReference('service2', HelloNamedService::class), + 'multi_hello_1' => new TypedReference('service6', MultiTagHelloNamedService::class), 'service1' => new TypedReference('service1', FooTagClass::class), ]; + $services = $priorityTaggedServiceTraitImplementation->test($tag, $container); $this->assertSame(array_keys($expected), array_keys($services)); $this->assertEquals($expected, $priorityTaggedServiceTraitImplementation->test($tag, $container)); } + public function testTaggedItemAttributesRepeatedWithoutNameThrows() + { + $container = new ContainerBuilder(); + $container->register('service1', MultiNoNameTagHelloNamedService::class) + ->setAutoconfigured(true) + ->addTag('my_custom_tag'); + + (new ResolveInstanceofConditionalsPass())->process($container); + $tag = new TaggedIteratorArgument('my_custom_tag', 'foo', 'getFooBar', exclude: ['service4', 'service5']); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('Attribute "Symfony\Component\DependencyInjection\Attribute\AsTaggedItem" on class "Symfony\Component\DependencyInjection\Tests\Compiler\MultiNoNameTagHelloNamedService" cannot have an empty index when repeated.'); + + (new PriorityTaggedServiceTraitImplementation())->test($tag, $container); + } + public function testResolveIndexedTags() { $container = new ContainerBuilder(); @@ -283,6 +305,18 @@ class HelloNamedService2 { } +#[AsTaggedItem(index: 'multi_hello_1', priority: 1)] +#[AsTaggedItem(index: 'multi_hello_2', priority: 2)] +class MultiTagHelloNamedService +{ +} + +#[AsTaggedItem(priority: 1)] +#[AsTaggedItem(priority: 2)] +class MultiNoNameTagHelloNamedService +{ +} + interface HelloInterface { public static function getFooBar(): string; diff --git a/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php b/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php index 78fc261ee..b4e50d39f 100644 --- a/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php +++ b/Tests/Compiler/ResolveInstanceofConditionalsPassTest.php @@ -39,7 +39,7 @@ public function testProcess() $parent = '.instanceof.'.parent::class.'.0.foo'; $def = $container->getDefinition('foo'); - $this->assertEmpty($def->getInstanceofConditionals()); + $this->assertSame([], $def->getInstanceofConditionals()); $this->assertInstanceOf(ChildDefinition::class, $def); $this->assertTrue($def->isAutowired()); $this->assertSame($parent, $def->getParent()); @@ -266,10 +266,10 @@ public function testMergeReset() $abstract = $container->getDefinition('.abstract.instanceof.bar'); - $this->assertEmpty($abstract->getArguments()); - $this->assertEmpty($abstract->getMethodCalls()); + $this->assertSame([], $abstract->getArguments()); + $this->assertSame([], $abstract->getMethodCalls()); $this->assertNull($abstract->getDecoratedService()); - $this->assertEmpty($abstract->getTags()); + $this->assertSame([], $abstract->getTags()); $this->assertTrue($abstract->isAbstract()); } @@ -376,6 +376,21 @@ public function testDecoratorsKeepBehaviorDescribingTags() ], $container->getDefinition('decorator')->getTags()); $this->assertFalse($container->hasParameter('container.behavior_describing_tags')); } + + public function testSyntheticService() + { + $container = new ContainerBuilder(); + $container->register('kernel', \stdClass::class) + ->setInstanceofConditionals([ + \stdClass::class => (new ChildDefinition('')) + ->addTag('container.excluded'), + ]) + ->setSynthetic(true); + + (new ResolveInstanceofConditionalsPass())->process($container); + + $this->assertSame([], $container->getDefinition('kernel')->getTags()); + } } class DecoratorWithBehavior implements ResetInterface, ResourceCheckerInterface, ServiceSubscriberInterface diff --git a/Tests/ContainerBuilderTest.php b/Tests/ContainerBuilderTest.php index 73be8aaed..06a20b4a4 100644 --- a/Tests/ContainerBuilderTest.php +++ b/Tests/ContainerBuilderTest.php @@ -25,6 +25,7 @@ use Symfony\Component\DependencyInjection\Argument\IteratorArgument; use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Attribute\AsTaggedItem; use Symfony\Component\DependencyInjection\ChildDefinition; use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; use Symfony\Component\DependencyInjection\Compiler\PassConfig; @@ -34,6 +35,7 @@ use Symfony\Component\DependencyInjection\Exception\BadMethodCallException; use Symfony\Component\DependencyInjection\Exception\EnvNotFoundException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; +use Symfony\Component\DependencyInjection\Exception\LogicException; use Symfony\Component\DependencyInjection\Exception\ParameterCircularReferenceException; use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; @@ -843,6 +845,36 @@ public function testMergeThrowsExceptionForDuplicateAutomaticInstanceofDefinitio $container->merge($config); } + public function testMergeAttributeAutoconfiguration() + { + $container = new ContainerBuilder(); + $container->registerAttributeForAutoconfiguration(AsTaggedItem::class, $c1 = static function (Definition $definition) {}); + $config = new ContainerBuilder(); + $config->registerAttributeForAutoconfiguration(AsTaggedItem::class, $c2 = function (Definition $definition) {}); + + $container->merge($config); + $this->assertSame([AsTaggedItem::class => [$c1, $c2]], $container->getAttributeAutoconfigurators()); + } + + /** + * @group legacy + */ + public function testGetAutoconfiguredAttributes() + { + $container = new ContainerBuilder(); + $container->registerAttributeForAutoconfiguration(AsTaggedItem::class, $c = static function () {}); + + $this->expectUserDeprecationMessage('Since symfony/dependency-injection 7.3: The "Symfony\Component\DependencyInjection\ContainerBuilder::getAutoconfiguredAttributes()" method is deprecated, use "getAttributeAutoconfigurators()" instead.'); + $configurators = $container->getAutoconfiguredAttributes(); + $this->assertSame($c, $configurators[AsTaggedItem::class]); + + // Method call fails with more than one configurator for a given attribute + $container->registerAttributeForAutoconfiguration(AsTaggedItem::class, $c = static function () {}); + + $this->expectException(LogicException::class); + $container->getAutoconfiguredAttributes(); + } + public function testResolveEnvValues() { $_ENV['DUMMY_ENV_VAR'] = 'du%%y'; @@ -1076,20 +1108,18 @@ public function testMergeLogicException() $container->merge(new ContainerBuilder()); } - public function testfindTaggedServiceIds() + public function testFindTaggedServiceIds() { $builder = new ContainerBuilder(); - $builder - ->register('foo', 'Bar\FooClass') + $builder->register('foo', 'Bar\FooClass') + ->setAbstract(true) ->addTag('foo', ['foo' => 'foo']) ->addTag('bar', ['bar' => 'bar']) - ->addTag('foo', ['foofoo' => 'foofoo']) - ; - $builder - ->register('bar', 'Bar\FooClass') + ->addTag('foo', ['foofoo' => 'foofoo']); + $builder->register('bar', 'Bar\FooClass') ->addTag('foo') - ->addTag('container.excluded') - ; + ->addTag('container.excluded'); + $this->assertEquals([ 'foo' => [ ['foo' => 'foo'], @@ -1099,6 +1129,45 @@ public function testfindTaggedServiceIds() $this->assertEquals([], $builder->findTaggedServiceIds('foobar'), '->findTaggedServiceIds() returns an empty array if there is annotated services'); } + public function testFindTaggedServiceIdsThrowsWhenAbstract() + { + $builder = new ContainerBuilder(); + $builder->register('foo', 'Bar\FooClass') + ->setAbstract(true) + ->addTag('foo', ['foo' => 'foo']); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The service "foo" tagged "foo" must not be abstract.'); + $builder->findTaggedServiceIds('foo', true); + } + + public function testFindTaggedResourceIds() + { + $builder = new ContainerBuilder(); + $builder->register('myservice', 'Bar\FooClass') + ->addTag('foo', ['foo' => 'foo']) + ->addTag('bar', ['bar' => 'bar']) + ->addTag('foo', ['foofoo' => 'foofoo']) + ->addResourceTag('container.excluded'); + + $expected = ['myservice' => [['foo' => 'foo'], ['foofoo' => 'foofoo']]]; + $this->assertSame($expected, $builder->findTaggedResourceIds('foo')); + $this->assertSame([], $builder->findTaggedResourceIds('foofoo')); + } + + public function testFindTaggedResourceIdsThrowsWhenNotExcluded() + { + $builder = new ContainerBuilder(); + $builder->register('myservice', 'Bar\FooClass') + ->addTag('foo', ['foo' => 'foo']) + ->addTag('bar', ['bar' => 'bar']) + ->addTag('foo', ['foofoo' => 'foofoo']); + + $this->expectException(InvalidArgumentException::class); + $this->expectExceptionMessage('The resource "myservice" tagged "foo" is missing the "container.excluded" tag.'); + $builder->findTaggedResourceIds('foo'); + } + public function testFindUnusedTags() { $builder = new ContainerBuilder(); @@ -1127,7 +1196,7 @@ public function testAddObjectResource() $container->setResourceTracking(false); $container->addObjectResource(new \BarClass()); - $this->assertEmpty($container->getResources(), 'No resources get registered without resource tracking'); + $this->assertSame([], $container->getResources(), 'No resources get registered without resource tracking'); $container->setResourceTracking(true); $container->addObjectResource(new \BarClass()); @@ -1136,7 +1205,7 @@ public function testAddObjectResource() $this->assertCount(1, $resources); - /* @var FileResource $resource */ + /** @var FileResource $resource */ $resource = end($resources); $this->assertInstanceOf(FileResource::class, $resource); @@ -1150,7 +1219,7 @@ public function testGetReflectionClass() $container->setResourceTracking(false); $r1 = $container->getReflectionClass('BarClass'); - $this->assertEmpty($container->getResources(), 'No resources get registered without resource tracking'); + $this->assertSame([], $container->getResources(), 'No resources get registered without resource tracking'); $container->setResourceTracking(true); $r2 = $container->getReflectionClass('BarClass'); @@ -1190,7 +1259,7 @@ public function testCompilesClassDefinitionsOfLazyServices() { $container = new ContainerBuilder(); - $this->assertEmpty($container->getResources(), 'No resources get registered without resource tracking'); + $this->assertSame([], $container->getResources(), 'No resources get registered without resource tracking'); $container->register('foo', 'BarClass')->setPublic(true); $container->getDefinition('foo')->setLazy(true); @@ -1349,7 +1418,7 @@ public function testExtensionConfig() $container = new ContainerBuilder(); $configs = $container->getExtensionConfig('foo'); - $this->assertEmpty($configs); + $this->assertSame([], $configs); $first = ['foo' => 'bar']; $container->prependExtensionConfig('foo', $first); @@ -1896,8 +1965,12 @@ public function testLazyWither() $container->compile(); $wither = $container->get('wither'); + if (\PHP_VERSION_ID >= 80400) { + $this->assertTrue((new \ReflectionClass($wither))->isUninitializedLazyObject($wither)); + } else { + $this->assertTrue($wither->resetLazyObject()); + } $this->assertInstanceOf(Foo::class, $wither->foo); - $this->assertTrue($wither->resetLazyObject()); $this->assertInstanceOf(Wither::class, $wither->withFoo1($wither->foo)); } diff --git a/Tests/DefinitionTest.php b/Tests/DefinitionTest.php index 3a7c3a980..459e566d2 100644 --- a/Tests/DefinitionTest.php +++ b/Tests/DefinitionTest.php @@ -258,6 +258,16 @@ public function testTags() ], $def->getTags(), '->getTags() returns all tags'); } + public function testAddResourceTag() + { + $def = new Definition('stdClass'); + $def->addResourceTag('foo', ['bar' => true]); + + $this->assertSame([['bar' => true]], $def->getTag('foo')); + $this->assertTrue($def->isAbstract()); + $this->assertSame([['source' => 'by tag "foo"']], $def->getTag('container.excluded')); + } + public function testSetArgument() { $def = new Definition('stdClass'); diff --git a/Tests/Dumper/PhpDumperTest.php b/Tests/Dumper/PhpDumperTest.php index 95abb7087..a117a69a0 100644 --- a/Tests/Dumper/PhpDumperTest.php +++ b/Tests/Dumper/PhpDumperTest.php @@ -340,7 +340,7 @@ public function testDumpAsFilesWithLazyFactoriesInlined() if ('\\' === \DIRECTORY_SEPARATOR) { $dump = str_replace("'.\\DIRECTORY_SEPARATOR.'", '/', $dump); } - $this->assertStringMatchesFormatFile(self::$fixturesPath.'/php/services9_lazy_inlined_factories.txt', $dump); + $this->assertStringMatchesFormatFile(self::$fixturesPath.'/php/'.(\PHP_VERSION_ID < 80400 ? 'legacy_' : '').'services9_lazy_inlined_factories.txt', $dump); } public function testServicesWithAnonymousFactories() @@ -794,7 +794,7 @@ public function testNonSharedLazy() 'inline_class_loader' => false, ]); $this->assertStringEqualsFile( - self::$fixturesPath.'/php/services_non_shared_lazy_public.php', + self::$fixturesPath.'/php/'.(\PHP_VERSION_ID < 80400 ? 'legacy_' : '').'services_non_shared_lazy_public.php', '\\' === \DIRECTORY_SEPARATOR ? str_replace("'.\\DIRECTORY_SEPARATOR.'", '/', $dump) : $dump ); eval('?>'.$dump); @@ -802,10 +802,18 @@ public function testNonSharedLazy() $container = new \Symfony_DI_PhpDumper_Service_Non_Shared_Lazy(); $foo1 = $container->get('foo'); - $this->assertTrue($foo1->resetLazyObject()); + if (\PHP_VERSION_ID >= 80400) { + $this->assertTrue((new \ReflectionClass($foo1))->isUninitializedLazyObject($foo1)); + } else { + $this->assertTrue($foo1->resetLazyObject()); + } $foo2 = $container->get('foo'); - $this->assertTrue($foo2->resetLazyObject()); + if (\PHP_VERSION_ID >= 80400) { + $this->assertTrue((new \ReflectionClass($foo2))->isUninitializedLazyObject($foo2)); + } else { + $this->assertTrue($foo2->resetLazyObject()); + } $this->assertNotSame($foo1, $foo2); } @@ -832,7 +840,7 @@ public function testNonSharedLazyAsFiles() $stringDump = print_r($dumps, true); $this->assertStringMatchesFormatFile( - self::$fixturesPath.'/php/services_non_shared_lazy_as_files.txt', + self::$fixturesPath.'/php/'.(\PHP_VERSION_ID < 80400 ? 'legacy_' : '').'services_non_shared_lazy_as_files.txt', '\\' === \DIRECTORY_SEPARATOR ? str_replace("'.\\DIRECTORY_SEPARATOR.'", '/', $stringDump) : $stringDump ); @@ -844,10 +852,18 @@ public function testNonSharedLazyAsFiles() $container = eval('?>'.$lastDump); $foo1 = $container->get('non_shared_foo'); - $this->assertTrue($foo1->resetLazyObject()); + if (\PHP_VERSION_ID >= 80400) { + $this->assertTrue((new \ReflectionClass($foo1))->isUninitializedLazyObject($foo1)); + } else { + $this->assertTrue($foo1->resetLazyObject()); + } $foo2 = $container->get('non_shared_foo'); - $this->assertTrue($foo2->resetLazyObject()); + if (\PHP_VERSION_ID >= 80400) { + $this->assertTrue((new \ReflectionClass($foo2))->isUninitializedLazyObject($foo2)); + } else { + $this->assertTrue($foo2->resetLazyObject()); + } $this->assertNotSame($foo1, $foo2); } @@ -869,7 +885,7 @@ public function testNonSharedLazyDefinitionReferences(bool $asGhostObject) $dumper->setProxyDumper(new \DummyProxyDumper()); } - $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_non_shared_lazy'.($asGhostObject ? '_ghost' : '').'.php', $dumper->dump()); + $this->assertStringEqualsFile(self::$fixturesPath.'/php/'.(\PHP_VERSION_ID < 80400 ? 'legacy_' : '').'services_non_shared_lazy'.($asGhostObject ? '_ghost' : '').'.php', $dumper->dump()); } public function testNonSharedDuplicates() @@ -942,7 +958,7 @@ public function testDedupLazyProxy() $dumper = new PhpDumper($container); - $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_dedup_lazy.php', $dumper->dump()); + $this->assertStringEqualsFile(self::$fixturesPath.'/php/'.(\PHP_VERSION_ID < 80400 ? 'legacy_' : '').'services_dedup_lazy.php', $dumper->dump()); } public function testLazyArgumentProvideGenerator() @@ -986,7 +1002,7 @@ public function testLazyArgumentProvideGenerator() } } - $this->assertEmpty(iterator_to_array($lazyContext->lazyEmptyValues)); + $this->assertSame([], iterator_to_array($lazyContext->lazyEmptyValues)); } public function testNormalizedId() @@ -1607,14 +1623,18 @@ public function testLazyWither() $container->compile(); $dumper = new PhpDumper($container); $dump = $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Service_Wither_Lazy']); - $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_wither_lazy.php', $dump); + $this->assertStringEqualsFile(self::$fixturesPath.'/php/'.(\PHP_VERSION_ID < 80400 ? 'legacy_' : '').'services_wither_lazy.php', $dump); eval('?>'.$dump); $container = new \Symfony_DI_PhpDumper_Service_Wither_Lazy(); $wither = $container->get('wither'); + if (\PHP_VERSION_ID >= 80400) { + $this->assertTrue((new \ReflectionClass($wither))->isUninitializedLazyObject($wither)); + } else { + $this->assertTrue($wither->resetLazyObject()); + } $this->assertInstanceOf(Foo::class, $wither->foo); - $this->assertTrue($wither->resetLazyObject()); } public function testLazyWitherNonShared() @@ -1632,18 +1652,26 @@ public function testLazyWitherNonShared() $container->compile(); $dumper = new PhpDumper($container); $dump = $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Service_Wither_Lazy_Non_Shared']); - $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_wither_lazy_non_shared.php', $dump); + $this->assertStringEqualsFile(self::$fixturesPath.'/php/'.(\PHP_VERSION_ID < 80400 ? 'legacy_' : '').'services_wither_lazy_non_shared.php', $dump); eval('?>'.$dump); $container = new \Symfony_DI_PhpDumper_Service_Wither_Lazy_Non_Shared(); $wither1 = $container->get('wither'); + if (\PHP_VERSION_ID >= 80400) { + $this->assertTrue((new \ReflectionClass($wither1))->isUninitializedLazyObject($wither1)); + } else { + $this->assertTrue($wither1->resetLazyObject()); + } $this->assertInstanceOf(Foo::class, $wither1->foo); - $this->assertTrue($wither1->resetLazyObject()); $wither2 = $container->get('wither'); + if (\PHP_VERSION_ID >= 80400) { + $this->assertTrue((new \ReflectionClass($wither2))->isUninitializedLazyObject($wither2)); + } else { + $this->assertTrue($wither2->resetLazyObject()); + } $this->assertInstanceOf(Foo::class, $wither2->foo); - $this->assertTrue($wither2->resetLazyObject()); $this->assertNotSame($wither1, $wither2); } @@ -1971,15 +1999,21 @@ public function testLazyAutowireAttribute() $container->compile(); $dumper = new PhpDumper($container); - $this->assertStringEqualsFile(self::$fixturesPath.'/php/lazy_autowire_attribute.php', $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_Lazy_Autowire_Attribute'])); + $this->assertStringEqualsFile(self::$fixturesPath.'/php/'.(\PHP_VERSION_ID < 80400 ? 'legacy_' : '').'lazy_autowire_attribute.php', $dumper->dump(['class' => 'Symfony_DI_PhpDumper_Test_Lazy_Autowire_Attribute'])); - require self::$fixturesPath.'/php/lazy_autowire_attribute.php'; + require self::$fixturesPath.'/php/'.(\PHP_VERSION_ID < 80400 ? 'legacy_' : '').'lazy_autowire_attribute.php'; $container = new \Symfony_DI_PhpDumper_Test_Lazy_Autowire_Attribute(); $this->assertInstanceOf(Foo::class, $container->get('bar')->foo); - $this->assertInstanceOf(LazyObjectInterface::class, $container->get('bar')->foo); - $this->assertSame($container->get('foo'), $container->get('bar')->foo->initializeLazyObject()); + if (\PHP_VERSION_ID >= 80400) { + $r = new \ReflectionClass(Foo::class); + $this->assertTrue($r->isUninitializedLazyObject($container->get('bar')->foo)); + $this->assertSame($container->get('foo'), $r->initializeLazyObject($container->get('bar')->foo)); + } else { + $this->assertInstanceOf(LazyObjectInterface::class, $container->get('bar')->foo); + $this->assertSame($container->get('foo'), $container->get('bar')->foo->initializeLazyObject()); + } } public function testLazyAutowireAttributeWithIntersection() @@ -2002,7 +2036,11 @@ public function testLazyAutowireAttributeWithIntersection() $dumper = new PhpDumper($container); - $this->assertStringEqualsFile(self::$fixturesPath.'/php/lazy_autowire_attribute_with_intersection.php', $dumper->dump()); + if (\PHP_VERSION_ID >= 80400) { + $this->assertStringEqualsFile(self::$fixturesPath.'/php/lazy_autowire_attribute_with_intersection.php', $dumper->dump()); + } else { + $this->assertStringEqualsFile(self::$fixturesPath.'/php/legacy_lazy_autowire_attribute_with_intersection.php', $dumper->dump()); + } } public function testCallableAdapterConsumer() diff --git a/Tests/Fixtures/AbstractSayClass.php b/Tests/Fixtures/AbstractSayClass.php new file mode 100644 index 000000000..f59aa1bf4 --- /dev/null +++ b/Tests/Fixtures/AbstractSayClass.php @@ -0,0 +1,17 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\Fixtures; + +abstract class AbstractSayClass +{ + abstract public function say(): string; +} diff --git a/Tests/Fixtures/Prototype/StaticConstructor/PrototypeStaticConstructor.php b/Tests/Fixtures/Prototype/StaticConstructor/PrototypeStaticConstructor.php index 87de94cb1..6ac5316a3 100644 --- a/Tests/Fixtures/Prototype/StaticConstructor/PrototypeStaticConstructor.php +++ b/Tests/Fixtures/Prototype/StaticConstructor/PrototypeStaticConstructor.php @@ -4,6 +4,10 @@ class PrototypeStaticConstructor implements PrototypeStaticConstructorInterface { + private function __construct() + { + } + public static function create(): static { return new self(); diff --git a/Tests/Fixtures/PrototypeAsAlias/WithAsAliasBothEnv.php b/Tests/Fixtures/PrototypeAsAlias/WithAsAliasBothEnv.php new file mode 100644 index 000000000..252842be3 --- /dev/null +++ b/Tests/Fixtures/PrototypeAsAlias/WithAsAliasBothEnv.php @@ -0,0 +1,10 @@ +privates['.lazy.Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo'] = $container->createProxy('FooProxyCd8d23a', static fn () => \FooProxyCd8d23a::createLazyProxy(static fn () => self::getFoo2Service($container, false))); + return $container->privates['.lazy.Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo'] = new \ReflectionClass('Symfony\Component\DependencyInjection\Tests\Compiler\Foo')->newLazyProxy(static fn () => self::getFoo2Service($container, false)); } return ($container->services['foo'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo()); } } - -class FooProxyCd8d23a extends \Symfony\Component\DependencyInjection\Tests\Compiler\Foo implements \Symfony\Component\VarExporter\LazyObjectInterface -{ - use \Symfony\Component\VarExporter\LazyProxyTrait; - - private const LAZY_OBJECT_PROPERTY_SCOPES = []; -} - -// Help opcache.preload discover always-needed symbols -class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php b/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php index fcf66ad12..15cab6e1d 100644 --- a/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php +++ b/Tests/Fixtures/php/lazy_autowire_attribute_with_intersection.php @@ -74,21 +74,16 @@ protected static function get_Lazy_Foo_QFdMZVKService($container, $lazyLoad = tr class objectProxy1fd6daa implements \Symfony\Component\DependencyInjection\Tests\Compiler\AInterface, \Symfony\Component\DependencyInjection\Tests\Compiler\IInterface, \Symfony\Component\VarExporter\LazyObjectInterface { - use \Symfony\Component\VarExporter\LazyProxyTrait; + use \Symfony\Component\VarExporter\Internal\LazyDecoratorTrait; private const LAZY_OBJECT_PROPERTY_SCOPES = []; public function initializeLazyObject(): \Symfony\Component\DependencyInjection\Tests\Compiler\AInterface&\Symfony\Component\DependencyInjection\Tests\Compiler\IInterface { - if ($state = $this->lazyObjectState ?? null) { - return $state->realInstance ??= ($state->initializer)(); - } - - return $this; + return $this->lazyObjectState->realInstance; } } // Help opcache.preload discover always-needed symbols class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/Tests/Fixtures/php/legacy_lazy_autowire_attribute.php b/Tests/Fixtures/php/legacy_lazy_autowire_attribute.php new file mode 100644 index 000000000..6cf1c86a5 --- /dev/null +++ b/Tests/Fixtures/php/legacy_lazy_autowire_attribute.php @@ -0,0 +1,99 @@ +services = $this->privates = []; + $this->methodMap = [ + 'bar' => 'getBarService', + 'foo' => 'getFooService', + ]; + + $this->aliases = []; + } + + public function compile(): void + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled(): bool + { + return true; + } + + public function getRemovedIds(): array + { + return [ + 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo' => true, + ]; + } + + protected function createProxy($class, \Closure $factory) + { + return $factory(); + } + + /** + * Gets the public 'bar' shared autowired service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Dumper\LazyServiceConsumer + */ + protected static function getBarService($container) + { + return $container->services['bar'] = new \Symfony\Component\DependencyInjection\Tests\Dumper\LazyServiceConsumer(($container->privates['.lazy.Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo'] ?? self::getFoo2Service($container))); + } + + /** + * Gets the public 'foo' shared service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Compiler\Foo + */ + protected static function getFooService($container) + { + return $container->services['foo'] = new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo(); + } + + /** + * Gets the private '.lazy.Symfony\Component\DependencyInjection\Tests\Compiler\Foo' shared service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Compiler\Foo + */ + protected static function getFoo2Service($container, $lazyLoad = true) + { + if (true === $lazyLoad) { + return $container->privates['.lazy.Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo'] = $container->createProxy('FooProxyCd8d23a', static fn () => \FooProxyCd8d23a::createLazyProxy(static fn () => self::getFoo2Service($container, false))); + } + + return ($container->services['foo'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo()); + } +} + +class FooProxyCd8d23a extends \Symfony\Component\DependencyInjection\Tests\Compiler\Foo implements \Symfony\Component\VarExporter\LazyObjectInterface +{ + use \Symfony\Component\VarExporter\LazyProxyTrait; + + private const LAZY_OBJECT_PROPERTY_SCOPES = [ + 'foo' => [parent::class, 'foo', null, 4], + ]; +} + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/Tests/Fixtures/php/legacy_lazy_autowire_attribute_with_intersection.php b/Tests/Fixtures/php/legacy_lazy_autowire_attribute_with_intersection.php new file mode 100644 index 000000000..fcf66ad12 --- /dev/null +++ b/Tests/Fixtures/php/legacy_lazy_autowire_attribute_with_intersection.php @@ -0,0 +1,94 @@ +services = $this->privates = []; + $this->methodMap = [ + 'foo' => 'getFooService', + ]; + + $this->aliases = []; + } + + public function compile(): void + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled(): bool + { + return true; + } + + protected function createProxy($class, \Closure $factory) + { + return $factory(); + } + + /** + * Gets the public 'foo' shared autowired service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Compiler\AAndIInterfaceConsumer + */ + protected static function getFooService($container) + { + $a = ($container->privates['.lazy.foo.qFdMZVK'] ?? self::get_Lazy_Foo_QFdMZVKService($container)); + + if (isset($container->services['foo'])) { + return $container->services['foo']; + } + + return $container->services['foo'] = new \Symfony\Component\DependencyInjection\Tests\Compiler\AAndIInterfaceConsumer($a); + } + + /** + * Gets the private '.lazy.foo.qFdMZVK' shared service. + * + * @return \object + */ + protected static function get_Lazy_Foo_QFdMZVKService($container, $lazyLoad = true) + { + if (true === $lazyLoad) { + return $container->privates['.lazy.foo.qFdMZVK'] = $container->createProxy('objectProxy1fd6daa', static fn () => \objectProxy1fd6daa::createLazyProxy(static fn () => self::get_Lazy_Foo_QFdMZVKService($container, false))); + } + + return ($container->services['foo'] ?? self::getFooService($container)); + } +} + +class objectProxy1fd6daa implements \Symfony\Component\DependencyInjection\Tests\Compiler\AInterface, \Symfony\Component\DependencyInjection\Tests\Compiler\IInterface, \Symfony\Component\VarExporter\LazyObjectInterface +{ + use \Symfony\Component\VarExporter\LazyProxyTrait; + + private const LAZY_OBJECT_PROPERTY_SCOPES = []; + + public function initializeLazyObject(): \Symfony\Component\DependencyInjection\Tests\Compiler\AInterface&\Symfony\Component\DependencyInjection\Tests\Compiler\IInterface + { + if ($state = $this->lazyObjectState ?? null) { + return $state->realInstance ??= ($state->initializer)(); + } + + return $this; + } +} + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/Tests/Fixtures/php/legacy_services9_lazy_inlined_factories.txt b/Tests/Fixtures/php/legacy_services9_lazy_inlined_factories.txt new file mode 100644 index 000000000..f945fdd50 --- /dev/null +++ b/Tests/Fixtures/php/legacy_services9_lazy_inlined_factories.txt @@ -0,0 +1,196 @@ +Array +( + [Container%s/proxy-classes.php] => targetDir.''.'/Fixtures/includes/foo.php'; + +class FooClassGhost1728205 extends \Bar\FooClass implements \Symfony\Component\VarExporter\LazyObjectInterface +%A + +if (!\class_exists('FooClassGhost1728205', false)) { + \class_alias(__NAMESPACE__.'\\FooClassGhost1728205', 'FooClassGhost1728205', false); +} + + [Container%s/ProjectServiceContainer.php] => targetDir = \dirname($containerDir); + $this->parameters = $this->getDefaultParameters(); + + $this->services = $this->privates = []; + $this->methodMap = [ + 'lazy_foo' => 'getLazyFooService', + ]; + + $this->aliases = []; + + $this->privates['service_container'] = static function ($container) { + include_once __DIR__.'/proxy-classes.php'; + }; + } + + public function compile(): void + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled(): bool + { + return true; + } + + protected function createProxy($class, \Closure $factory) + { + return $factory(); + } + + /** + * Gets the public 'lazy_foo' shared service. + * + * @return \Bar\FooClass + */ + protected static function getLazyFooService($container, $lazyLoad = true) + { + if (true === $lazyLoad) { + return $container->services['lazy_foo'] = $container->createProxy('FooClassGhost1728205', static fn () => \FooClassGhost1728205::createLazyGhost(static fn ($proxy) => self::getLazyFooService($container, $proxy))); + } + + include_once $container->targetDir.''.'/Fixtures/includes/foo_lazy.php'; + + return ($lazyLoad->__construct(new \Bar\FooLazyClass()) && false ?: $lazyLoad); + } + + public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null + { + if (isset($this->buildParameters[$name])) { + return $this->buildParameters[$name]; + } + + if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters))) { + throw new ParameterNotFoundException($name); + } + + if (isset($this->loadedDynamicParameters[$name])) { + $value = $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); + } else { + $value = $this->parameters[$name]; + } + + return $value; + } + + public function hasParameter(string $name): bool + { + if (isset($this->buildParameters[$name])) { + return true; + } + + return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters); + } + + public function setParameter(string $name, $value): void + { + throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); + } + + public function getParameterBag(): ParameterBagInterface + { + if (!isset($this->parameterBag)) { + $parameters = $this->parameters; + foreach ($this->loadedDynamicParameters as $name => $loaded) { + $parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); + } + foreach ($this->buildParameters as $name => $value) { + $parameters[$name] = $value; + } + $this->parameterBag = new FrozenParameterBag($parameters, []); + } + + return $this->parameterBag; + } + + private $loadedDynamicParameters = []; + private $dynamicParameters = []; + + private function getDynamicParameter(string $name) + { + throw new ParameterNotFoundException($name); + } + + protected function getDefaultParameters(): array + { + return [ + 'lazy_foo_class' => 'Bar\\FooClass', + 'container.dumper.inline_factories' => true, + 'container.dumper.inline_class_loader' => true, + ]; + } +} + + [ProjectServiceContainer.preload.php] => = 7.4 when preloading is desired + +use Symfony\Component\DependencyInjection\Dumper\Preloader; + +if (in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { + return; +} + +require dirname(__DIR__, %d).'%svendor/autoload.php'; +(require __DIR__.'/ProjectServiceContainer.php')->set(\Container%s\ProjectServiceContainer::class, null); + +$classes = []; +$classes[] = 'Bar\FooClass'; +$classes[] = 'Bar\FooLazyClass'; +$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface'; + +$preloaded = Preloader::preload($classes); + + [ProjectServiceContainer.php] => '%s', + 'container.build_id' => '%s', + 'container.build_time' => 1563381341, + 'container.runtime_mode' => \in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) ? 'web=0' : 'web=1', +], __DIR__.\DIRECTORY_SEPARATOR.'Container%s'); + +) diff --git a/Tests/Fixtures/php/legacy_services_dedup_lazy.php b/Tests/Fixtures/php/legacy_services_dedup_lazy.php new file mode 100644 index 000000000..60add492b --- /dev/null +++ b/Tests/Fixtures/php/legacy_services_dedup_lazy.php @@ -0,0 +1,126 @@ +services = $this->privates = []; + $this->methodMap = [ + 'bar' => 'getBarService', + 'baz' => 'getBazService', + 'buz' => 'getBuzService', + 'foo' => 'getFooService', + ]; + + $this->aliases = []; + } + + public function compile(): void + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled(): bool + { + return true; + } + + protected function createProxy($class, \Closure $factory) + { + return $factory(); + } + + /** + * Gets the public 'bar' shared service. + * + * @return \stdClass + */ + protected static function getBarService($container, $lazyLoad = true) + { + if (true === $lazyLoad) { + return $container->services['bar'] = $container->createProxy('stdClassGhostAa01f12', static fn () => \stdClassGhostAa01f12::createLazyGhost(static fn ($proxy) => self::getBarService($container, $proxy))); + } + + return $lazyLoad; + } + + /** + * Gets the public 'baz' shared service. + * + * @return \stdClass + */ + protected static function getBazService($container, $lazyLoad = true) + { + if (true === $lazyLoad) { + return $container->services['baz'] = $container->createProxy('stdClassProxyAa01f12', static fn () => \stdClassProxyAa01f12::createLazyProxy(static fn () => self::getBazService($container, false))); + } + + return \foo_bar(); + } + + /** + * Gets the public 'buz' shared service. + * + * @return \stdClass + */ + protected static function getBuzService($container, $lazyLoad = true) + { + if (true === $lazyLoad) { + return $container->services['buz'] = $container->createProxy('stdClassProxyAa01f12', static fn () => \stdClassProxyAa01f12::createLazyProxy(static fn () => self::getBuzService($container, false))); + } + + return \foo_bar(); + } + + /** + * Gets the public 'foo' shared service. + * + * @return \stdClass + */ + protected static function getFooService($container, $lazyLoad = true) + { + if (true === $lazyLoad) { + return $container->services['foo'] = $container->createProxy('stdClassGhostAa01f12', static fn () => \stdClassGhostAa01f12::createLazyGhost(static fn ($proxy) => self::getFooService($container, $proxy))); + } + + return $lazyLoad; + } +} + +class stdClassGhostAa01f12 extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface +{ + use \Symfony\Component\VarExporter\LazyGhostTrait; + + private const LAZY_OBJECT_PROPERTY_SCOPES = []; +} + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +class stdClassProxyAa01f12 extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface +{ + use \Symfony\Component\VarExporter\LazyProxyTrait; + + private const LAZY_OBJECT_PROPERTY_SCOPES = []; +} + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/Tests/Fixtures/php/legacy_services_non_shared_lazy.php b/Tests/Fixtures/php/legacy_services_non_shared_lazy.php new file mode 100644 index 000000000..f584bef6b --- /dev/null +++ b/Tests/Fixtures/php/legacy_services_non_shared_lazy.php @@ -0,0 +1,76 @@ +services = $this->privates = []; + $this->methodMap = [ + 'bar' => 'getBarService', + ]; + + $this->aliases = []; + } + + public function compile(): void + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled(): bool + { + return true; + } + + public function getRemovedIds(): array + { + return [ + 'foo' => true, + ]; + } + + protected function createProxy($class, \Closure $factory) + { + return $factory(); + } + + /** + * Gets the public 'bar' shared service. + * + * @return \stdClass + */ + protected static function getBarService($container) + { + return $container->services['bar'] = new \stdClass((isset($container->factories['service_container']['foo']) ? $container->factories['service_container']['foo']($container) : self::getFooService($container))); + } + + /** + * Gets the private 'foo' service. + * + * @return \stdClass + */ + protected static function getFooService($container, $lazyLoad = true) + { + $container->factories['service_container']['foo'] ??= self::getFooService(...); + + // lazy factory for stdClass + + return new \stdClass(); + } +} + +// proxy code for stdClass diff --git a/Tests/Fixtures/php/legacy_services_non_shared_lazy_as_files.txt b/Tests/Fixtures/php/legacy_services_non_shared_lazy_as_files.txt new file mode 100644 index 000000000..d52dd5a7b --- /dev/null +++ b/Tests/Fixtures/php/legacy_services_non_shared_lazy_as_files.txt @@ -0,0 +1,173 @@ +Array +( + [Container%s/getNonSharedFooService.php] => factories['non_shared_foo'] ??= fn () => self::do($container); + + if (true === $lazyLoad) { + return $container->createProxy('FooLazyClassGhost%s', static fn () => \FooLazyClassGhost%s::createLazyGhost(static fn ($proxy) => self::do($container, $proxy))); + } + + static $include = true; + + if ($include) { + include_once '%sfoo_lazy.php'; + + $include = false; + } + + return $lazyLoad; + } +} + + [Container%s/FooLazyClassGhost%s.php] => [parent::class, 'foo', null, 4], + ]; +} + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); + +if (!\class_exists('FooLazyClassGhost%s', false)) { + \class_alias(__NAMESPACE__.'\\FooLazyClassGhost%s', 'FooLazyClassGhost%s', false); +} + + [Container%s/Symfony_DI_PhpDumper_Service_Non_Shared_Lazy_As_File.php] => services = $this->privates = []; + $this->fileMap = [ + 'non_shared_foo' => 'getNonSharedFooService', + ]; + + $this->aliases = []; + } + + public function compile(): void + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled(): bool + { + return true; + } + + protected function load($file, $lazyLoad = true): mixed + { + if (class_exists($class = __NAMESPACE__.'\\'.$file, false)) { + return $class::do($this, $lazyLoad); + } + + if ('.' === $file[-4]) { + $class = substr($class, 0, -4); + } else { + $file .= '.php'; + } + + $service = require $this->containerDir.\DIRECTORY_SEPARATOR.$file; + + return class_exists($class, false) ? $class::do($this, $lazyLoad) : $service; + } + + protected function createProxy($class, \Closure $factory) + { + class_exists($class, false) || require __DIR__.'/'.$class.'.php'; + + return $factory(); + } +} + + [Symfony_DI_PhpDumper_Service_Non_Shared_Lazy_As_File.preload.php] => = 7.4 when preloading is desired + +use Symfony\Component\DependencyInjection\Dumper\Preloader; + +if (in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { + return; +} + +require '%svendor/autoload.php'; +(require __DIR__.'/Symfony_DI_PhpDumper_Service_Non_Shared_Lazy_As_File.php')->set(\Container%s\Symfony_DI_PhpDumper_Service_Non_Shared_Lazy_As_File::class, null); +require __DIR__.'/Container%s/FooLazyClassGhost%s.php'; +require __DIR__.'/Container%s/getNonSharedFooService.php'; + +$classes = []; +$classes[] = 'Bar\FooLazyClass'; +$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface'; + +$preloaded = Preloader::preload($classes); + + [Symfony_DI_PhpDumper_Service_Non_Shared_Lazy_As_File.php] => '%s', + 'container.build_id' => '%s', + 'container.build_time' => %d, + 'container.runtime_mode' => \in_array(\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) ? 'web=0' : 'web=1', +], __DIR__.\DIRECTORY_SEPARATOR.'Container%s'); + +) diff --git a/Tests/Fixtures/php/legacy_services_non_shared_lazy_ghost.php b/Tests/Fixtures/php/legacy_services_non_shared_lazy_ghost.php new file mode 100644 index 000000000..b03463295 --- /dev/null +++ b/Tests/Fixtures/php/legacy_services_non_shared_lazy_ghost.php @@ -0,0 +1,88 @@ +services = $this->privates = []; + $this->methodMap = [ + 'bar' => 'getBarService', + ]; + + $this->aliases = []; + } + + public function compile(): void + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled(): bool + { + return true; + } + + public function getRemovedIds(): array + { + return [ + 'foo' => true, + ]; + } + + protected function createProxy($class, \Closure $factory) + { + return $factory(); + } + + /** + * Gets the public 'bar' shared service. + * + * @return \stdClass + */ + protected static function getBarService($container) + { + return $container->services['bar'] = new \stdClass((isset($container->factories['service_container']['foo']) ? $container->factories['service_container']['foo']($container) : self::getFooService($container))); + } + + /** + * Gets the private 'foo' service. + * + * @return \stdClass + */ + protected static function getFooService($container, $lazyLoad = true) + { + $container->factories['service_container']['foo'] ??= self::getFooService(...); + + if (true === $lazyLoad) { + return $container->createProxy('stdClassGhostAa01f12', static fn () => \stdClassGhostAa01f12::createLazyGhost(static fn ($proxy) => self::getFooService($container, $proxy))); + } + + return $lazyLoad; + } +} + +class stdClassGhostAa01f12 extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface +{ + use \Symfony\Component\VarExporter\LazyGhostTrait; + + private const LAZY_OBJECT_PROPERTY_SCOPES = []; +} + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/Tests/Fixtures/php/legacy_services_non_shared_lazy_public.php b/Tests/Fixtures/php/legacy_services_non_shared_lazy_public.php new file mode 100644 index 000000000..0841cf192 --- /dev/null +++ b/Tests/Fixtures/php/legacy_services_non_shared_lazy_public.php @@ -0,0 +1,81 @@ +services = $this->privates = []; + $this->methodMap = [ + 'foo' => 'getFooService', + ]; + + $this->aliases = []; + } + + public function compile(): void + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled(): bool + { + return true; + } + + protected function createProxy($class, \Closure $factory) + { + return $factory(); + } + + /** + * Gets the public 'foo' service. + * + * @return \Bar\FooLazyClass + */ + protected static function getFooService($container, $lazyLoad = true) + { + $container->factories['foo'] ??= fn () => self::getFooService($container); + + if (true === $lazyLoad) { + return $container->createProxy('FooLazyClassGhost82ad1a4', static fn () => \FooLazyClassGhost82ad1a4::createLazyGhost(static fn ($proxy) => self::getFooService($container, $proxy))); + } + + static $include = true; + + if ($include) { + include_once __DIR__.'/Fixtures/includes/foo_lazy.php'; + + $include = false; + } + + return $lazyLoad; + } +} + +class FooLazyClassGhost82ad1a4 extends \Bar\FooLazyClass implements \Symfony\Component\VarExporter\LazyObjectInterface +{ + use \Symfony\Component\VarExporter\LazyGhostTrait; + + private const LAZY_OBJECT_PROPERTY_SCOPES = [ + 'foo' => [parent::class, 'foo', null, 4], + ]; +} + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/Tests/Fixtures/php/legacy_services_wither_lazy.php b/Tests/Fixtures/php/legacy_services_wither_lazy.php new file mode 100644 index 000000000..b9e916457 --- /dev/null +++ b/Tests/Fixtures/php/legacy_services_wither_lazy.php @@ -0,0 +1,86 @@ +services = $this->privates = []; + $this->methodMap = [ + 'wither' => 'getWitherService', + ]; + + $this->aliases = []; + } + + public function compile(): void + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled(): bool + { + return true; + } + + public function getRemovedIds(): array + { + return [ + 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo' => true, + ]; + } + + protected function createProxy($class, \Closure $factory) + { + return $factory(); + } + + /** + * Gets the public 'wither' shared autowired service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Compiler\Wither + */ + protected static function getWitherService($container, $lazyLoad = true) + { + if (true === $lazyLoad) { + return $container->services['wither'] = $container->createProxy('WitherProxy1991f2a', static fn () => \WitherProxy1991f2a::createLazyProxy(static fn () => self::getWitherService($container, false))); + } + + $instance = new \Symfony\Component\DependencyInjection\Tests\Compiler\Wither(); + + $a = ($container->privates['Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo()); + + $instance = $instance->withFoo1($a); + $instance = $instance->withFoo2($a); + $instance->setFoo($a); + + return $instance; + } +} + +class WitherProxy1991f2a extends \Symfony\Component\DependencyInjection\Tests\Compiler\Wither implements \Symfony\Component\VarExporter\LazyObjectInterface +{ + use \Symfony\Component\VarExporter\LazyProxyTrait; + + private const LAZY_OBJECT_PROPERTY_SCOPES = [ + 'foo' => [parent::class, 'foo', null, 4], + ]; +} + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/Tests/Fixtures/php/legacy_services_wither_lazy_non_shared.php b/Tests/Fixtures/php/legacy_services_wither_lazy_non_shared.php new file mode 100644 index 000000000..d70588f65 --- /dev/null +++ b/Tests/Fixtures/php/legacy_services_wither_lazy_non_shared.php @@ -0,0 +1,88 @@ +services = $this->privates = []; + $this->methodMap = [ + 'wither' => 'getWitherService', + ]; + + $this->aliases = []; + } + + public function compile(): void + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled(): bool + { + return true; + } + + public function getRemovedIds(): array + { + return [ + 'Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo' => true, + ]; + } + + protected function createProxy($class, \Closure $factory) + { + return $factory(); + } + + /** + * Gets the public 'wither' autowired service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Compiler\Wither + */ + protected static function getWitherService($container, $lazyLoad = true) + { + $container->factories['wither'] ??= fn () => self::getWitherService($container); + + if (true === $lazyLoad) { + return $container->createProxy('WitherProxyE94fdba', static fn () => \WitherProxyE94fdba::createLazyProxy(static fn () => self::getWitherService($container, false))); + } + + $instance = new \Symfony\Component\DependencyInjection\Tests\Compiler\Wither(); + + $a = ($container->privates['Symfony\\Component\\DependencyInjection\\Tests\\Compiler\\Foo'] ??= new \Symfony\Component\DependencyInjection\Tests\Compiler\Foo()); + + $instance = $instance->withFoo1($a); + $instance = $instance->withFoo2($a); + $instance->setFoo($a); + + return $instance; + } +} + +class WitherProxyE94fdba extends \Symfony\Component\DependencyInjection\Tests\Compiler\Wither implements \Symfony\Component\VarExporter\LazyObjectInterface +{ + use \Symfony\Component\VarExporter\LazyProxyTrait; + + private const LAZY_OBJECT_PROPERTY_SCOPES = [ + 'foo' => [parent::class, 'foo', null, 4], + ]; +} + +// Help opcache.preload discover always-needed symbols +class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); +class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/Tests/Fixtures/php/services9_lazy_inlined_factories.txt b/Tests/Fixtures/php/services9_lazy_inlined_factories.txt index f945fdd50..9e6a3865f 100644 --- a/Tests/Fixtures/php/services9_lazy_inlined_factories.txt +++ b/Tests/Fixtures/php/services9_lazy_inlined_factories.txt @@ -1,18 +1,5 @@ Array ( - [Container%s/proxy-classes.php] => targetDir.''.'/Fixtures/includes/foo.php'; - -class FooClassGhost1728205 extends \Bar\FooClass implements \Symfony\Component\VarExporter\LazyObjectInterface -%A - -if (!\class_exists('FooClassGhost1728205', false)) { - \class_alias(__NAMESPACE__.'\\FooClassGhost1728205', 'FooClassGhost1728205', false); -} - [Container%s/ProjectServiceContainer.php] => aliases = []; - - $this->privates['service_container'] = static function ($container) { - include_once __DIR__.'/proxy-classes.php'; - }; } public function compile(): void @@ -74,9 +57,10 @@ class ProjectServiceContainer extends Container protected static function getLazyFooService($container, $lazyLoad = true) { if (true === $lazyLoad) { - return $container->services['lazy_foo'] = $container->createProxy('FooClassGhost1728205', static fn () => \FooClassGhost1728205::createLazyGhost(static fn ($proxy) => self::getLazyFooService($container, $proxy))); + return $container->services['lazy_foo'] = new \ReflectionClass('Bar\FooClass')->newLazyGhost(static function ($proxy) use ($container) { self::getLazyFooService($container, $proxy); }); } + include_once $container->targetDir.''.'/Fixtures/includes/foo.php'; include_once $container->targetDir.''.'/Fixtures/includes/foo_lazy.php'; return ($lazyLoad->__construct(new \Bar\FooLazyClass()) && false ?: $lazyLoad); diff --git a/Tests/Fixtures/php/services_dedup_lazy.php b/Tests/Fixtures/php/services_dedup_lazy.php index 60add492b..2c6142d0e 100644 --- a/Tests/Fixtures/php/services_dedup_lazy.php +++ b/Tests/Fixtures/php/services_dedup_lazy.php @@ -52,7 +52,7 @@ protected function createProxy($class, \Closure $factory) protected static function getBarService($container, $lazyLoad = true) { if (true === $lazyLoad) { - return $container->services['bar'] = $container->createProxy('stdClassGhostAa01f12', static fn () => \stdClassGhostAa01f12::createLazyGhost(static fn ($proxy) => self::getBarService($container, $proxy))); + return $container->services['bar'] = new \ReflectionClass('stdClass')->newLazyGhost(static function ($proxy) use ($container) { self::getBarService($container, $proxy); }); } return $lazyLoad; @@ -94,28 +94,16 @@ protected static function getBuzService($container, $lazyLoad = true) protected static function getFooService($container, $lazyLoad = true) { if (true === $lazyLoad) { - return $container->services['foo'] = $container->createProxy('stdClassGhostAa01f12', static fn () => \stdClassGhostAa01f12::createLazyGhost(static fn ($proxy) => self::getFooService($container, $proxy))); + return $container->services['foo'] = new \ReflectionClass('stdClass')->newLazyGhost(static function ($proxy) use ($container) { self::getFooService($container, $proxy); }); } return $lazyLoad; } } -class stdClassGhostAa01f12 extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface -{ - use \Symfony\Component\VarExporter\LazyGhostTrait; - - private const LAZY_OBJECT_PROPERTY_SCOPES = []; -} - -// Help opcache.preload discover always-needed symbols -class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); - class stdClassProxyAa01f12 extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface { - use \Symfony\Component\VarExporter\LazyProxyTrait; + use \Symfony\Component\VarExporter\Internal\LazyDecoratorTrait; private const LAZY_OBJECT_PROPERTY_SCOPES = []; } @@ -123,4 +111,3 @@ class stdClassProxyAa01f12 extends \stdClass implements \Symfony\Component\VarEx // Help opcache.preload discover always-needed symbols class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt b/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt index 488895d7c..69a47220c 100644 --- a/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt +++ b/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt @@ -23,7 +23,7 @@ class getNonSharedFooService extends Symfony_DI_PhpDumper_Service_Non_Shared_Laz $container->factories['non_shared_foo'] ??= fn () => self::do($container); if (true === $lazyLoad) { - return $container->createProxy('FooLazyClassGhost%s', static fn () => \FooLazyClassGhost%s::createLazyGhost(static fn ($proxy) => self::do($container, $proxy))); + return new \ReflectionClass('Bar\FooLazyClass')->newLazyGhost(static function ($proxy) use ($container) { self::do($container, $proxy); }); } static $include = true; @@ -38,26 +38,6 @@ class getNonSharedFooService extends Symfony_DI_PhpDumper_Service_Non_Shared_Laz } } - [Container%s/FooLazyClassGhost%s.php] => set(\Container%s\Symfony_DI_PhpDumper_Service_Non_Shared_Lazy_As_File::class, null); -require __DIR__.'/Container%s/FooLazyClassGhost%s.php'; require __DIR__.'/Container%s/getNonSharedFooService.php'; $classes = []; diff --git a/Tests/Fixtures/php/services_non_shared_lazy_ghost.php b/Tests/Fixtures/php/services_non_shared_lazy_ghost.php index b03463295..281baf615 100644 --- a/Tests/Fixtures/php/services_non_shared_lazy_ghost.php +++ b/Tests/Fixtures/php/services_non_shared_lazy_ghost.php @@ -68,21 +68,9 @@ protected static function getFooService($container, $lazyLoad = true) $container->factories['service_container']['foo'] ??= self::getFooService(...); if (true === $lazyLoad) { - return $container->createProxy('stdClassGhostAa01f12', static fn () => \stdClassGhostAa01f12::createLazyGhost(static fn ($proxy) => self::getFooService($container, $proxy))); + return new \ReflectionClass('stdClass')->newLazyGhost(static function ($proxy) use ($container) { self::getFooService($container, $proxy); }); } return $lazyLoad; } } - -class stdClassGhostAa01f12 extends \stdClass implements \Symfony\Component\VarExporter\LazyObjectInterface -{ - use \Symfony\Component\VarExporter\LazyGhostTrait; - - private const LAZY_OBJECT_PROPERTY_SCOPES = []; -} - -// Help opcache.preload discover always-needed symbols -class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/Tests/Fixtures/php/services_non_shared_lazy_public.php b/Tests/Fixtures/php/services_non_shared_lazy_public.php index 7f870f886..93b7ee3ed 100644 --- a/Tests/Fixtures/php/services_non_shared_lazy_public.php +++ b/Tests/Fixtures/php/services_non_shared_lazy_public.php @@ -51,7 +51,7 @@ protected static function getFooService($container, $lazyLoad = true) $container->factories['foo'] ??= fn () => self::getFooService($container); if (true === $lazyLoad) { - return $container->createProxy('FooLazyClassGhost82ad1a4', static fn () => \FooLazyClassGhost82ad1a4::createLazyGhost(static fn ($proxy) => self::getFooService($container, $proxy))); + return new \ReflectionClass('Bar\FooLazyClass')->newLazyGhost(static function ($proxy) use ($container) { self::getFooService($container, $proxy); }); } static $include = true; @@ -65,15 +65,3 @@ protected static function getFooService($container, $lazyLoad = true) return $lazyLoad; } } - -class FooLazyClassGhost82ad1a4 extends \Bar\FooLazyClass implements \Symfony\Component\VarExporter\LazyObjectInterface -{ - use \Symfony\Component\VarExporter\LazyGhostTrait; - - private const LAZY_OBJECT_PROPERTY_SCOPES = []; -} - -// Help opcache.preload discover always-needed symbols -class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/Tests/Fixtures/php/services_wither_lazy.php b/Tests/Fixtures/php/services_wither_lazy.php index b9e916457..76031f1ca 100644 --- a/Tests/Fixtures/php/services_wither_lazy.php +++ b/Tests/Fixtures/php/services_wither_lazy.php @@ -56,7 +56,7 @@ protected function createProxy($class, \Closure $factory) protected static function getWitherService($container, $lazyLoad = true) { if (true === $lazyLoad) { - return $container->services['wither'] = $container->createProxy('WitherProxy1991f2a', static fn () => \WitherProxy1991f2a::createLazyProxy(static fn () => self::getWitherService($container, false))); + return $container->services['wither'] = new \ReflectionClass('Symfony\Component\DependencyInjection\Tests\Compiler\Wither')->newLazyProxy(static fn () => self::getWitherService($container, false)); } $instance = new \Symfony\Component\DependencyInjection\Tests\Compiler\Wither(); @@ -70,17 +70,3 @@ protected static function getWitherService($container, $lazyLoad = true) return $instance; } } - -class WitherProxy1991f2a extends \Symfony\Component\DependencyInjection\Tests\Compiler\Wither implements \Symfony\Component\VarExporter\LazyObjectInterface -{ - use \Symfony\Component\VarExporter\LazyProxyTrait; - - private const LAZY_OBJECT_PROPERTY_SCOPES = [ - 'foo' => [parent::class, 'foo', null, 4], - ]; -} - -// Help opcache.preload discover always-needed symbols -class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/Tests/Fixtures/php/services_wither_lazy_non_shared.php b/Tests/Fixtures/php/services_wither_lazy_non_shared.php index d70588f65..640955a7e 100644 --- a/Tests/Fixtures/php/services_wither_lazy_non_shared.php +++ b/Tests/Fixtures/php/services_wither_lazy_non_shared.php @@ -58,7 +58,7 @@ protected static function getWitherService($container, $lazyLoad = true) $container->factories['wither'] ??= fn () => self::getWitherService($container); if (true === $lazyLoad) { - return $container->createProxy('WitherProxyE94fdba', static fn () => \WitherProxyE94fdba::createLazyProxy(static fn () => self::getWitherService($container, false))); + return new \ReflectionClass('Symfony\Component\DependencyInjection\Tests\Compiler\Wither')->newLazyProxy(static fn () => self::getWitherService($container, false)); } $instance = new \Symfony\Component\DependencyInjection\Tests\Compiler\Wither(); @@ -72,17 +72,3 @@ protected static function getWitherService($container, $lazyLoad = true) return $instance; } } - -class WitherProxyE94fdba extends \Symfony\Component\DependencyInjection\Tests\Compiler\Wither implements \Symfony\Component\VarExporter\LazyObjectInterface -{ - use \Symfony\Component\VarExporter\LazyProxyTrait; - - private const LAZY_OBJECT_PROPERTY_SCOPES = [ - 'foo' => [parent::class, 'foo', null, 4], - ]; -} - -// Help opcache.preload discover always-needed symbols -class_exists(\Symfony\Component\VarExporter\Internal\Hydrator::class); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectRegistry::class); -class_exists(\Symfony\Component\VarExporter\Internal\LazyObjectState::class); diff --git a/Tests/Fixtures/yaml/services_with_short_service_closure.yml b/Tests/Fixtures/yaml/services_with_short_service_closure.yml new file mode 100644 index 000000000..7215e538d --- /dev/null +++ b/Tests/Fixtures/yaml/services_with_short_service_closure.yml @@ -0,0 +1,8 @@ +services: + service_container: + class: Symfony\Component\DependencyInjection\ContainerInterface + public: true + synthetic: true + foo: + class: Foo + arguments: ['@>bar'] diff --git a/Tests/LazyProxy/Instantiator/LazyServiceInstantiatorTest.php b/Tests/LazyProxy/Instantiator/LazyServiceInstantiatorTest.php new file mode 100644 index 000000000..359b9824b --- /dev/null +++ b/Tests/LazyProxy/Instantiator/LazyServiceInstantiatorTest.php @@ -0,0 +1,45 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\DependencyInjection\Tests\LazyProxy\Instantiator; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\LazyProxy\Instantiator\LazyServiceInstantiator; +use Symfony\Component\DependencyInjection\Tests\Fixtures\AbstractSayClass; + +class LazyServiceInstantiatorTest extends TestCase +{ + public function testInstantiateAbstractClassProxy() + { + $instantiator = new LazyServiceInstantiator(); + $instance = new class extends AbstractSayClass { + public int $calls = 0; + + public function say(): string + { + ++$this->calls; + + return 'Hello from the abstract class!'; + } + }; + + $definition = (new Definition(AbstractSayClass::class)) + ->setLazy(true); + + $proxy = $instantiator->instantiateProxy(new Container(), $definition, 'foo', fn () => $instance); + + $this->assertSame(0, $instance->calls); + $this->assertSame('Hello from the abstract class!', $proxy->say()); + $this->assertSame(1, $instance->calls); + } +} diff --git a/Tests/LazyProxy/PhpDumper/LazyServiceDumperTest.php b/Tests/LazyProxy/PhpDumper/LazyServiceDumperTest.php index 467972a88..1d5e9b6bf 100644 --- a/Tests/LazyProxy/PhpDumper/LazyServiceDumperTest.php +++ b/Tests/LazyProxy/PhpDumper/LazyServiceDumperTest.php @@ -16,6 +16,7 @@ use Symfony\Component\DependencyInjection\Definition; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper; +use Symfony\Component\DependencyInjection\Tests\Fixtures\AbstractSayClass; use Symfony\Component\DependencyInjection\Tests\Fixtures\ReadOnlyClass; class LazyServiceDumperTest extends TestCase @@ -40,6 +41,16 @@ public function testFinalClassInterface() $this->assertStringContainsString('function get(', $dumper->getProxyCode($definition)); } + public function testAbstractClass() + { + $dumper = new LazyServiceDumper(); + $definition = (new Definition(AbstractSayClass::class)) + ->setLazy(true); + + $this->assertTrue($dumper->isProxyCandidate($definition)); + $this->assertNotSame(AbstractSayClass::class, $dumper->getProxyClass($definition, false)); + } + public function testInvalidClass() { $dumper = new LazyServiceDumper(); @@ -63,7 +74,7 @@ public function testReadonlyClass() $definition = (new Definition(ReadOnlyClass::class))->setLazy(true); $this->assertTrue($dumper->isProxyCandidate($definition)); - $this->assertStringContainsString('readonly class ReadOnlyClassGhost', $dumper->getProxyCode($definition)); + $this->assertStringContainsString(\PHP_VERSION_ID >= 80400 ? '' : 'readonly class ReadOnlyClassGhost', $dumper->getProxyCode($definition)); } } diff --git a/Tests/Loader/FileLoaderTest.php b/Tests/Loader/FileLoaderTest.php index a195f2a93..0ad1b363c 100644 --- a/Tests/Loader/FileLoaderTest.php +++ b/Tests/Loader/FileLoaderTest.php @@ -33,14 +33,18 @@ use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\AnotherSub; use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\AnotherSub\DeeperBaz; use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\OtherDir\Baz; +use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\StaticConstructor\PrototypeStaticConstructor; use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\Bar; use Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\Sub\BarInterface; use Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\AliasBarInterface; use Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\AliasFooInterface; use Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\WithAsAlias; +use Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\WithAsAliasBothEnv; +use Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\WithAsAliasDevEnv; use Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\WithAsAliasIdMultipleInterface; use Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\WithAsAliasInterface; use Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\WithAsAliasMultiple; +use Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\WithAsAliasProdEnv; use Symfony\Component\DependencyInjection\Tests\Fixtures\Utils\NotAService; class FileLoaderTest extends TestCase @@ -348,10 +352,10 @@ public function testRegisterThrowsWithBothWhenAndNotWhenAttribute() /** * @dataProvider provideResourcesWithAsAliasAttributes */ - public function testRegisterClassesWithAsAlias(string $resource, array $expectedAliases) + public function testRegisterClassesWithAsAlias(string $resource, array $expectedAliases, ?string $env = null) { $container = new ContainerBuilder(); - $loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures')); + $loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures'), $env); $loader->registerClasses( (new Definition())->setAutoconfigured(true), 'Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\\', @@ -373,6 +377,15 @@ public static function provideResourcesWithAsAliasAttributes(): iterable AliasBarInterface::class => new Alias(WithAsAliasIdMultipleInterface::class), AliasFooInterface::class => new Alias(WithAsAliasIdMultipleInterface::class), ]]; + yield 'Dev-env specific' => ['PrototypeAsAlias/WithAsAlias*Env.php', [ + AliasFooInterface::class => new Alias(WithAsAliasDevEnv::class), + AliasBarInterface::class => new Alias(WithAsAliasBothEnv::class), + ], 'dev']; + yield 'Prod-env specific' => ['PrototypeAsAlias/WithAsAlias*Env.php', [ + AliasFooInterface::class => new Alias(WithAsAliasProdEnv::class), + AliasBarInterface::class => new Alias(WithAsAliasBothEnv::class), + ], 'prod']; + yield 'Test-env specific' => ['PrototypeAsAlias/WithAsAlias*Env.php', [], 'test']; } /** @@ -380,11 +393,11 @@ public static function provideResourcesWithAsAliasAttributes(): iterable */ public function testRegisterClassesWithDuplicatedAsAlias(string $resource, string $expectedExceptionMessage) { - $this->expectException(LogicException::class); - $this->expectExceptionMessage($expectedExceptionMessage); - $container = new ContainerBuilder(); $loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures')); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage($expectedExceptionMessage); $loader->registerClasses( (new Definition())->setAutoconfigured(true), 'Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\\', @@ -400,17 +413,28 @@ public static function provideResourcesWithDuplicatedAsAliasAttributes(): iterab public function testRegisterClassesWithAsAliasAndImplementingMultipleInterfaces() { - $this->expectException(LogicException::class); - $this->expectExceptionMessage('Alias cannot be automatically determined for class "Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\WithAsAliasMultipleInterface". If you have used the #[AsAlias] attribute with a class implementing multiple interfaces, add the interface you want to alias to the first parameter of #[AsAlias].'); - $container = new ContainerBuilder(); $loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures')); + + $this->expectException(LogicException::class); + $this->expectExceptionMessage('Alias cannot be automatically determined for class "Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\WithAsAliasMultipleInterface". If you have used the #[AsAlias] attribute with a class implementing multiple interfaces, add the interface you want to alias to the first parameter of #[AsAlias].'); $loader->registerClasses( (new Definition())->setAutoconfigured(true), 'Symfony\Component\DependencyInjection\Tests\Fixtures\PrototypeAsAlias\\', 'PrototypeAsAlias/{WithAsAliasMultipleInterface,AliasBarInterface,AliasFooInterface}.php' ); } + + public function testRegisterClassesWithStaticConstructor() + { + $container = new ContainerBuilder(); + $loader = new TestFileLoader($container, new FileLocator(self::$fixturesPath.'/Fixtures')); + + $prototype = (new Definition())->setAutoconfigured(true); + $loader->registerClasses($prototype, 'Symfony\Component\DependencyInjection\Tests\Fixtures\Prototype\StaticConstructor\\', 'Prototype/StaticConstructor'); + + $this->assertTrue($container->has(PrototypeStaticConstructor::class)); + } } class TestFileLoader extends FileLoader diff --git a/Tests/Loader/XmlFileLoaderTest.php b/Tests/Loader/XmlFileLoaderTest.php index 1b4361411..f962fa106 100644 --- a/Tests/Loader/XmlFileLoaderTest.php +++ b/Tests/Loader/XmlFileLoaderTest.php @@ -1280,7 +1280,7 @@ public function testStaticConstructor() public function testStaticConstructorWithFactoryThrows() { $container = new ContainerBuilder(); - $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath . '/xml')); + $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); $this->expectException(LogicException::class); $this->expectExceptionMessage('The "static_constructor" service cannot declare a factory as well as a constructor.'); @@ -1341,7 +1341,7 @@ public function testUnknownConstantAsKey() public function testDeprecatedTagged() { $container = new ContainerBuilder(); - $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath . '/xml')); + $loader = new XmlFileLoader($container, new FileLocator(self::$fixturesPath.'/xml')); $this->expectUserDeprecationMessage(\sprintf('Since symfony/dependency-injection 7.2: Type "tagged" is deprecated for tag , use "tagged_iterator" instead in "%s/xml%sservices_with_deprecated_tagged.xml".', self::$fixturesPath, \DIRECTORY_SEPARATOR)); diff --git a/Tests/Loader/YamlFileLoaderTest.php b/Tests/Loader/YamlFileLoaderTest.php index 8da59796e..54900e4c3 100644 --- a/Tests/Loader/YamlFileLoaderTest.php +++ b/Tests/Loader/YamlFileLoaderTest.php @@ -466,6 +466,15 @@ public function testParseServiceClosure() $this->assertEquals(new ServiceClosureArgument(new Reference('bar', ContainerInterface::IGNORE_ON_INVALID_REFERENCE)), $container->getDefinition('foo')->getArgument(0)); } + public function testParseShortServiceClosure() + { + $container = new ContainerBuilder(); + $loader = new YamlFileLoader($container, new FileLocator(self::$fixturesPath.'/yaml')); + $loader->load('services_with_short_service_closure.yml'); + + $this->assertEquals(new ServiceClosureArgument(new Reference('bar')), $container->getDefinition('foo')->getArgument(0)); + } + public function testNameOnlyTagsAreAllowedAsString() { $container = new ContainerBuilder(); @@ -841,7 +850,7 @@ public function testAnonymousServicesInInstanceof() $anonymous = $container->getDefinition((string) $args['foo']); $this->assertEquals('Anonymous', $anonymous->getClass()); $this->assertFalse($anonymous->isPublic()); - $this->assertEmpty($anonymous->getInstanceofConditionals()); + $this->assertSame([], $anonymous->getInstanceofConditionals()); $this->assertFalse($container->has('Bar')); } diff --git a/Tests/ParameterBag/EnvPlaceholderParameterBagTest.php b/Tests/ParameterBag/EnvPlaceholderParameterBagTest.php index ee0af9ff3..b6779b450 100644 --- a/Tests/ParameterBag/EnvPlaceholderParameterBagTest.php +++ b/Tests/ParameterBag/EnvPlaceholderParameterBagTest.php @@ -66,7 +66,7 @@ public function testMergeWhereFirstBagIsEmptyWillWork() // initialize placeholder only in second bag $secondBag->get($parameter); - $this->assertEmpty($firstBag->getEnvPlaceholders()); + $this->assertSame([], $firstBag->getEnvPlaceholders()); $firstBag->mergeEnvPlaceholders($secondBag); $mergedPlaceholders = $firstBag->getEnvPlaceholders(); pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy