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 = <<