diff --git a/CHANGELOG-4.0.md b/CHANGELOG-4.0.md index fc1f989d485f7..f1c98b1874d2a 100644 --- a/CHANGELOG-4.0.md +++ b/CHANGELOG-4.0.md @@ -7,6 +7,32 @@ in 4.0 minor versions. To get the diff for a specific change, go to https://github.com/symfony/symfony/commit/XXX where XXX is the change hash To get the diff between two versions, go to https://github.com/symfony/symfony/compare/v4.0.0...v4.0.1 +* 4.0.0 (2017-11-30) + + * bug #25220 [HttpFoundation] Add Session::isEmpty(), fix MockFileSessionStorage to behave like the native one (nicolas-grekas) + * bug #25209 [VarDumper] Dont use empty(), it chokes on eg GMP objects (nicolas-grekas) + * bug #25200 [HttpKernel] Arrays with scalar values passed to ESI fragment renderer throw deprecation notice (Simperfit) + * bug #25201 [HttpKernel] Add a better error messages when passing a private or non-tagged controller (Simperfit) + * bug #25155 [DependencyInjection] Detect case mismatch in autowiring (Simperfit, sroze) + * bug #25217 [Dotenv] Changed preg_match flags from null to 0 (deekthesqueak) + * bug #25180 [DI] Fix circular reference when using setters (nicolas-grekas) + * bug #25204 [DI] Clear service reference graph (nicolas-grekas) + * bug #25203 [DI] Fix infinite loop in InlineServiceDefinitionsPass (nicolas-grekas) + * bug #25185 [Serializer] Do not cache attributes if `attributes` in context (sroze) + * bug #25190 [HttpKernel] Keep legacy container files for concurrent requests (nicolas-grekas) + * bug #25182 [HttpFoundation] AutExpireFlashBag should not clear new flashes (Simperfit, sroze) + * bug #25174 [Translation] modify definitions only if the do exist (xabbuh) + * bug #25179 [FrameworkBundle][Serializer] Remove YamlEncoder definition if Yaml component isn't installed (ogizanagi) + * bug #25160 [DI] Prevent a ReflectionException during cache:clear when the parent class doesn't exist (dunglas) + * bug #25163 [DI] Fix tracking of env vars in exceptions (nicolas-grekas) + * bug #25162 [HttpKernel] Read $_ENV when checking SHELL_VERBOSITY (nicolas-grekas) + * bug #25158 [DI] Remove unreachable code (GawainLynch) + * bug #25152 [Form] Don't rely on `Symfony\Component\HttpFoundation\File\File` if http-foundation isn't in FileType (issei-m) + * bug #24987 [Console] Fix global console flag when used in chain (Simperfit) + * bug #25137 Adding checks for the expression language (weaverryan) + * bug #25151 [FrameworkBundle] Automatically enable the CSRF protection if CSRF manager exists (sroze) + * bug #25043 [Yaml] added ability for substitute aliases when mapping is on single line (MichaĆ Strzelecki, xabbuh) + * 4.0.0-RC2 (2017-11-24) * bug #25146 [DI] Dont resolve envs in service ids (nicolas-grekas) diff --git a/UPGRADE-4.0.md b/UPGRADE-4.0.md index 80451f5d61a4c..96c652705aa81 100644 --- a/UPGRADE-4.0.md +++ b/UPGRADE-4.0.md @@ -598,12 +598,6 @@ HttpKernel tags: ['console.command'] ``` - * Removed the `kernel.root_dir` parameter. Use the `kernel.project_dir` parameter - instead. - - * Removed the `Kernel::getRootDir()` method. Use the `Kernel::getProjectDir()` - method instead. - * The `Extension::addClassesToCompile()` and `Extension::getClassesToCompile()` methods have been removed. * Possibility to pass non-scalar values as URI attributes to the ESI and SSI diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php index 5414790df4963..0f82e9c9e139f 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php @@ -21,6 +21,7 @@ use Symfony\Component\Form\Form; use Symfony\Component\Lock\Lock; use Symfony\Component\Lock\Store\SemaphoreStore; +use Symfony\Component\Security\Csrf\CsrfTokenManagerInterface; use Symfony\Component\Serializer\Serializer; use Symfony\Component\Translation\Translator; use Symfony\Component\Validator\Validation; @@ -109,7 +110,7 @@ private function addCsrfSection(ArrayNodeDefinition $rootNode) $rootNode ->children() ->arrayNode('csrf_protection') - ->canBeEnabled() + ->{!class_exists(FullStack::class) && class_exists(CsrfTokenManagerInterface::class) ? 'canBeDisabled' : 'canBeEnabled'}() ->end() ->end() ; diff --git a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php index 4874b1bba3d2c..ec9010a16e576 100644 --- a/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php +++ b/src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php @@ -74,6 +74,7 @@ use Symfony\Component\WebLink\HttpHeaderSerializer; use Symfony\Component\Workflow; use Symfony\Component\Yaml\Command\LintCommand as BaseYamlLintCommand; +use Symfony\Component\Yaml\Yaml; /** * FrameworkExtension. @@ -1159,6 +1160,10 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder $container->removeDefinition('serializer.normalizer.object'); } + if (!class_exists(Yaml::class)) { + $container->removeDefinition('serializer.encoder.yaml'); + } + $serializerLoaders = array(); if (isset($config['enable_annotations']) && $config['enable_annotations']) { if (!$this->annotationsConfigEnabled) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php index 2cb1ba8f7d67b..539306fcea2b9 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Kernel/MicroKernelTraitTest.php @@ -18,7 +18,7 @@ class MicroKernelTraitTest extends TestCase { public function test() { - $kernel = new ConcreteMicroKernel('test', true); + $kernel = new ConcreteMicroKernel('test', false); $kernel->boot(); $request = Request::create('/'); @@ -31,7 +31,7 @@ public function test() public function testAsEventSubscriber() { - $kernel = new ConcreteMicroKernel('test', true); + $kernel = new ConcreteMicroKernel('test', false); $kernel->boot(); $request = Request::create('/danger'); diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php index d1b7980faa8a6..26fa75572cd9e 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/AbstractFactory.php @@ -29,7 +29,7 @@ abstract class AbstractFactory implements SecurityFactoryInterface protected $options = array( 'check_path' => '/login_check', 'use_forward' => false, - 'require_previous_session' => true, + 'require_previous_session' => false, ); protected $defaultSuccessHandlerOptions = array( diff --git a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php index 28a7bf5743078..5a391ffacaeab 100644 --- a/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php +++ b/src/Symfony/Bundle/SecurityBundle/DependencyInjection/Security/Factory/JsonLoginFactory.php @@ -28,7 +28,6 @@ public function __construct() $this->addOption('password_path', 'password'); $this->defaultFailureHandlerOptions = array(); $this->defaultSuccessHandlerOptions = array(); - $this->options['require_previous_session'] = false; } /** diff --git a/src/Symfony/Component/Console/Input/ArgvInput.php b/src/Symfony/Component/Console/Input/ArgvInput.php index 4441296b7f1a7..df12d4d42ebb1 100644 --- a/src/Symfony/Component/Console/Input/ArgvInput.php +++ b/src/Symfony/Component/Console/Input/ArgvInput.php @@ -280,6 +280,14 @@ public function hasParameterOption($values, $onlyParams = false) if ($token === $value || 0 === strpos($token, $value.'=')) { return true; } + + if (0 === strpos($token, '-') && 0 !== strpos($token, '--')) { + $searchableToken = str_replace('-', '', $token); + $searchableValue = str_replace('-', '', $value); + if ('' !== $searchableToken && '' !== $searchableValue && false !== strpos($searchableToken, $searchableValue)) { + return true; + } + } } } diff --git a/src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php b/src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php index 8287bce521d37..b9b42b9af4c3f 100644 --- a/src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php +++ b/src/Symfony/Component/Console/Tests/Input/ArgvInputTest.php @@ -314,6 +314,9 @@ public function testHasParameterOption() $input = new ArgvInput(array('cli.php', '-f', 'foo')); $this->assertTrue($input->hasParameterOption('-f'), '->hasParameterOption() returns true if the given short option is in the raw input'); + $input = new ArgvInput(array('cli.php', '-fh')); + $this->assertTrue($input->hasParameterOption('-fh'), '->hasParameterOption() returns true if the given short option is in the raw input'); + $input = new ArgvInput(array('cli.php', '--foo', 'foo')); $this->assertTrue($input->hasParameterOption('--foo'), '->hasParameterOption() returns true if the given short option is in the raw input'); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php index afda55c94f21b..638669aa57646 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/AutowirePass.php @@ -337,7 +337,10 @@ private function createTypeAlternatives(TypedReference $reference) return ' '.$message; } - if (isset($this->ambiguousServiceTypes[$type])) { + $servicesAndAliases = $this->container->getServiceIds(); + if (!$this->container->has($type) && false !== $key = array_search(strtolower($type), array_map('strtolower', $servicesAndAliases))) { + return sprintf(' Did you mean "%s"?', $servicesAndAliases[$key]); + } elseif (isset($this->ambiguousServiceTypes[$type])) { $message = sprintf('one of these existing services: "%s"', implode('", "', $this->ambiguousServiceTypes[$type])); } elseif (isset($this->types[$type])) { $message = sprintf('the existing "%s" service', $this->types[$type]); diff --git a/src/Symfony/Component/DependencyInjection/Compiler/Compiler.php b/src/Symfony/Component/DependencyInjection/Compiler/Compiler.php index abc6205a969b6..7c797a92b3d54 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/Compiler.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/Compiler.php @@ -113,6 +113,8 @@ public function compile(ContainerBuilder $container) } throw $e; + } finally { + $this->getServiceReferenceGraph()->clear(); } } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php index 76f2e502170de..8fdbd8e2b5681 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/InlineServiceDefinitionsPass.php @@ -47,10 +47,7 @@ protected function processValue($value, $isRoot = false) if ($this->isInlineableDefinition($id, $definition, $this->container->getCompiler()->getServiceReferenceGraph())) { $this->container->log($this, sprintf('Inlined service "%s" to "%s".', $id, $this->currentId)); - if ($definition->isShared()) { - return $definition; - } - $value = clone $definition; + return $definition->isShared() ? $definition : clone $definition; } } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php b/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php index b6fee88f4aeef..e077529b59185 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/MergeExtensionConfigurationPass.php @@ -56,18 +56,26 @@ public function process(ContainerBuilder $container) } $config = $resolvingBag->resolveValue($config); - $tmpContainer = new MergeExtensionConfigurationContainerBuilder($extension, $resolvingBag); - $tmpContainer->setResourceTracking($container->isTrackingResources()); - $tmpContainer->addObjectResource($extension); - if ($extension instanceof ConfigurationExtensionInterface && null !== $configuration = $extension->getConfiguration($config, $tmpContainer)) { - $tmpContainer->addObjectResource($configuration); - } + try { + $tmpContainer = new MergeExtensionConfigurationContainerBuilder($extension, $resolvingBag); + $tmpContainer->setResourceTracking($container->isTrackingResources()); + $tmpContainer->addObjectResource($extension); + if ($extension instanceof ConfigurationExtensionInterface && null !== $configuration = $extension->getConfiguration($config, $tmpContainer)) { + $tmpContainer->addObjectResource($configuration); + } - foreach ($exprLangProviders as $provider) { - $tmpContainer->addExpressionLanguageProvider($provider); - } + foreach ($exprLangProviders as $provider) { + $tmpContainer->addExpressionLanguageProvider($provider); + } - $extension->load($config, $tmpContainer); + $extension->load($config, $tmpContainer); + } catch (\Exception $e) { + if ($resolvingBag instanceof MergeExtensionConfigurationParameterBag) { + $container->getParameterBag()->mergeEnvPlaceholders($resolvingBag); + } + + throw $e; + } if ($resolvingBag instanceof MergeExtensionConfigurationParameterBag) { // don't keep track of env vars that are *overridden* when configs are merged diff --git a/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php b/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php index 8c81452b315a6..f8dba86a0b547 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/RegisterServiceSubscribersPass.php @@ -74,9 +74,6 @@ protected function processValue($value, $isRoot = false) if ($optionalBehavior = '?' === $type[0]) { $type = substr($type, 1); $optionalBehavior = ContainerInterface::IGNORE_ON_INVALID_REFERENCE; - } elseif ($optionalBehavior = '!' === $type[0]) { - $type = substr($type, 1); - $optionalBehavior = ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE; } if (is_int($key)) { $key = $type; diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php index 0474541f3d761..5a370398408dc 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraph.php @@ -64,6 +64,9 @@ public function getNodes(): array */ public function clear() { + foreach ($this->nodes as $node) { + $node->clear(); + } $this->nodes = array(); } diff --git a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php index d655dba69a6a7..b7274c425b707 100644 --- a/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php +++ b/src/Symfony/Component/DependencyInjection/Compiler/ServiceReferenceGraphNode.php @@ -107,4 +107,12 @@ public function getValue() { return $this->value; } + + /** + * Clears all edges. + */ + public function clear() + { + $this->inEdges = $this->outEdges = array(); + } } diff --git a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php index 3707b163d0dcd..7ee66c05f015b 100644 --- a/src/Symfony/Component/DependencyInjection/ContainerBuilder.php +++ b/src/Symfony/Component/DependencyInjection/ContainerBuilder.php @@ -519,6 +519,14 @@ public function has($id) */ public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE) { + return $this->doGet($id, $invalidBehavior); + } + + private function doGet($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE, array &$inlineServices = array()) + { + if (isset($inlineServices[$id])) { + return $inlineServices[$id]; + } if (ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $invalidBehavior) { return parent::get($id, $invalidBehavior); } @@ -527,7 +535,7 @@ public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INV } if (!isset($this->definitions[$id]) && isset($this->aliasDefinitions[$id])) { - return $this->get((string) $this->aliasDefinitions[$id], $invalidBehavior); + return $this->doGet((string) $this->aliasDefinitions[$id], $invalidBehavior, $inlineServices); } try { @@ -544,7 +552,7 @@ public function get($id, $invalidBehavior = ContainerInterface::EXCEPTION_ON_INV $this->{$loading}[$id] = true; try { - $service = $this->createService($definition, new \SplObjectStorage(), $id); + $service = $this->createService($definition, $inlineServices, $id); } finally { unset($this->{$loading}[$id]); } @@ -978,10 +986,10 @@ public function findDefinition($id) * @throws RuntimeException When the service is a synthetic service * @throws InvalidArgumentException When configure callable is not callable */ - private function createService(Definition $definition, \SplObjectStorage $inlinedDefinitions, $id = null, $tryProxy = true) + private function createService(Definition $definition, array &$inlineServices, $id = null, $tryProxy = true) { - if (null === $id && isset($inlinedDefinitions[$definition])) { - return $inlinedDefinitions[$definition]; + if (null === $id && isset($inlineServices[$h = spl_object_hash($definition)])) { + return $inlineServices[$h]; } if ($definition instanceof ChildDefinition) { @@ -1002,11 +1010,11 @@ private function createService(Definition $definition, \SplObjectStorage $inline ->instantiateProxy( $this, $definition, - $id, function () use ($definition, $inlinedDefinitions, $id) { - return $this->createService($definition, $inlinedDefinitions, $id, false); + $id, function () use ($definition, &$inlineServices, $id) { + return $this->createService($definition, $inlineServices, $id, false); } ); - $this->shareService($definition, $proxy, $id, $inlinedDefinitions); + $this->shareService($definition, $proxy, $id, $inlineServices); return $proxy; } @@ -1017,7 +1025,7 @@ private function createService(Definition $definition, \SplObjectStorage $inline require_once $parameterBag->resolveValue($definition->getFile()); } - $arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())), $inlinedDefinitions); + $arguments = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getArguments())), $inlineServices); if (null !== $id && $definition->isShared() && isset($this->services[$id]) && ($tryProxy || !$definition->isLazy())) { return $this->services[$id]; @@ -1025,7 +1033,7 @@ private function createService(Definition $definition, \SplObjectStorage $inline if (null !== $factory = $definition->getFactory()) { if (is_array($factory)) { - $factory = array($this->doResolveServices($parameterBag->resolveValue($factory[0]), $inlinedDefinitions), $factory[1]); + $factory = array($this->doResolveServices($parameterBag->resolveValue($factory[0]), $inlineServices), $factory[1]); } elseif (!is_string($factory)) { throw new RuntimeException(sprintf('Cannot create service "%s" because of invalid factory', $id)); } @@ -1051,16 +1059,16 @@ private function createService(Definition $definition, \SplObjectStorage $inline if ($tryProxy || !$definition->isLazy()) { // share only if proxying failed, or if not a proxy - $this->shareService($definition, $service, $id, $inlinedDefinitions); + $this->shareService($definition, $service, $id, $inlineServices); } - $properties = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties())), $inlinedDefinitions); + $properties = $this->doResolveServices($parameterBag->unescapeValue($parameterBag->resolveValue($definition->getProperties())), $inlineServices); foreach ($properties as $name => $value) { $service->$name = $value; } foreach ($definition->getMethodCalls() as $call) { - $this->callMethod($service, $call, $inlinedDefinitions); + $this->callMethod($service, $call, $inlineServices); } if ($callable = $definition->getConfigurator()) { @@ -1068,9 +1076,9 @@ private function createService(Definition $definition, \SplObjectStorage $inline $callable[0] = $parameterBag->resolveValue($callable[0]); if ($callable[0] instanceof Reference) { - $callable[0] = $this->get((string) $callable[0], $callable[0]->getInvalidBehavior()); + $callable[0] = $this->doGet((string) $callable[0], $callable[0]->getInvalidBehavior(), $inlineServices); } elseif ($callable[0] instanceof Definition) { - $callable[0] = $this->createService($callable[0], $inlinedDefinitions); + $callable[0] = $this->createService($callable[0], $inlineServices); } } @@ -1094,14 +1102,14 @@ private function createService(Definition $definition, \SplObjectStorage $inline */ public function resolveServices($value) { - return $this->doResolveServices($value, new \SplObjectStorage()); + return $this->doResolveServices($value); } - private function doResolveServices($value, \SplObjectStorage $inlinedDefinitions) + private function doResolveServices($value, array &$inlineServices = array()) { if (is_array($value)) { foreach ($value as $k => $v) { - $value[$k] = $this->doResolveServices($v, $inlinedDefinitions); + $value[$k] = $this->doResolveServices($v, $inlineServices); } } elseif ($value instanceof ServiceClosureArgument) { $reference = $value->getValues()[0]; @@ -1117,7 +1125,7 @@ private function doResolveServices($value, \SplObjectStorage $inlinedDefinitions } } foreach (self::getInitializedConditionals($v) as $s) { - if (!$this->get($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) { + if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) { continue 2; } } @@ -1133,7 +1141,7 @@ private function doResolveServices($value, \SplObjectStorage $inlinedDefinitions } } foreach (self::getInitializedConditionals($v) as $s) { - if (!$this->get($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) { + if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) { continue 2; } } @@ -1144,9 +1152,9 @@ private function doResolveServices($value, \SplObjectStorage $inlinedDefinitions return $count; }); } elseif ($value instanceof Reference) { - $value = $this->get((string) $value, $value->getInvalidBehavior()); + $value = $this->doGet((string) $value, $value->getInvalidBehavior(), $inlineServices); } elseif ($value instanceof Definition) { - $value = $this->createService($value, $inlinedDefinitions); + $value = $this->createService($value, $inlineServices); } elseif ($value instanceof Expression) { $value = $this->getExpressionLanguage()->evaluate($value, array('container' => $this)); } @@ -1443,7 +1451,7 @@ private function getProxyInstantiator(): InstantiatorInterface return $this->proxyInstantiator; } - private function callMethod($service, $call, \SplObjectStorage $inlinedDefinitions) + private function callMethod($service, $call, array &$inlineServices) { foreach (self::getServiceConditionals($call[1]) as $s) { if (!$this->has($s)) { @@ -1451,12 +1459,12 @@ private function callMethod($service, $call, \SplObjectStorage $inlinedDefinitio } } foreach (self::getInitializedConditionals($call[1]) as $s) { - if (!$this->get($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE)) { + if (!$this->doGet($s, ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE, $inlineServices)) { return; } } - call_user_func_array(array($service, $call[0]), $this->doResolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])), $inlinedDefinitions)); + call_user_func_array(array($service, $call[0]), $this->doResolveServices($this->getParameterBag()->unescapeValue($this->getParameterBag()->resolveValue($call[1])), $inlineServices)); } /** @@ -1466,14 +1474,11 @@ private function callMethod($service, $call, \SplObjectStorage $inlinedDefinitio * @param object $service * @param string|null $id */ - private function shareService(Definition $definition, $service, $id, \SplObjectStorage $inlinedDefinitions) + private function shareService(Definition $definition, $service, $id, array &$inlineServices) { - if (!$definition->isShared()) { - return; - } - if (null === $id) { - $inlinedDefinitions[$definition] = $service; - } else { + $inlineServices[null !== $id ? $id : spl_object_hash($definition)] = $service; + + if (null !== $id && $definition->isShared()) { $this->services[$id] = $service; unset($this->loading[$id], $this->alreadyLoading[$id]); } diff --git a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php index baf9613d841ad..f6204169bd96f 100644 --- a/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php +++ b/src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php @@ -52,7 +52,6 @@ class PhpDumper extends Dumper */ const NON_FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_'; - private $inlinedDefinitions; private $definitionVariables; private $referenceVariables; private $variableCount; @@ -85,8 +84,6 @@ public function __construct(ContainerBuilder $container) } parent::__construct($container); - - $this->inlinedDefinitions = new \SplObjectStorage(); } /** @@ -143,6 +140,7 @@ public function dump(array $options = array()) $currentPath = array($id => $id); $this->analyzeCircularReferences($node->getOutEdges(), $checkedNodes, $currentPath); } + $this->container->getCompiler()->getServiceReferenceGraph()->clear(); $this->docStar = $options['debug'] ? '*' : ''; @@ -272,54 +270,46 @@ private function getProxyDumper(): ProxyDumper return $this->proxyDumper; } - private function addServiceLocalTempVariables(string $cId, Definition $definition, array $inlinedDefinitions): string + private function addServiceLocalTempVariables(string $cId, Definition $definition, \SplObjectStorage $inlinedDefinitions, \SplObjectStorage $allInlinedDefinitions): string { - static $template = " \$%s = %s;\n"; + $allCalls = $calls = $behavior = array(); - array_unshift($inlinedDefinitions, $definition); + foreach ($allInlinedDefinitions as $def) { + $arguments = array($def->getArguments(), $def->getFactory(), $def->getProperties(), $def->getMethodCalls(), $def->getConfigurator()); + $this->getServiceCallsFromArguments($arguments, $allCalls, false, $cId, $behavior, $allInlinedDefinitions[$def]); + } - $collectLineage = $this->inlineRequires && !$this->isHotPath($definition); - $isNonLazyShared = isset($this->circularReferences[$cId]) && !$this->getProxyDumper()->isProxyCandidate($definition) && $definition->isShared(); - $lineage = $calls = $behavior = array(); - foreach ($inlinedDefinitions as $iDefinition) { - if ($collectLineage && !$iDefinition->isDeprecated() && $class = is_array($factory = $iDefinition->getFactory()) && is_string($factory[0]) ? $factory[0] : $iDefinition->getClass()) { - $this->collectLineage($class, $lineage); + $isPreInstance = isset($inlinedDefinitions[$definition]) && isset($this->circularReferences[$cId]) && !$this->getProxyDumper()->isProxyCandidate($definition) && $definition->isShared(); + foreach ($inlinedDefinitions as $def) { + $this->getServiceCallsFromArguments(array($def->getArguments(), $def->getFactory()), $calls, $isPreInstance, $cId); + if ($def !== $definition) { + $arguments = array($def->getProperties(), $def->getMethodCalls(), $def->getConfigurator()); + $this->getServiceCallsFromArguments($arguments, $calls, $isPreInstance && !$this->hasReference($cId, $arguments, true), $cId); } - $this->getServiceCallsFromArguments($iDefinition->getArguments(), $calls, $behavior, $isNonLazyShared, $cId); - $isPreInstantiation = $isNonLazyShared && $iDefinition !== $definition && !$this->hasReference($cId, $iDefinition->getMethodCalls(), true) && !$this->hasReference($cId, $iDefinition->getProperties(), true); - $this->getServiceCallsFromArguments($iDefinition->getMethodCalls(), $calls, $behavior, $isPreInstantiation, $cId); - $this->getServiceCallsFromArguments($iDefinition->getProperties(), $calls, $behavior, $isPreInstantiation, $cId); - $this->getServiceCallsFromArguments(array($iDefinition->getConfigurator()), $calls, $behavior, $isPreInstantiation, $cId); - $this->getServiceCallsFromArguments(array($iDefinition->getFactory()), $calls, $behavior, $isNonLazyShared, $cId); + } + if (!isset($inlinedDefinitions[$definition])) { + $arguments = array($definition->getProperties(), $definition->getMethodCalls(), $definition->getConfigurator()); + $this->getServiceCallsFromArguments($arguments, $calls, false, $cId); } $code = ''; foreach ($calls as $id => $callCount) { - if ('service_container' === $id || $id === $cId) { + if ('service_container' === $id || $id === $cId || isset($this->referenceVariables[$id])) { continue; } - - if ($collectLineage && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $behavior[$id] && $this->container->has($id) - && $this->isTrivialInstance($iDefinition = $this->container->findDefinition($id)) - && $class = is_array($factory = $iDefinition->getFactory()) && is_string($factory[0]) ? $factory[0] : $iDefinition->getClass() - ) { - $this->collectLineage($class, $lineage); + if ($callCount <= 1 && $allCalls[$id] <= 1) { + continue; } - if ($callCount > 1) { - $name = $this->getNextVariableName(); - $this->referenceVariables[$id] = new Variable($name); + $name = $this->getNextVariableName(); + $this->referenceVariables[$id] = new Variable($name); - if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $behavior[$id]) { - $code .= sprintf($template, $name, $this->getServiceCall($id)); - } else { - $code .= sprintf($template, $name, $this->getServiceCall($id, new Reference($id, $behavior[$id]))); - } - } + $reference = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $behavior[$id] ? new Reference($id, $behavior[$id]) : null; + $code .= sprintf(" \$%s = %s;\n", $name, $this->getServiceCall($id, $reference)); } if ('' !== $code) { - if ($isNonLazyShared) { + if ($isPreInstance) { $code .= sprintf(<<<'EOTXT' if (isset($this->%s['%s'])) { @@ -333,14 +323,6 @@ private function addServiceLocalTempVariables(string $cId, Definition $definitio $code .= "\n"; } - if ($lineage && $lineage = array_diff_key(array_flip($lineage), $this->inlinedRequires)) { - $code = "\n".$code; - - foreach (array_reverse($lineage) as $file => $class) { - $code = sprintf(" require_once %s;\n", $file).$code; - } - } - return $code; } @@ -350,11 +332,6 @@ private function analyzeCircularReferences(array $edges, &$checkedNodes, &$curre $node = $edge->getDestNode(); $id = $node->getId(); - if (isset($checkedNodes[$id])) { - continue; - } - $checkedNodes[$id] = true; - if ($node->getValue() && ($edge->isLazy() || $edge->isWeak())) { // no-op } elseif (isset($currentPath[$id])) { @@ -362,10 +339,11 @@ private function analyzeCircularReferences(array $edges, &$checkedNodes, &$curre $this->circularReferences[$parentId][$id] = $id; $id = $parentId; } - } else { + } elseif (!isset($checkedNodes[$id])) { + $checkedNodes[$id] = true; $currentPath[$id] = $id; $this->analyzeCircularReferences($node->getOutEdges(), $checkedNodes, $currentPath); - array_pop($currentPath); + unset($currentPath[$id]); } } } @@ -375,7 +353,7 @@ private function collectLineage($class, array &$lineage) if (isset($lineage[$class])) { return; } - if (!$r = $this->container->getReflectionClass($class)) { + if (!$r = $this->container->getReflectionClass($class, false)) { return; } if ($this->container instanceof $class) { @@ -420,18 +398,39 @@ private function generateProxyClasses() } } - private function addServiceInclude(Definition $definition, array $inlinedDefinitions): string + private function addServiceInclude(string $cId, Definition $definition, \SplObjectStorage $inlinedDefinitions): string { - $template = " require_once %s;\n"; $code = ''; - if (null !== $file = $definition->getFile()) { - $code .= sprintf($template, $this->dumpValue($file)); + if ($this->inlineRequires && !$this->isHotPath($definition)) { + $lineage = $calls = $behavior = array(); + foreach ($inlinedDefinitions as $def) { + if (!$def->isDeprecated() && $class = is_array($factory = $def->getFactory()) && is_string($factory[0]) ? $factory[0] : $def->getClass()) { + $this->collectLineage($class, $lineage); + } + $arguments = array($def->getArguments(), $def->getFactory(), $def->getProperties(), $def->getMethodCalls(), $def->getConfigurator()); + $this->getServiceCallsFromArguments($arguments, $calls, false, $cId, $behavior, $inlinedDefinitions[$def]); + } + + foreach ($calls as $id => $callCount) { + if ('service_container' !== $id && $id !== $cId + && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $behavior[$id] + && $this->container->has($id) + && $this->isTrivialInstance($def = $this->container->findDefinition($id)) + && $class = is_array($factory = $def->getFactory()) && is_string($factory[0]) ? $factory[0] : $def->getClass() + ) { + $this->collectLineage($class, $lineage); + } + } + + foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) { + $code .= sprintf(" require_once %s;\n", $file); + } } - foreach ($inlinedDefinitions as $definition) { - if (null !== $file = $definition->getFile()) { - $code .= sprintf($template, $this->dumpValue($file)); + foreach ($inlinedDefinitions as $def) { + if ($file = $def->getFile()) { + $code .= sprintf(" require_once %s;\n", $this->dumpValue($file)); } } @@ -448,54 +447,46 @@ private function addServiceInclude(Definition $definition, array $inlinedDefinit * @throws RuntimeException When the factory definition is incomplete * @throws ServiceCircularReferenceException When a circular reference is detected */ - private function addServiceInlinedDefinitions(string $id, array $inlinedDefinitions): string + private function addServiceInlinedDefinitions(string $id, Definition $definition, \SplObjectStorage $inlinedDefinitions, bool &$isSimpleInstance): string { $code = ''; - $variableMap = $this->definitionVariables; - $nbOccurrences = new \SplObjectStorage(); - $processed = new \SplObjectStorage(); - foreach ($inlinedDefinitions as $definition) { - if (false === $nbOccurrences->contains($definition)) { - $nbOccurrences->offsetSet($definition, 1); - } else { - $i = $nbOccurrences->offsetGet($definition); - $nbOccurrences->offsetSet($definition, $i + 1); + foreach ($inlinedDefinitions as $def) { + if ($definition === $def) { + continue; } - } - - foreach ($inlinedDefinitions as $sDefinition) { - if ($processed->contains($sDefinition)) { + if ($inlinedDefinitions[$def] <= 1 && !$def->getMethodCalls() && !$def->getProperties() && !$def->getConfigurator() && false === strpos($this->dumpValue($def->getClass()), '$')) { continue; } - $processed->offsetSet($sDefinition); - - $class = $this->dumpValue($sDefinition->getClass()); - if ($nbOccurrences->offsetGet($sDefinition) > 1 || $sDefinition->getMethodCalls() || $sDefinition->getProperties() || null !== $sDefinition->getConfigurator() || false !== strpos($class, '$')) { + if (isset($this->definitionVariables[$def])) { + $name = $this->definitionVariables[$def]; + } else { $name = $this->getNextVariableName(); - $variableMap->offsetSet($sDefinition, new Variable($name)); - - // a construct like: - // $a = new ServiceA(ServiceB $b); $b = new ServiceB(ServiceA $a); - // this is an indication for a wrong implementation, you can circumvent this problem - // by setting up your service structure like this: - // $b = new ServiceB(); - // $a = new ServiceA(ServiceB $b); - // $b->setServiceA(ServiceA $a); - if ($this->hasReference($id, $sDefinition->getArguments())) { - throw new ServiceCircularReferenceException($id, array($id)); - } + $this->definitionVariables[$def] = new Variable($name); + } - $code .= $this->addNewInstance($sDefinition, '$'.$name, ' = ', $id); + // a construct like: + // $a = new ServiceA(ServiceB $b); $b = new ServiceB(ServiceA $a); + // this is an indication for a wrong implementation, you can circumvent this problem + // by setting up your service structure like this: + // $b = new ServiceB(); + // $a = new ServiceA(ServiceB $b); + // $b->setServiceA(ServiceA $a); + if ($this->hasReference($id, array($def->getArguments(), $def->getFactory()))) { + throw new ServiceCircularReferenceException($id, array($id)); + } - if (!$this->hasReference($id, $sDefinition->getMethodCalls(), true) && !$this->hasReference($id, $sDefinition->getProperties(), true)) { - $code .= $this->addServiceProperties($sDefinition, $name); - $code .= $this->addServiceMethodCalls($sDefinition, $name); - $code .= $this->addServiceConfigurator($sDefinition, $name); - } + $code .= $this->addNewInstance($def, '$'.$name, ' = ', $id); - $code .= "\n"; + if (!$this->hasReference($id, array($def->getProperties(), $def->getMethodCalls(), $def->getConfigurator()), true)) { + $code .= $this->addServiceProperties($def, $name); + $code .= $this->addServiceMethodCalls($def, $name); + $code .= $this->addServiceConfigurator($def, $name); + } else { + $isSimpleInstance = false; } + + $code .= "\n"; } return $code; @@ -538,21 +529,6 @@ private function addServiceInstance(string $id, Definition $definition, string $ return $code; } - private function isSimpleInstance(string $id, Definition $definition, array $inlinedDefinitions): bool - { - foreach (array_merge(array($definition), $inlinedDefinitions) as $sDefinition) { - if ($definition !== $sDefinition && !$this->hasReference($id, $sDefinition->getMethodCalls())) { - continue; - } - - if ($sDefinition->getMethodCalls() || $sDefinition->getProperties() || $sDefinition->getConfigurator()) { - return false; - } - } - - return true; - } - private function isTrivialInstance(Definition $definition): bool { if ($definition->isSynthetic() || $definition->getFile() || $definition->getMethodCalls() || $definition->getProperties() || $definition->getConfigurator()) { @@ -623,19 +599,13 @@ private function addServiceProperties(Definition $definition, $variableName = 'i /** * @throws ServiceCircularReferenceException when the container contains a circular reference */ - private function addServiceInlinedDefinitionsSetup(string $id, array $inlinedDefinitions, bool $isSimpleInstance): string + private function addServiceInlinedDefinitionsSetup(string $id, Definition $definition, \SplObjectStorage $inlinedDefinitions, bool $isSimpleInstance): string { $this->referenceVariables[$id] = new Variable('instance'); $code = ''; - $processed = new \SplObjectStorage(); - foreach ($inlinedDefinitions as $iDefinition) { - if ($processed->contains($iDefinition)) { - continue; - } - $processed->offsetSet($iDefinition); - - if (!$this->hasReference($id, $iDefinition->getMethodCalls(), true) && !$this->hasReference($id, $iDefinition->getProperties(), true)) { + foreach ($inlinedDefinitions as $def) { + if ($definition === $def || !$this->hasReference($id, array($def->getProperties(), $def->getMethodCalls(), $def->getConfigurator()), true)) { continue; } @@ -645,13 +615,13 @@ private function addServiceInlinedDefinitionsSetup(string $id, array $inlinedDef throw new ServiceCircularReferenceException($id, array($id)); } - $name = (string) $this->definitionVariables->offsetGet($iDefinition); - $code .= $this->addServiceProperties($iDefinition, $name); - $code .= $this->addServiceMethodCalls($iDefinition, $name); - $code .= $this->addServiceConfigurator($iDefinition, $name); + $name = (string) $this->definitionVariables[$def]; + $code .= $this->addServiceProperties($def, $name); + $code .= $this->addServiceMethodCalls($def, $name); + $code .= $this->addServiceConfigurator($def, $name); } - if ('' !== $code) { + if ('' !== $code && ($definition->getProperties() || $definition->getMethodCalls() || $definition->getConfigurator())) { $code .= "\n"; } @@ -759,15 +729,28 @@ protected function {$methodName}($lazyInitialization) $code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", $this->export($definition->getDeprecationMessage($id))); } - $inlinedDefinitions = $this->getInlinedDefinitions($definition); - $isSimpleInstance = $this->isSimpleInstance($id, $definition, $inlinedDefinitions); + $inlinedDefinitions = $this->getDefinitionsFromArguments(array($definition)); + $constructorDefinitions = $this->getDefinitionsFromArguments(array($definition->getArguments(), $definition->getFactory())); + $otherDefinitions = new \SplObjectStorage(); + + foreach ($inlinedDefinitions as $def) { + if ($def === $definition || isset($constructorDefinitions[$def])) { + $constructorDefinitions[$def] = $inlinedDefinitions[$def]; + } else { + $otherDefinitions[$def] = $inlinedDefinitions[$def]; + } + } + + $isSimpleInstance = !$definition->getProperties() && !$definition->getMethodCalls() && !$definition->getConfigurator(); $code .= - $this->addServiceInclude($definition, $inlinedDefinitions). - $this->addServiceLocalTempVariables($id, $definition, $inlinedDefinitions). - $this->addServiceInlinedDefinitions($id, $inlinedDefinitions). + $this->addServiceInclude($id, $definition, $inlinedDefinitions). + $this->addServiceLocalTempVariables($id, $definition, $constructorDefinitions, $inlinedDefinitions). + $this->addServiceInlinedDefinitions($id, $definition, $constructorDefinitions, $isSimpleInstance). $this->addServiceInstance($id, $definition, $isSimpleInstance). - $this->addServiceInlinedDefinitionsSetup($id, $inlinedDefinitions, $isSimpleInstance). + $this->addServiceLocalTempVariables($id, $definition, $otherDefinitions, $inlinedDefinitions). + $this->addServiceInlinedDefinitions($id, $definition, $otherDefinitions, $isSimpleInstance). + $this->addServiceInlinedDefinitionsSetup($id, $definition, $inlinedDefinitions, $isSimpleInstance). $this->addServiceProperties($definition). $this->addServiceMethodCalls($definition). $this->addServiceConfigurator($definition). @@ -1085,11 +1068,10 @@ private function addInlineRequires() :string foreach ($this->container->findTaggedServiceIds($this->hotPathTag) as $id => $tags) { $definition = $this->container->getDefinition($id); - $inlinedDefinitions = $this->getInlinedDefinitions($definition); - array_unshift($inlinedDefinitions, $definition); + $inlinedDefinitions = $this->getDefinitionsFromArguments(array($definition)); - foreach ($inlinedDefinitions as $iDefinition) { - if ($class = is_array($factory = $iDefinition->getFactory()) && is_string($factory[0]) ? $factory[0] : $iDefinition->getClass()) { + foreach ($inlinedDefinitions as $def) { + if ($class = is_array($factory = $def->getFactory()) && is_string($factory[0]) ? $factory[0] : $def->getClass()) { $this->collectLineage($class, $lineage); } } @@ -1298,16 +1280,16 @@ private function getServiceConditionals($value): string return implode(' && ', $conditions); } - private function getServiceCallsFromArguments(array $arguments, array &$calls, array &$behavior, bool $isPreInstantiation, string $callerId) + private function getServiceCallsFromArguments(array $arguments, array &$calls, bool $isPreInstance, string $callerId, array &$behavior = array(), int $step = 1) { foreach ($arguments as $argument) { if (is_array($argument)) { - $this->getServiceCallsFromArguments($argument, $calls, $behavior, $isPreInstantiation, $callerId); + $this->getServiceCallsFromArguments($argument, $calls, $isPreInstance, $callerId, $behavior, $step); } elseif ($argument instanceof Reference) { $id = (string) $argument; if (!isset($calls[$id])) { - $calls[$id] = (int) ($isPreInstantiation && isset($this->circularReferences[$callerId][$id])); + $calls[$id] = (int) ($isPreInstance && isset($this->circularReferences[$callerId][$id])); } if (!isset($behavior[$id])) { $behavior[$id] = $argument->getInvalidBehavior(); @@ -1315,42 +1297,35 @@ private function getServiceCallsFromArguments(array $arguments, array &$calls, a $behavior[$id] = min($behavior[$id], $argument->getInvalidBehavior()); } - ++$calls[$id]; + $calls[$id] += $step; } } } - private function getInlinedDefinitions(Definition $definition): array + private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage $definitions = null): \SplObjectStorage { - if (false === $this->inlinedDefinitions->contains($definition)) { - $definitions = array_merge( - $this->getDefinitionsFromArguments($definition->getArguments()), - $this->getDefinitionsFromArguments($definition->getMethodCalls()), - $this->getDefinitionsFromArguments($definition->getProperties()), - $this->getDefinitionsFromArguments(array($definition->getConfigurator())), - $this->getDefinitionsFromArguments(array($definition->getFactory())) - ); - - $this->inlinedDefinitions->offsetSet($definition, $definitions); - - return $definitions; + if (null === $definitions) { + $definitions = new \SplObjectStorage(); } - return $this->inlinedDefinitions->offsetGet($definition); - } - - private function getDefinitionsFromArguments(array $arguments): array - { - $definitions = array(); foreach ($arguments as $argument) { if (is_array($argument)) { - $definitions = array_merge($definitions, $this->getDefinitionsFromArguments($argument)); - } elseif ($argument instanceof Definition) { - $definitions = array_merge( - $definitions, - $this->getInlinedDefinitions($argument), - array($argument) - ); + $this->getDefinitionsFromArguments($argument, $definitions); + } elseif (!$argument instanceof Definition) { + // no-op + } elseif (isset($definitions[$argument])) { + $definitions[$argument] = 1 + $definitions[$argument]; + } else { + $definitions[$argument] = 1; + $this->getDefinitionsFromArguments($argument->getArguments(), $definitions); + $this->getDefinitionsFromArguments(array($argument->getFactory()), $definitions); + $this->getDefinitionsFromArguments($argument->getProperties(), $definitions); + $this->getDefinitionsFromArguments($argument->getMethodCalls(), $definitions); + $this->getDefinitionsFromArguments(array($argument->getConfigurator()), $definitions); + // move current definition last in the list + $nbOccurences = $definitions[$argument]; + unset($definitions[$argument]); + $definitions[$argument] = $nbOccurences; } } @@ -1395,9 +1370,7 @@ private function hasReference(string $id, array $arguments, bool $deep = false, continue; } - $arguments = array_merge($service->getMethodCalls(), $service->getArguments(), $service->getProperties()); - - if ($this->hasReference($id, $arguments, $deep, $visited)) { + if ($this->hasReference($id, array($service->getArguments(), $service->getFactory(), $service->getProperties(), $service->getMethodCalls(), $service->getConfigurator()), $deep, $visited)) { return true; } } @@ -1473,7 +1446,7 @@ private function dumpValue($value, bool $interpolate = true): string } } elseif ($value instanceof Definition) { if (null !== $this->definitionVariables && $this->definitionVariables->contains($value)) { - return $this->dumpValue($this->definitionVariables->offsetGet($value), $interpolate); + return $this->dumpValue($this->definitionVariables[$value], $interpolate); } if ($value->getMethodCalls()) { throw new RuntimeException('Cannot dump definitions which have method calls.'); diff --git a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php index a7e6ed4bf042a..26bed7d91e602 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/XmlFileLoader.php @@ -488,6 +488,10 @@ private function getArgumentsAsPhp(\DOMElement $node, $name, $file, $lowercase = $arguments[$key] = new Reference($arg->getAttribute('id'), $invalidBehavior); break; case 'expression': + if (!class_exists(Expression::class)) { + throw new \LogicException(sprintf('The type="expression" attribute cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".')); + } + $arguments[$key] = new Expression($arg->nodeValue); break; case 'collection': diff --git a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php index 346531bc6a952..2e225aa05d627 100644 --- a/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php +++ b/src/Symfony/Component/DependencyInjection/Loader/YamlFileLoader.php @@ -727,6 +727,10 @@ private function resolveServices($value, $file, $isParameter = false) $value[$k] = $this->resolveServices($v, $file, $isParameter); } } elseif (is_string($value) && 0 === strpos($value, '@=')) { + if (!class_exists(Expression::class)) { + throw new \LogicException(sprintf('The "@=" expression syntax cannot be used without the ExpressionLanguage component. Try running "composer require symfony/expression-language".')); + } + return new Expression(substr($value, 2)); } elseif (is_string($value) && 0 === strpos($value, '@')) { if (0 === strpos($value, '@@')) { diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php index 250965b898e83..906341034f726 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/AutowirePassTest.php @@ -685,6 +685,24 @@ public function provideNotWireableCalls() ); } + /** + * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException + * @expectedExceptionMessage Cannot autowire service "foo": argument "$sam" of method "Symfony\Component\DependencyInjection\Tests\Compiler\NotWireable::setNotAutowireableBecauseOfATypo()" references class "Symfony\Component\DependencyInjection\Tests\Compiler\lesTilleuls" but no such service exists. Did you mean "Symfony\Component\DependencyInjection\Tests\Compiler\LesTilleuls"? + */ + public function testSuggestRegisteredServicesWithSimilarCase() + { + $container = new ContainerBuilder(); + + $container->register(LesTilleuls::class, LesTilleuls::class); + $container->register('foo', NotWireable::class)->setAutowired(true) + ->addMethodCall('setNotAutowireableBecauseOfATypo', array()) + ; + + (new ResolveClassPass())->process($container); + (new AutowireRequiredMethodsPass())->process($container); + (new AutowirePass())->process($container); + } + /** * @expectedException \Symfony\Component\DependencyInjection\Exception\AutowiringFailedException * @expectedExceptionMessage Cannot autowire service "j": argument "$i" of method "Symfony\Component\DependencyInjection\Tests\Compiler\J::__construct()" references class "Symfony\Component\DependencyInjection\Tests\Compiler\I" but no such service exists. Try changing the type-hint to "Symfony\Component\DependencyInjection\Tests\Compiler\IInterface" instead. diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php index 3e1cf8a8c2b10..9630cca14f015 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/InlineServiceDefinitionsPassTest.php @@ -92,6 +92,25 @@ public function testProcessDoesInlineNonSharedService() $this->assertNotSame($container->getDefinition('bar'), $arguments[2]); } + public function testProcessInlinesMixedServicesLoop() + { + $container = new ContainerBuilder(); + $container + ->register('foo') + ->addArgument(new Reference('bar')) + ->setShared(false) + ; + $container + ->register('bar') + ->setPublic(false) + ->addMethodCall('setFoo', array(new Reference('foo'))) + ; + + $this->process($container); + + $this->assertEquals($container->getDefinition('foo')->getArgument(0), $container->getDefinition('bar')); + } + public function testProcessInlinesIfMultipleReferencesButAllFromTheSameDefinition() { $container = new ContainerBuilder(); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Compiler/MergeExtensionConfigurationPassTest.php b/src/Symfony/Component/DependencyInjection/Tests/Compiler/MergeExtensionConfigurationPassTest.php index b64aa7778cbe8..fccda7e129847 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Compiler/MergeExtensionConfigurationPassTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Compiler/MergeExtensionConfigurationPassTest.php @@ -110,10 +110,26 @@ public function testProcessedEnvsAreIncompatibleWithResolve() { $container = new ContainerBuilder(); $container->registerExtension(new BarExtension()); - $container->prependExtensionConfig('bar', array()); + $container->prependExtensionConfig('bar', []); (new MergeExtensionConfigurationPass())->process($container); } + + public function testThrowingExtensionsGetMergedBag() + { + $container = new ContainerBuilder(); + $container->registerExtension(new ThrowingExtension()); + $container->prependExtensionConfig('throwing', array('bar' => '%env(FOO)%')); + + try { + $pass = new MergeExtensionConfigurationPass(); + $pass->process($container); + $this->fail('An exception should have been thrown.'); + } catch (\Exception $e) { + } + + $this->assertSame(array('FOO'), array_keys($container->getParameterBag()->getEnvPlaceholders())); + } } class FooConfiguration implements ConfigurationInterface @@ -163,3 +179,21 @@ public function load(array $configs, ContainerBuilder $container) $container->resolveEnvPlaceholders('%env(int:FOO)%', true); } } + +class ThrowingExtension extends Extension +{ + public function getAlias() + { + return 'throwing'; + } + + public function getConfiguration(array $config, ContainerBuilder $container) + { + return new FooConfiguration(); + } + + public function load(array $configs, ContainerBuilder $container) + { + throw new \Exception(); + } +} diff --git a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php index 5bcb915bc139c..1a52ebf5f9b22 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/ContainerBuilderTest.php @@ -1223,24 +1223,26 @@ public function testUninitializedReference() $this->assertEquals(array('foo1' => new \stdClass(), 'foo3' => new \stdClass()), iterator_to_array($bar->iter)); } - public function testAlmostCircularPrivate() + /** + * @dataProvider provideAlmostCircular + */ + public function testAlmostCircular($visibility) { - $public = false; $container = include __DIR__.'/Fixtures/containers/container_almost_circular.php'; $foo = $container->get('foo'); - $this->assertSame($foo, $foo->bar->foobar->foo); - } - public function testAlmostCircularPublic() - { - $public = true; - $container = include __DIR__.'/Fixtures/containers/container_almost_circular.php'; + $foo2 = $container->get('foo2'); + $this->assertSame($foo2, $foo2->bar->foobar->foo); - $foo = $container->get('foo'); + $this->assertSame(array(), (array) $container->get('foobar4')); + } - $this->assertSame($foo, $foo->bar->foobar->foo); + public function provideAlmostCircular() + { + yield array('public'); + yield array('private'); } public function testRegisterForAutoconfiguration() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php index e1a5a28f159fd..8dadbb8fbfd01 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Dumper/PhpDumperTest.php @@ -301,21 +301,6 @@ public function testOverrideServiceWhenUsingADumpedContainer() $this->assertSame($decorator, $container->get('decorator_service'), '->set() overrides an already defined service'); } - /** - * @expectedException \Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException - */ - public function testCircularReference() - { - $container = new ContainerBuilder(); - $container->register('foo', 'stdClass')->addArgument(new Reference('bar'))->setPublic(true); - $container->register('bar', 'stdClass')->setPublic(false)->addMethodCall('setA', array(new Reference('baz'))); - $container->register('baz', 'stdClass')->addMethodCall('setA', array(new Reference('foo')))->setPublic(true); - $container->compile(); - - $dumper = new PhpDumper($container); - $dumper->dump(); - } - public function testDumpAutowireData() { $container = include self::$fixturesPath.'/containers/container24.php'; @@ -773,38 +758,35 @@ public function testUninitializedReference() $this->assertEquals(array('foo1' => new \stdClass(), 'foo3' => new \stdClass()), iterator_to_array($bar->iter)); } - public function testAlmostCircularPrivate() + /** + * @dataProvider provideAlmostCircular + */ + public function testAlmostCircular($visibility) { - $public = false; $container = include self::$fixturesPath.'/containers/container_almost_circular.php'; $container->compile(); $dumper = new PhpDumper($container); - $this->assertStringEqualsFile(self::$fixturesPath.'/php/container_almost_circular_private.php', $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_Almost_Circular_Private'))); + $container = 'Symfony_DI_PhpDumper_Test_Almost_Circular_'.ucfirst($visibility); + $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_almost_circular_'.$visibility.'.php', $dumper->dump(array('class' => $container))); - require self::$fixturesPath.'/php/container_almost_circular_private.php'; + require self::$fixturesPath.'/php/services_almost_circular_'.$visibility.'.php'; - $container = new \Symfony_DI_PhpDumper_Test_Almost_Circular_Private(); - $foo = $container->get('foo'); + $container = new $container(); + $foo = $container->get('foo'); $this->assertSame($foo, $foo->bar->foobar->foo); - } - public function testAlmostCircularPublic() - { - $public = true; - $container = include self::$fixturesPath.'/containers/container_almost_circular.php'; - $container->compile(); - $dumper = new PhpDumper($container); + $foo2 = $container->get('foo2'); + $this->assertSame($foo2, $foo2->bar->foobar->foo); - $this->assertStringEqualsFile(self::$fixturesPath.'/php/container_almost_circular_public.php', $dumper->dump(array('class' => 'Symfony_DI_PhpDumper_Test_Almost_Circular_Public'))); - - require self::$fixturesPath.'/php/container_almost_circular_public.php'; - - $container = new \Symfony_DI_PhpDumper_Test_Almost_Circular_Public(); - $foo = $container->get('foo'); + $this->assertSame(array(), (array) $container->get('foobar4')); + } - $this->assertSame($foo, $foo->bar->foobar->foo); + public function provideAlmostCircular() + { + yield array('public'); + yield array('private'); } public function testHotPathOptimizations() @@ -814,12 +796,12 @@ public function testHotPathOptimizations() $container->compile(); $dumper = new PhpDumper($container); - $dump = $dumper->dump(array('hot_path_tag' => 'container.hot_path', 'inline_class_loader_parameter' => 'inline_requires', 'file' => self::$fixturesPath.'/php/container_inline_requires.php')); + $dump = $dumper->dump(array('hot_path_tag' => 'container.hot_path', 'inline_class_loader_parameter' => 'inline_requires', 'file' => self::$fixturesPath.'/php/services_inline_requires.php')); if ('\\' === DIRECTORY_SEPARATOR) { $dump = str_replace("'\\\\includes\\\\HotPath\\\\", "'/includes/HotPath/", $dump); } - $this->assertStringEqualsFile(self::$fixturesPath.'/php/container_inline_requires.php', $dump); + $this->assertStringEqualsFile(self::$fixturesPath.'/php/services_inline_requires.php', $dump); } public function testDumpHandlesLiteralClassWithRootNamespace() diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/ParentNotExists.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/ParentNotExists.php new file mode 100644 index 0000000000000..ae637f917fef0 --- /dev/null +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/ParentNotExists.php @@ -0,0 +1,7 @@ +register('foo', FooCircular::class)->setPublic(true) ->addArgument(new Reference('bar')); @@ -16,4 +19,31 @@ $container->register('foobar', FoobarCircular::class)->setPublic($public) ->addArgument(new Reference('foo')); +// mixed visibility for deps + +$container->register('foo2', FooCircular::class)->setPublic(true) + ->addArgument(new Reference('bar2')); + +$container->register('bar2', BarCircular::class)->setPublic(!$public) + ->addMethodCall('addFoobar', array(new Reference('foobar2'))); + +$container->register('foobar2', FoobarCircular::class)->setPublic($public) + ->addArgument(new Reference('foo2')); + +// simple inline setter with internal reference + +$container->register('bar3', BarCircular::class)->setPublic(true) + ->addMethodCall('addFoobar', array(new Reference('foobar3'), new Reference('foobar3'))); + +$container->register('foobar3', FoobarCircular::class)->setPublic($public); + +// loop with non-shared dep + +$container->register('foo4', 'stdClass')->setPublic($public) + ->setShared(false) + ->setProperty('foobar', new Reference('foobar4')); + +$container->register('foobar4', 'stdClass')->setPublic(true) + ->addArgument(new Reference('foo4')); + return $container; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_inline_requires.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_inline_requires.php index 1acbfdfcaf81e..3bbfa31fa3c46 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_inline_requires.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/containers/container_inline_requires.php @@ -7,11 +7,13 @@ use Symfony\Component\DependencyInjection\Dumper\PhpDumper; use Symfony\Component\DependencyInjection\Reference; use Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath; +use Symfony\Component\DependencyInjection\Tests\Fixtures\ParentNotExists; $container = new ContainerBuilder(); $container->register(HotPath\C1::class)->addTag('container.hot_path')->setPublic(true); $container->register(HotPath\C2::class)->addArgument(new Reference(HotPath\C3::class))->setPublic(true); $container->register(HotPath\C3::class); +$container->register(ParentNotExists::class)->setPublic(true); return $container; diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php index bf99eff6a2838..ae1e92eadbafd 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/includes/autowiring_classes.php @@ -299,6 +299,10 @@ public function setNotAutowireable(NotARealClass $n) { } + public function setNotAutowireableBecauseOfATypo(lesTilleuls $sam) + { + } + public function setBar() { } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt index 7d70b4288d001..579bf285ad643 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt @@ -66,11 +66,11 @@ use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; // This file has been auto-generated by the Symfony Dependency Injection Component for internal use. // Returns the public 'configured_service' shared service. +$this->services['configured_service'] = $instance = new \stdClass(); + $a = new \ConfClass(); $a->setFoo(($this->services['baz'] ?? $this->load(__DIR__.'/getBazService.php'))); -$this->services['configured_service'] = $instance = new \stdClass(); - $a->configureStdClass($instance); return $instance; @@ -186,10 +186,10 @@ use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; // This file has been auto-generated by the Symfony Dependency Injection Component for internal use. // Returns the public 'foo_with_inline' shared service. -$a = new \Bar(); - $this->services['foo_with_inline'] = $instance = new \Foo(); +$a = new \Bar(); + $a->pub = 'pub'; $a->setBaz(($this->services['baz'] ?? $this->load(__DIR__.'/getBazService.php'))); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php index 47ccfc5e4d2ad..72e06b3416f69 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_compiled.php @@ -162,11 +162,11 @@ protected function getBazService() */ protected function getConfiguredServiceService() { + $this->services['configured_service'] = $instance = new \stdClass(); + $a = new \ConfClass(); $a->setFoo(($this->services['baz'] ?? $this->getBazService())); - $this->services['configured_service'] = $instance = new \stdClass(); - $a->configureStdClass($instance); return $instance; @@ -292,10 +292,10 @@ protected function getFooBarService() */ protected function getFooWithInlineService() { - $a = new \Bar(); - $this->services['foo_with_inline'] = $instance = new \Foo(); + $a = new \Bar(); + $a->pub = 'pub'; $a->setBaz(($this->services['baz'] ?? $this->getBazService())); diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/container_almost_circular_private.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php similarity index 54% rename from src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/container_almost_circular_private.php rename to src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php index 3a3f753c1fd3f..76af8a5484861 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/container_almost_circular_private.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_private.php @@ -24,7 +24,11 @@ public function __construct() { $this->services = $this->privates = array(); $this->methodMap = array( + 'bar2' => 'getBar2Service', + 'bar3' => 'getBar3Service', 'foo' => 'getFooService', + 'foo2' => 'getFoo2Service', + 'foobar4' => 'getFoobar4Service', ); $this->aliases = array(); @@ -52,10 +56,43 @@ public function getRemovedIds() 'Psr\\Container\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, 'bar' => true, + 'foo4' => true, 'foobar' => true, + 'foobar2' => true, + 'foobar3' => true, ); } + /** + * Gets the public 'bar2' shared service. + * + * @return \BarCircular + */ + protected function getBar2Service() + { + $this->services['bar2'] = $instance = new \BarCircular(); + + $instance->addFoobar(new \FoobarCircular(($this->services['foo2'] ?? $this->getFoo2Service()))); + + return $instance; + } + + /** + * Gets the public 'bar3' shared service. + * + * @return \BarCircular + */ + protected function getBar3Service() + { + $this->services['bar3'] = $instance = new \BarCircular(); + + $a = new \FoobarCircular(); + + $instance->addFoobar($a, $a); + + return $instance; + } + /** * Gets the public 'foo' shared service. * @@ -69,6 +106,37 @@ protected function getFooService() $a->addFoobar(new \FoobarCircular($instance)); + return $instance; + } + + /** + * Gets the public 'foo2' shared service. + * + * @return \FooCircular + */ + protected function getFoo2Service() + { + $a = ($this->services['bar2'] ?? $this->getBar2Service()); + + if (isset($this->services['foo2'])) { + return $this->services['foo2']; + } + + return $this->services['foo2'] = new \FooCircular($a); + } + + /** + * Gets the public 'foobar4' shared service. + * + * @return \stdClass + */ + protected function getFoobar4Service() + { + $a = new \stdClass(); + + $this->services['foobar4'] = $instance = new \stdClass($a); + + $a->foobar = $instance; return $instance; } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/container_almost_circular_public.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php similarity index 54% rename from src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/container_almost_circular_public.php rename to src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php index a306443e88109..a552a22b43358 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/container_almost_circular_public.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_almost_circular_public.php @@ -25,8 +25,14 @@ public function __construct() $this->services = $this->privates = array(); $this->methodMap = array( 'bar' => 'getBarService', + 'bar3' => 'getBar3Service', 'foo' => 'getFooService', + 'foo2' => 'getFoo2Service', + 'foo4' => 'getFoo4Service', 'foobar' => 'getFoobarService', + 'foobar2' => 'getFoobar2Service', + 'foobar3' => 'getFoobar3Service', + 'foobar4' => 'getFoobar4Service', ); $this->aliases = array(); @@ -53,6 +59,7 @@ public function getRemovedIds() return array( 'Psr\\Container\\ContainerInterface' => true, 'Symfony\\Component\\DependencyInjection\\ContainerInterface' => true, + 'bar2' => true, ); } @@ -70,6 +77,22 @@ protected function getBarService() return $instance; } + /** + * Gets the public 'bar3' shared service. + * + * @return \BarCircular + */ + protected function getBar3Service() + { + $this->services['bar3'] = $instance = new \BarCircular(); + + $a = ($this->services['foobar3'] ?? $this->services['foobar3'] = new \FoobarCircular()); + + $instance->addFoobar($a, $a); + + return $instance; + } + /** * Gets the public 'foo' shared service. * @@ -86,6 +109,36 @@ protected function getFooService() return $this->services['foo'] = new \FooCircular($a); } + /** + * Gets the public 'foo2' shared service. + * + * @return \FooCircular + */ + protected function getFoo2Service() + { + $a = new \BarCircular(); + + $this->services['foo2'] = $instance = new \FooCircular($a); + + $a->addFoobar(($this->services['foobar2'] ?? $this->getFoobar2Service())); + + return $instance; + } + + /** + * Gets the public 'foo4' service. + * + * @return \stdClass + */ + protected function getFoo4Service() + { + $instance = new \stdClass(); + + $instance->foobar = ($this->services['foobar4'] ?? $this->getFoobar4Service()); + + return $instance; + } + /** * Gets the public 'foobar' shared service. * @@ -101,4 +154,46 @@ protected function getFoobarService() return $this->services['foobar'] = new \FoobarCircular($a); } + + /** + * Gets the public 'foobar2' shared service. + * + * @return \FoobarCircular + */ + protected function getFoobar2Service() + { + $a = ($this->services['foo2'] ?? $this->getFoo2Service()); + + if (isset($this->services['foobar2'])) { + return $this->services['foobar2']; + } + + return $this->services['foobar2'] = new \FoobarCircular($a); + } + + /** + * Gets the public 'foobar3' shared service. + * + * @return \FoobarCircular + */ + protected function getFoobar3Service() + { + return $this->services['foobar3'] = new \FoobarCircular(); + } + + /** + * Gets the public 'foobar4' shared service. + * + * @return \stdClass + */ + protected function getFoobar4Service() + { + $a = new \stdClass(); + + $this->services['foobar4'] = $instance = new \stdClass($a); + + $a->foobar = $instance; + + return $instance; + } } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/container_inline_requires.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php similarity index 90% rename from src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/container_inline_requires.php rename to src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php index 19bc72a43e6ee..05bae361195d4 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/container_inline_requires.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_inline_requires.php @@ -30,6 +30,7 @@ public function __construct() $this->services = $this->privates = array(); $this->methodMap = array( + 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\ParentNotExists' => 'getParentNotExistsService', 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\includes\\HotPath\\C1' => 'getC1Service', 'Symfony\\Component\\DependencyInjection\\Tests\\Fixtures\\includes\\HotPath\\C2' => 'getC2Service', ); @@ -67,6 +68,16 @@ public function getRemovedIds() ); } + /** + * Gets the public 'Symfony\Component\DependencyInjection\Tests\Fixtures\ParentNotExists' shared service. + * + * @return \Symfony\Component\DependencyInjection\Tests\Fixtures\ParentNotExists + */ + protected function getParentNotExistsService() + { + return $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\ParentNotExists'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\ParentNotExists(); + } + /** * Gets the public 'Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C1' shared service. * @@ -84,8 +95,8 @@ protected function getC1Service() */ protected function getC2Service() { - require_once $this->targetDirs[1].'/includes/HotPath/C2.php'; require_once $this->targetDirs[1].'/includes/HotPath/C3.php'; + require_once $this->targetDirs[1].'/includes/HotPath/C2.php'; return $this->services['Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2'] = new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C2(new \Symfony\Component\DependencyInjection\Tests\Fixtures\includes\HotPath\C3()); } diff --git a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php index f4b6ea500cf49..b8aba31b1ea8b 100644 --- a/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php +++ b/src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_locator.php @@ -156,12 +156,12 @@ protected function getTranslator2Service() */ protected function getTranslator3Service() { - $a = ($this->services['translator.loader_3'] ?? $this->services['translator.loader_3'] = new \stdClass()); - $this->services['translator_3'] = $instance = new \Symfony\Component\DependencyInjection\Tests\Fixtures\StubbedTranslator(new \Symfony\Component\DependencyInjection\ServiceLocator(array('translator.loader_3' => function () { return ($this->services['translator.loader_3'] ?? $this->services['translator.loader_3'] = new \stdClass()); }))); + $a = ($this->services['translator.loader_3'] ?? $this->services['translator.loader_3'] = new \stdClass()); + $instance->addResource('db', $a, 'nl'); $instance->addResource('db', $a, 'en'); diff --git a/src/Symfony/Component/Dotenv/Dotenv.php b/src/Symfony/Component/Dotenv/Dotenv.php index c5f28d6e697fc..dc9aa11123e2e 100644 --- a/src/Symfony/Component/Dotenv/Dotenv.php +++ b/src/Symfony/Component/Dotenv/Dotenv.php @@ -174,7 +174,7 @@ private function lexVarname() private function lexValue() { - if (preg_match('/[ \t]*+(?:#.*)?$/Am', $this->data, $matches, null, $this->cursor)) { + if (preg_match('/[ \t]*+(?:#.*)?$/Am', $this->data, $matches, 0, $this->cursor)) { $this->moveCursor($matches[0]); $this->skipEmptyLines(); @@ -296,7 +296,7 @@ private function lexNestedExpression() private function skipEmptyLines() { - if (preg_match('/(?:\s*+(?:#[^\n]*+)?+)++/A', $this->data, $match, null, $this->cursor)) { + if (preg_match('/(?:\s*+(?:#[^\n]*+)?+)++/A', $this->data, $match, 0, $this->cursor)) { $this->moveCursor($match[0]); } } diff --git a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php index b67736b0ec8ed..387325782933c 100644 --- a/src/Symfony/Component/Form/Extension/Core/Type/FileType.php +++ b/src/Symfony/Component/Form/Extension/Core/Type/FileType.php @@ -92,9 +92,12 @@ public function finishView(FormView $view, FormInterface $form, array $options) */ public function configureOptions(OptionsResolver $resolver) { - $dataClass = function (Options $options) { - return $options['multiple'] ? null : 'Symfony\Component\HttpFoundation\File\File'; - }; + $dataClass = null; + if (class_exists('Symfony\Component\HttpFoundation\File\File')) { + $dataClass = function (Options $options) { + return $options['multiple'] ? null : 'Symfony\Component\HttpFoundation\File\File'; + }; + } $emptyData = function (Options $options) { return $options['multiple'] ? array() : null; diff --git a/src/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php b/src/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php index 08784fcda19d4..ef23457b410f4 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php +++ b/src/Symfony/Component/HttpFoundation/Session/Flash/AutoExpireFlashBag.php @@ -106,7 +106,7 @@ public function get($type, array $default = array()) public function all() { $return = $this->flashes['display']; - $this->flashes = array('new' => array(), 'display' => array()); + $this->flashes['display'] = array(); return $return; } diff --git a/src/Symfony/Component/HttpFoundation/Session/Session.php b/src/Symfony/Component/HttpFoundation/Session/Session.php index 09caa3442fa49..0c3371fab6c6d 100644 --- a/src/Symfony/Component/HttpFoundation/Session/Session.php +++ b/src/Symfony/Component/HttpFoundation/Session/Session.php @@ -28,6 +28,7 @@ class Session implements SessionInterface, \IteratorAggregate, \Countable private $flashName; private $attributeName; + private $data = array(); /** * @param SessionStorageInterface $storage A SessionStorageInterface instance @@ -108,7 +109,7 @@ public function remove($name) */ public function clear() { - $this->storage->getBag($this->attributeName)->clear(); + $this->getAttributeBag()->clear(); } /** @@ -139,6 +140,22 @@ public function count() return count($this->getAttributeBag()->all()); } + /** + * @return bool + * + * @internal + */ + public function isEmpty() + { + foreach ($this->data as &$data) { + if (!empty($data)) { + return false; + } + } + + return true; + } + /** * {@inheritdoc} */ @@ -210,7 +227,7 @@ public function getMetadataBag() */ public function registerBag(SessionBagInterface $bag) { - $this->storage->registerBag($bag); + $this->storage->registerBag(new SessionBagProxy($bag, $this->data)); } /** @@ -218,7 +235,7 @@ public function registerBag(SessionBagInterface $bag) */ public function getBag($name) { - return $this->storage->getBag($name); + return $this->storage->getBag($name)->getBag(); } /** @@ -240,6 +257,6 @@ public function getFlashBag() */ private function getAttributeBag() { - return $this->storage->getBag($this->attributeName); + return $this->storage->getBag($this->attributeName)->getBag(); } } diff --git a/src/Symfony/Component/HttpFoundation/Session/SessionBagProxy.php b/src/Symfony/Component/HttpFoundation/Session/SessionBagProxy.php new file mode 100644 index 0000000000000..6c4cab6716456 --- /dev/null +++ b/src/Symfony/Component/HttpFoundation/Session/SessionBagProxy.php @@ -0,0 +1,79 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpFoundation\Session; + +/** + * @author Nicolas Grekas
+ *
+ * @internal
+ */
+final class SessionBagProxy implements SessionBagInterface
+{
+ private $bag;
+ private $data;
+
+ public function __construct(SessionBagInterface $bag, array &$data)
+ {
+ $this->bag = $bag;
+ $this->data = &$data;
+ }
+
+ /**
+ * @return SessionBagInterface
+ */
+ public function getBag()
+ {
+ return $this->bag;
+ }
+
+ /**
+ * @return bool
+ */
+ public function isEmpty()
+ {
+ return empty($this->data[$this->bag->getStorageKey()]);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getName()
+ {
+ return $this->bag->getName();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function initialize(array &$array)
+ {
+ $this->data[$this->bag->getStorageKey()] = &$array;
+
+ $this->bag->initialize($array);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function getStorageKey()
+ {
+ return $this->bag->getStorageKey();
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function clear()
+ {
+ return $this->bag->clear();
+ }
+}
diff --git a/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php b/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php
index 23a05492166fa..732f92abf9e4d 100644
--- a/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php
+++ b/src/Symfony/Component/HttpFoundation/Session/Storage/MockFileSessionStorage.php
@@ -91,7 +91,26 @@ public function save()
throw new \RuntimeException('Trying to save a session that was not started yet or was already closed');
}
- file_put_contents($this->getFilePath(), serialize($this->data));
+ $data = $this->data;
+
+ foreach ($this->bags as $bag) {
+ if (empty($data[$key = $bag->getStorageKey()])) {
+ unset($data[$key]);
+ }
+ }
+ if (array($key = $this->metadataBag->getStorageKey()) === array_keys($data)) {
+ unset($data[$key]);
+ }
+
+ try {
+ if ($data) {
+ file_put_contents($this->getFilePath(), serialize($data));
+ } else {
+ $this->destroy();
+ }
+ } finally {
+ $this->data = $data;
+ }
// this is needed for Silex, where the session object is re-used across requests
// in functional tests. In Symfony, the container is rebooted, so we don't have
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/Flash/AutoExpireFlashBagTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/Flash/AutoExpireFlashBagTest.php
index a4d176a100d9e..fa8626ab923b7 100644
--- a/src/Symfony/Component/HttpFoundation/Tests/Session/Flash/AutoExpireFlashBagTest.php
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/Flash/AutoExpireFlashBagTest.php
@@ -150,4 +150,12 @@ public function testClear()
{
$this->assertEquals(array('notice' => array('A previous flash message')), $this->bag->clear());
}
+
+ public function testDoNotRemoveTheNewFlashesWhenDisplayingTheExistingOnes()
+ {
+ $this->bag->add('success', 'Something');
+ $this->bag->all();
+
+ $this->assertEquals(array('new' => array('success' => array('Something')), 'display' => array()), $this->array);
+ }
}
diff --git a/src/Symfony/Component/HttpFoundation/Tests/Session/SessionTest.php b/src/Symfony/Component/HttpFoundation/Tests/Session/SessionTest.php
index fa93507a41aaf..41720e4b6fc4e 100644
--- a/src/Symfony/Component/HttpFoundation/Tests/Session/SessionTest.php
+++ b/src/Symfony/Component/HttpFoundation/Tests/Session/SessionTest.php
@@ -221,4 +221,22 @@ public function testGetMeta()
{
$this->assertInstanceOf('Symfony\Component\HttpFoundation\Session\Storage\MetadataBag', $this->session->getMetadataBag());
}
+
+ public function testIsEmpty()
+ {
+ $this->assertTrue($this->session->isEmpty());
+
+ $this->session->set('hello', 'world');
+ $this->assertFalse($this->session->isEmpty());
+
+ $this->session->remove('hello');
+ $this->assertTrue($this->session->isEmpty());
+
+ $flash = $this->session->getFlashBag();
+ $flash->set('hello', 'world');
+ $this->assertFalse($this->session->isEmpty());
+
+ $flash->get('hello');
+ $this->assertTrue($this->session->isEmpty());
+ }
}
diff --git a/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php b/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php
index fbcecad25e18a..7b0aa4b5a226e 100644
--- a/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php
+++ b/src/Symfony/Component/HttpKernel/Controller/ContainerControllerResolver.php
@@ -13,6 +13,7 @@
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
+use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\HttpFoundation\Request;
/**
@@ -86,6 +87,15 @@ protected function instantiateController($class)
return $this->container->get($class);
}
- return parent::instantiateController($class);
+ try {
+ return parent::instantiateController($class);
+ } catch (\ArgumentCountError $e) {
+ }
+
+ if ($this->container instanceof Container && in_array($class, $this->container->getRemovedIds(), true)) {
+ throw new \LogicException(sprintf('Controller "%s" cannot be fetched from the container because it is private. Did you forget to tag the service with "controller.service_arguments"?', $class), 0, $e);
+ }
+
+ throw $e;
}
}
diff --git a/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php b/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php
index eb0320f6b91e6..2531db66790d2 100644
--- a/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php
+++ b/src/Symfony/Component/HttpKernel/EventListener/AbstractTestSessionListener.php
@@ -12,6 +12,7 @@
namespace Symfony\Component\HttpKernel\EventListener;
use Symfony\Component\HttpFoundation\Cookie;
+use Symfony\Component\HttpFoundation\Session\Session;
use Symfony\Component\HttpFoundation\Session\SessionInterface;
use Symfony\Component\HttpKernel\KernelEvents;
use Symfony\Component\HttpKernel\Event\FilterResponseEvent;
@@ -60,8 +61,10 @@ public function onKernelResponse(FilterResponseEvent $event)
$session = $event->getRequest()->getSession();
if ($session && $session->isStarted()) {
$session->save();
- $params = session_get_cookie_params();
- $event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly']));
+ if (!$session instanceof Session || !\method_exists($session, 'isEmpty') || !$session->isEmpty()) {
+ $params = session_get_cookie_params();
+ $event->getResponse()->headers->setCookie(new Cookie($session->getName(), $session->getId(), 0 === $params['lifetime'] ? 0 : time() + $params['lifetime'], $params['path'], $params['domain'], $params['secure'], $params['httponly']));
+ }
}
}
diff --git a/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php b/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php
index 2489a639376e1..ba1f3687038aa 100644
--- a/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php
+++ b/src/Symfony/Component/HttpKernel/Fragment/AbstractSurrogateFragmentRenderer.php
@@ -98,8 +98,8 @@ private function generateSignedFragmentUri($uri, Request $request): string
private function containsNonScalars(array $values): bool
{
foreach ($values as $value) {
- if (is_array($value) && $this->containsNonScalars($value)) {
- return true;
+ if (is_array($value)) {
+ return $this->containsNonScalars($value);
} elseif (!is_scalar($value) && null !== $value) {
return true;
}
diff --git a/src/Symfony/Component/HttpKernel/Kernel.php b/src/Symfony/Component/HttpKernel/Kernel.php
index b0cd20167fe81..2a6d8a4fd17e8 100644
--- a/src/Symfony/Component/HttpKernel/Kernel.php
+++ b/src/Symfony/Component/HttpKernel/Kernel.php
@@ -63,12 +63,12 @@ abstract class Kernel implements KernelInterface, RebootableInterface, Terminabl
private $requestStackSize = 0;
private $resetServices = false;
- const VERSION = '4.0.0-RC2';
+ const VERSION = '4.0.0';
const VERSION_ID = 40000;
const MAJOR_VERSION = 4;
const MINOR_VERSION = 0;
const RELEASE_VERSION = 0;
- const EXTRA_VERSION = 'RC2';
+ const EXTRA_VERSION = '';
const END_OF_MAINTENANCE = '07/2018';
const END_OF_LIFE = '01/2019';
@@ -112,7 +112,7 @@ public function boot()
return;
}
- if ($this->debug && !isset($_SERVER['SHELL_VERBOSITY'])) {
+ if ($this->debug && !isset($_ENV['SHELL_VERBOSITY']) && !isset($_SERVER['SHELL_VERBOSITY'])) {
putenv('SHELL_VERBOSITY=3');
$_ENV['SHELL_VERBOSITY'] = 3;
$_SERVER['SHELL_VERBOSITY'] = 3;
@@ -451,8 +451,11 @@ protected function initializeContainer()
$class = $this->getContainerClass();
$cacheDir = $this->warmupDir ?: $this->getCacheDir();
$cache = new ConfigCache($cacheDir.'/'.$class.'.php', $this->debug);
- $fresh = true;
- if (!$cache->isFresh()) {
+ if ($fresh = $cache->isFresh()) {
+ $this->container = require $cache->getPath();
+ $fresh = \is_object($this->container);
+ }
+ if (!$fresh) {
if ($this->debug) {
$collectedLogs = array();
$previousHandler = set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) {
@@ -502,11 +505,9 @@ protected function initializeContainer()
$oldContainer = file_exists($cache->getPath()) && is_object($oldContainer = @include $cache->getPath()) ? new \ReflectionClass($oldContainer) : false;
$this->dumpContainer($cache, $container, $class, $this->getContainerBaseClass());
-
- $fresh = false;
+ $this->container = require $cache->getPath();
}
- $this->container = require $cache->getPath();
$this->container->set('kernel', $this);
if ($fresh) {
@@ -514,7 +515,17 @@ protected function initializeContainer()
}
if ($oldContainer && get_class($this->container) !== $oldContainer->name) {
- (new Filesystem())->remove(dirname($oldContainer->getFileName()));
+ // Because concurrent requests might still be using them,
+ // old container files are not removed immediately,
+ // but on a next dump of the container.
+ $oldContainerDir = dirname($oldContainer->getFileName());
+ foreach (glob(dirname($oldContainerDir).'/*.legacyContainer') as $legacyContainer) {
+ if ($oldContainerDir.'.legacyContainer' !== $legacyContainer && @unlink($legacyContainer)) {
+ (new Filesystem())->remove(substr($legacyContainer, 0, -16));
+ }
+ }
+
+ touch($oldContainerDir.'.legacyContainer');
}
if ($this->container->has('cache_warmer')) {
diff --git a/src/Symfony/Component/HttpKernel/Log/Logger.php b/src/Symfony/Component/HttpKernel/Log/Logger.php
index e26767f55d5e8..bf6ab49232fac 100644
--- a/src/Symfony/Component/HttpKernel/Log/Logger.php
+++ b/src/Symfony/Component/HttpKernel/Log/Logger.php
@@ -42,8 +42,8 @@ public function __construct(string $minLevel = null, $output = 'php://stderr', c
if (null === $minLevel) {
$minLevel = LogLevel::WARNING;
- if (isset($_SERVER['SHELL_VERBOSITY'])) {
- switch ((int) $_SERVER['SHELL_VERBOSITY']) {
+ if (isset($_ENV['SHELL_VERBOSITY']) || isset($_SERVER['SHELL_VERBOSITY'])) {
+ switch ((int) (isset($_ENV['SHELL_VERBOSITY']) ? $_ENV['SHELL_VERBOSITY'] : $_SERVER['SHELL_VERBOSITY'])) {
case -1: $minLevel = LogLevel::ERROR; break;
case 1: $minLevel = LogLevel::NOTICE; break;
case 2: $minLevel = LogLevel::INFO; break;
diff --git a/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php b/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php
index b3deb03c9138a..b3fa081a63c88 100644
--- a/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/Controller/ContainerControllerResolverTest.php
@@ -13,6 +13,8 @@
use Psr\Container\ContainerInterface;
use Psr\Log\LoggerInterface;
+use Symfony\Component\Debug\ErrorHandler;
+use Symfony\Component\DependencyInjection\Container;
use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpKernel\Controller\ContainerControllerResolver;
@@ -106,6 +108,38 @@ public function testNonInstantiableController()
$this->assertSame(array(NonInstantiableController::class, 'action'), $controller);
}
+ /**
+ * @expectedException \LogicException
+ * @expectedExceptionMessage Controller "Symfony\Component\HttpKernel\Tests\Controller\ImpossibleConstructController" cannot be fetched from the container because it is private. Did you forget to tag the service with "controller.service_arguments"?
+ */
+ public function testNonConstructController()
+ {
+ $container = $this->getMockBuilder(Container::class)->getMock();
+ $container->expects($this->at(0))
+ ->method('has')
+ ->with(ImpossibleConstructController::class)
+ ->will($this->returnValue(true))
+ ;
+
+ $container->expects($this->at(1))
+ ->method('has')
+ ->with(ImpossibleConstructController::class)
+ ->will($this->returnValue(false))
+ ;
+
+ $container->expects($this->atLeastOnce())
+ ->method('getRemovedIds')
+ ->with()
+ ->will($this->returnValue(array(ImpossibleConstructController::class)))
+ ;
+
+ $resolver = $this->createControllerResolver(null, $container);
+ $request = Request::create('/');
+ $request->attributes->set('_controller', array(ImpossibleConstructController::class, 'action'));
+
+ $resolver->getController($request);
+ }
+
public function testNonInstantiableControllerWithCorrespondingService()
{
$service = new \stdClass();
@@ -196,3 +230,14 @@ public static function action()
{
}
}
+
+class ImpossibleConstructController
+{
+ public function __construct($toto, $controller)
+ {
+ }
+
+ public function action()
+ {
+ }
+}
diff --git a/src/Symfony/Component/HttpKernel/Tests/Event/FilterControllerArgumentsEventTest.php b/src/Symfony/Component/HttpKernel/Tests/Event/FilterControllerArgumentsEventTest.php
new file mode 100644
index 0000000000000..9165d31f24a15
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/Tests/Event/FilterControllerArgumentsEventTest.php
@@ -0,0 +1,17 @@
+assertEquals($filterController->getArguments(), array('test'));
+ }
+}
diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/TestSessionListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/TestSessionListenerTest.php
index 124bd64d848f3..4452f48771b8b 100644
--- a/src/Symfony/Component/HttpKernel/Tests/EventListener/TestSessionListenerTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/TestSessionListenerTest.php
@@ -73,6 +73,19 @@ public function testDoesNotDeleteCookieIfUsingSessionLifetime()
$this->assertEquals(0, reset($cookies)->getExpiresTime());
}
+ /**
+ * @requires function \Symfony\Component\HttpFoundation\Session\Session::isEmpty
+ */
+ public function testEmptySessionDoesNotSendCookie()
+ {
+ $this->sessionHasBeenStarted();
+ $this->sessionIsEmpty();
+
+ $response = $this->filterResponse(new Request(), HttpKernelInterface::MASTER_REQUEST);
+
+ $this->assertSame(array(), $response->headers->getCookies());
+ }
+
public function testUnstartedSessionIsNotSave()
{
$this->sessionHasNotBeenStarted();
@@ -130,6 +143,13 @@ private function sessionHasNotBeenStarted()
->will($this->returnValue(false));
}
+ private function sessionIsEmpty()
+ {
+ $this->session->expects($this->once())
+ ->method('isEmpty')
+ ->will($this->returnValue(true));
+ }
+
private function getSession()
{
$mock = $this->getMockBuilder('Symfony\Component\HttpFoundation\Session\Session')
diff --git a/src/Symfony/Component/HttpKernel/Tests/Fragment/EsiFragmentRendererTest.php b/src/Symfony/Component/HttpKernel/Tests/Fragment/EsiFragmentRendererTest.php
index 6cefea6b02f3b..8a40dcd5bb892 100644
--- a/src/Symfony/Component/HttpKernel/Tests/Fragment/EsiFragmentRendererTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/Fragment/EsiFragmentRendererTest.php
@@ -26,6 +26,14 @@ public function testRenderFallbackToInlineStrategyIfEsiNotSupported()
$strategy->render('/', Request::create('/'));
}
+ public function testRenderFallbackWithScalar()
+ {
+ $strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy(true), new UriSigner('foo'));
+ $request = Request::create('/');
+ $reference = new ControllerReference('main_controller', array('foo' => array(true)), array());
+ $strategy->render($reference, $request);
+ }
+
public function testRender()
{
$strategy = new EsiFragmentRenderer(new Esi(), $this->getInlineStrategy());
diff --git a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php
index b70a859fff2f8..955b1cf83ba57 100644
--- a/src/Symfony/Component/HttpKernel/Tests/KernelTest.php
+++ b/src/Symfony/Component/HttpKernel/Tests/KernelTest.php
@@ -520,7 +520,8 @@ public function testKernelReset()
$kernel->boot();
$this->assertTrue(get_class($kernel->getContainer()) !== $containerClass);
- $this->assertFileNotExists($containerFile);
+ $this->assertFileExists($containerFile);
+ $this->assertFileExists(dirname($containerFile).'.legacyContainer');
}
public function testKernelPass()
diff --git a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php
index f547b4984ffd4..406a36d2325a1 100644
--- a/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php
+++ b/src/Symfony/Component/Serializer/Normalizer/AbstractObjectNormalizer.php
@@ -129,6 +129,10 @@ protected function getAttributes($object, $format = null, array $context)
return $allowedAttributes;
}
+ if (isset($context['attributes'])) {
+ return $this->extractAttributes($object, $format, $context);
+ }
+
if (isset($this->attributesCache[$class])) {
return $this->attributesCache[$class];
}
diff --git a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php
index f10937bd9c3a2..0d4e880ec7f18 100644
--- a/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php
+++ b/src/Symfony/Component/Serializer/Tests/Normalizer/ObjectNormalizerTest.php
@@ -723,6 +723,37 @@ public function testAttributesContextDenormalizeConstructor()
'inner' => array('foo' => 'foo', 'bar' => 'bar'),
), DummyWithConstructorObjectAndDefaultValue::class, null, $context));
}
+
+ public function testNormalizeSameObjectWithDifferentAttributes()
+ {
+ $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
+ $this->normalizer = new ObjectNormalizer($classMetadataFactory);
+ $serializer = new Serializer(array($this->normalizer));
+ $this->normalizer->setSerializer($serializer);
+
+ $dummy = new ObjectOuter();
+ $dummy->foo = new ObjectInner();
+ $dummy->foo->foo = 'foo.foo';
+ $dummy->foo->bar = 'foo.bar';
+
+ $dummy->bar = new ObjectInner();
+ $dummy->bar->foo = 'bar.foo';
+ $dummy->bar->bar = 'bar.bar';
+
+ $this->assertEquals(array(
+ 'foo' => array(
+ 'bar' => 'foo.bar',
+ ),
+ 'bar' => array(
+ 'foo' => 'bar.foo',
+ ),
+ ), $this->normalizer->normalize($dummy, 'json', array(
+ 'attributes' => array(
+ 'foo' => array('bar'),
+ 'bar' => array('foo'),
+ ),
+ )));
+ }
}
class ObjectDummy
diff --git a/src/Symfony/Component/Translation/DependencyInjection/TranslatorPass.php b/src/Symfony/Component/Translation/DependencyInjection/TranslatorPass.php
index 7ec06684f5155..1f7839dbff35e 100644
--- a/src/Symfony/Component/Translation/DependencyInjection/TranslatorPass.php
+++ b/src/Symfony/Component/Translation/DependencyInjection/TranslatorPass.php
@@ -64,8 +64,15 @@ public function process(ContainerBuilder $container)
->replaceArgument(3, $loaders)
;
- if ($container->hasParameter('twig.default_path')) {
+ if (!$container->hasParameter('twig.default_path')) {
+ return;
+ }
+
+ if ($container->hasDefinition($this->debugCommandServiceId)) {
$container->getDefinition($this->debugCommandServiceId)->replaceArgument(4, $container->getParameter('twig.default_path'));
+ }
+
+ if ($container->hasDefinition($this->updateCommandServiceId)) {
$container->getDefinition($this->updateCommandServiceId)->replaceArgument(5, $container->getParameter('twig.default_path'));
}
}
diff --git a/src/Symfony/Component/VarDumper/Caster/Caster.php b/src/Symfony/Component/VarDumper/Caster/Caster.php
index ee8dc3a3cd9c5..7f4e02283f580 100644
--- a/src/Symfony/Component/VarDumper/Caster/Caster.php
+++ b/src/Symfony/Component/VarDumper/Caster/Caster.php
@@ -113,8 +113,8 @@ public static function filter(array $a, $filter, array $listedProperties = array
if (null === $v) {
$type |= self::EXCLUDE_NULL & $filter;
- }
- if (empty($v)) {
+ $type |= self::EXCLUDE_EMPTY & $filter;
+ } elseif (false === $v || '' === $v || '0' === $v || 0 === $v || 0.0 === $v || array() === $v) {
$type |= self::EXCLUDE_EMPTY & $filter;
}
if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !in_array($k, $listedProperties, true)) {
diff --git a/src/Symfony/Component/VarDumper/Cloner/VarCloner.php b/src/Symfony/Component/VarDumper/Cloner/VarCloner.php
index 79b920532f412..012743cd611da 100644
--- a/src/Symfony/Component/VarDumper/Cloner/VarCloner.php
+++ b/src/Symfony/Component/VarDumper/Cloner/VarCloner.php
@@ -101,13 +101,16 @@ protected function doClone($var)
// Create $stub when the original value $v can not be used directly
// If $v is a nested structure, put that structure in array $a
switch (true) {
- case empty($v):
- case true === $v:
+ case null === $v:
+ case \is_bool($v):
case \is_int($v):
case \is_float($v):
continue 2;
case \is_string($v):
+ if ('' === $v) {
+ continue 2;
+ }
if (!\preg_match('//u', $v)) {
$stub = new Stub();
$stub->type = Stub::TYPE_STRING;
@@ -131,6 +134,9 @@ protected function doClone($var)
break;
case \is_array($v):
+ if (!$v) {
+ continue 2;
+ }
$stub = $arrayStub;
$stub->class = Stub::ARRAY_INDEXED;
diff --git a/src/Symfony/Component/Yaml/Inline.php b/src/Symfony/Component/Yaml/Inline.php
index fe9f1d87e2ca7..475e46337ade2 100644
--- a/src/Symfony/Component/Yaml/Inline.php
+++ b/src/Symfony/Component/Yaml/Inline.php
@@ -402,6 +402,7 @@ private static function parseMapping(string $mapping, int $flags, int &$i = 0, a
$output = array();
$len = strlen($mapping);
++$i;
+ $allowOverwrite = false;
// {foo: bar, bar:foo, ...}
while ($i < $len) {
@@ -443,6 +444,10 @@ private static function parseMapping(string $mapping, int $flags, int &$i = 0, a
throw new ParseException('Colons must be followed by a space or an indication character (i.e. " ", ",", "[", "]", "{", "}").', self::$parsedLineNumber + 1, $mapping);
}
+ if ('<<' === $key) {
+ $allowOverwrite = true;
+ }
+
while ($i < $len) {
if (':' === $mapping[$i] || ' ' === $mapping[$i]) {
++$i;
@@ -458,7 +463,18 @@ private static function parseMapping(string $mapping, int $flags, int &$i = 0, a
// Spec: Keys MUST be unique; first one wins.
// Parser cannot abort this mapping earlier, since lines
// are processed sequentially.
- if (isset($output[$key])) {
+ // But overwriting is allowed when a merge node is used in current block.
+ if ('<<' === $key) {
+ foreach ($value as $parsedValue) {
+ $output += $parsedValue;
+ }
+ } elseif ($allowOverwrite || !isset($output[$key])) {
+ if (null !== $tag) {
+ $output[$key] = new TaggedValue($tag, $value);
+ } else {
+ $output[$key] = $value;
+ }
+ } elseif (isset($output[$key])) {
throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping);
}
break;
@@ -468,7 +484,16 @@ private static function parseMapping(string $mapping, int $flags, int &$i = 0, a
// Spec: Keys MUST be unique; first one wins.
// Parser cannot abort this mapping earlier, since lines
// are processed sequentially.
- if (isset($output[$key])) {
+ // But overwriting is allowed when a merge node is used in current block.
+ if ('<<' === $key) {
+ $output += $value;
+ } elseif ($allowOverwrite || !isset($output[$key])) {
+ if (null !== $tag) {
+ $output[$key] = new TaggedValue($tag, $value);
+ } else {
+ $output[$key] = $value;
+ }
+ } elseif (isset($output[$key])) {
throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping);
}
break;
@@ -477,18 +502,20 @@ private static function parseMapping(string $mapping, int $flags, int &$i = 0, a
// Spec: Keys MUST be unique; first one wins.
// Parser cannot abort this mapping earlier, since lines
// are processed sequentially.
- if (isset($output[$key])) {
+ // But overwriting is allowed when a merge node is used in current block.
+ if ('<<' === $key) {
+ $output += $value;
+ } elseif ($allowOverwrite || !isset($output[$key])) {
+ if (null !== $tag) {
+ $output[$key] = new TaggedValue($tag, $value);
+ } else {
+ $output[$key] = $value;
+ }
+ } elseif (isset($output[$key])) {
throw new ParseException(sprintf('Duplicate key "%s" detected.', $key), self::$parsedLineNumber + 1, $mapping);
}
--$i;
}
-
- if (null !== $tag && '' !== $tag) {
- $output[$key] = new TaggedValue($tag, $value);
- } else {
- $output[$key] = $value;
- }
-
++$i;
continue 2;
diff --git a/src/Symfony/Component/Yaml/Tests/Fixtures/sfMergeKey.yml b/src/Symfony/Component/Yaml/Tests/Fixtures/sfMergeKey.yml
index 18e5abf939f7c..83264cfab8190 100644
--- a/src/Symfony/Component/Yaml/Tests/Fixtures/sfMergeKey.yml
+++ b/src/Symfony/Component/Yaml/Tests/Fixtures/sfMergeKey.yml
@@ -21,6 +21,7 @@ yaml: |
c:
foo: bar
bar: foo
+ bar_inline: {a: before, d: other, <<: *foo, b: new, x: Oren, c: { foo: bar, bar: foo}}
foo2: &foo2
a: Ballmer
ding: &dong [ fi, fei, fo, fam]
@@ -42,14 +43,19 @@ yaml: |
p: 12345
z:
<<: *nestedref
+ head_inline: &head_inline { <<: [ *foo , *dong , *foo2 ] }
+ recursive_inline: { <<: *head_inline, c: { <<: *foo2 } }
php: |
array(
'foo' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' => 'notnull'),
'bar' => array('a' => 'before', 'd' => 'other', 'e' => null, 'b' => 'new', 'c' => array('foo' => 'bar', 'bar' => 'foo'), 'x' => 'Oren'),
+ 'bar_inline' => array('a' => 'before', 'd' => 'other', 'b' => 'new', 'c' => array('foo' => 'bar', 'bar' => 'foo'), 'e' => 'notnull', 'x' => 'Oren'),
'foo2' => array('a' => 'Ballmer'),
'ding' => array('fi', 'fei', 'fo', 'fam'),
'check' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' => 'notnull', 'fi', 'fei', 'fo', 'fam', 'isit' => 'tested'),
'head' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' => 'notnull', 'fi', 'fei', 'fo', 'fam'),
'taz' => array('a' => 'Steve', 'w' => array('p' => 1234)),
- 'nested' => array('a' => 'Steve', 'w' => array('p' => 12345), 'd' => 'Doug', 'z' => array('p' => 12345))
+ 'nested' => array('a' => 'Steve', 'w' => array('p' => 12345), 'd' => 'Doug', 'z' => array('p' => 12345)),
+ 'head_inline' => array('a' => 'Steve', 'b' => 'Clark', 'c' => 'Brian', 'e' => 'notnull', 'fi', 'fei', 'fo', 'fam'),
+ 'recursive_inline' => array('a' => 'Steve', 'b' => 'Clark', 'c' => array('a' => 'Ballmer'), 'e' => 'notnull', 'fi', 'fei', 'fo', 'fam'),
)
diff --git a/src/Symfony/Component/Yaml/Tests/ParserTest.php b/src/Symfony/Component/Yaml/Tests/ParserTest.php
index fb28277abf040..b3b1465aaf379 100644
--- a/src/Symfony/Component/Yaml/Tests/ParserTest.php
+++ b/src/Symfony/Component/Yaml/Tests/ParserTest.php
@@ -1888,6 +1888,18 @@ public function testParseReferencesOnMergeKeysWithMappingsParsedAsObjects()
$this->assertEquals($expected, $this->parser->parse($yaml, Yaml::PARSE_OBJECT_FOR_MAP));
}
+
+ /**
+ * @expectedException \Symfony\Component\Yaml\Exception\ParseException
+ * @expectedExceptionMessage Reference "foo" does not exist
+ */
+ public function testEvalRefException()
+ {
+ $yaml = << 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:Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.