Mercurial > hg > isophonics-drupal-site
diff vendor/symfony/dependency-injection/Dumper/PhpDumper.php @ 14:1fec387a4317
Update Drupal core to 8.5.2 via Composer
author | Chris Cannam |
---|---|
date | Mon, 23 Apr 2018 09:46:53 +0100 |
parents | 7a779792577d |
children | c2387f117808 |
line wrap: on
line diff
--- a/vendor/symfony/dependency-injection/Dumper/PhpDumper.php Mon Apr 23 09:33:26 2018 +0100 +++ b/vendor/symfony/dependency-injection/Dumper/PhpDumper.php Mon Apr 23 09:46:53 2018 +0100 @@ -11,12 +11,17 @@ namespace Symfony\Component\DependencyInjection\Dumper; +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; use Symfony\Component\DependencyInjection\Variable; use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass; use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; use Symfony\Component\DependencyInjection\Parameter; use Symfony\Component\DependencyInjection\Exception\EnvParameterException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; @@ -38,19 +43,14 @@ { /** * Characters that might appear in the generated variable name as first character. - * - * @var string */ const FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz'; /** * Characters that might appear in the generated variable name as any but the first character. - * - * @var string */ const NON_FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_'; - private $inlinedDefinitions; private $definitionVariables; private $referenceVariables; private $variableCount; @@ -61,9 +61,15 @@ private $docStar; private $serviceIdToMethodNameMap; private $usedMethodNames; + private $namespace; + private $asFiles; + private $hotPathTag; + private $inlineRequires; + private $inlinedRequires = array(); + private $circularReferences = array(); /** - * @var \Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface + * @var ProxyDumper */ private $proxyDumper; @@ -72,15 +78,15 @@ */ public function __construct(ContainerBuilder $container) { + if (!$container->isCompiled()) { + @trigger_error('Dumping an uncompiled ContainerBuilder is deprecated since Symfony 3.3 and will not be supported anymore in 4.0. Compile the container beforehand.', E_USER_DEPRECATED); + } + parent::__construct($container); - - $this->inlinedDefinitions = new \SplObjectStorage(); } /** * Sets the dumper to be used when dumping proxies in the generated container. - * - * @param ProxyDumper $proxyDumper */ public function setProxyDumper(ProxyDumper $proxyDumper) { @@ -95,24 +101,51 @@ * * class: The class name * * base_class: The base class name * * namespace: The class namespace + * * as_files: To split the container in several files * - * @param array $options An array of options - * - * @return string A PHP class representing of the service container + * @return string|array A PHP class representing the service container or an array of PHP files if the "as_files" option is set * * @throws EnvParameterException When an env var exists but has not been dumped */ public function dump(array $options = array()) { $this->targetDirRegex = null; + $this->inlinedRequires = array(); $options = array_merge(array( 'class' => 'ProjectServiceContainer', 'base_class' => 'Container', 'namespace' => '', + 'as_files' => false, 'debug' => true, + 'hot_path_tag' => 'container.hot_path', + 'inline_class_loader_parameter' => 'container.dumper.inline_class_loader', + 'build_time' => time(), ), $options); - $this->initializeMethodNamesMap($options['base_class']); + $this->namespace = $options['namespace']; + $this->asFiles = $options['as_files']; + $this->hotPathTag = $options['hot_path_tag']; + $this->inlineRequires = $options['inline_class_loader_parameter'] && $this->container->hasParameter($options['inline_class_loader_parameter']) && $this->container->getParameter($options['inline_class_loader_parameter']); + + if (0 !== strpos($baseClass = $options['base_class'], '\\') && 'Container' !== $baseClass) { + $baseClass = sprintf('%s\%s', $options['namespace'] ? '\\'.$options['namespace'] : '', $baseClass); + $baseClassWithNamespace = $baseClass; + } elseif ('Container' === $baseClass) { + $baseClassWithNamespace = Container::class; + } else { + $baseClassWithNamespace = $baseClass; + } + + $this->initializeMethodNamesMap('Container' === $baseClass ? Container::class : $baseClass); + + (new AnalyzeServiceReferencesPass())->process($this->container); + $this->circularReferences = array(); + $checkedNodes = array(); + foreach ($this->container->getCompiler()->getServiceReferenceGraph()->getNodes() as $id => $node) { + $currentPath = array($id => $id); + $this->analyzeCircularReferences($node->getOutEdges(), $checkedNodes, $currentPath); + } + $this->container->getCompiler()->getServiceReferenceGraph()->clear(); $this->docStar = $options['debug'] ? '*' : ''; @@ -141,23 +174,85 @@ } } - $code = $this->startClass($options['class'], $options['base_class'], $options['namespace']); + $code = + $this->startClass($options['class'], $baseClass, $baseClassWithNamespace). + $this->addServices(). + $this->addDefaultParametersMethod(). + $this->endClass() + ; - if ($this->container->isFrozen()) { - $code .= $this->addFrozenConstructor(); - $code .= $this->addFrozenCompile(); - $code .= $this->addIsFrozenMethod(); + if ($this->asFiles) { + $fileStart = <<<EOF +<?php + +use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; + +// This file has been auto-generated by the Symfony Dependency Injection Component for internal use. + +EOF; + $files = array(); + + if ($ids = array_keys($this->container->getRemovedIds())) { + sort($ids); + $c = "<?php\n\nreturn array(\n"; + foreach ($ids as $id) { + $c .= ' '.$this->doExport($id)." => true,\n"; + } + $files['removed-ids.php'] = $c .= ");\n"; + } + + foreach ($this->generateServiceFiles() as $file => $c) { + $files[$file] = $fileStart.$c; + } + foreach ($this->generateProxyClasses() as $file => $c) { + $files[$file] = "<?php\n".$c; + } + $files[$options['class'].'.php'] = $code; + $hash = ucfirst(strtr(ContainerBuilder::hash($files), '._', 'xx')); + $code = array(); + + foreach ($files as $file => $c) { + $code["Container{$hash}/{$file}"] = $c; + } + array_pop($code); + $code["Container{$hash}/{$options['class']}.php"] = substr_replace($files[$options['class'].'.php'], "<?php\n\nnamespace Container{$hash};\n", 0, 6); + $namespaceLine = $this->namespace ? "\nnamespace {$this->namespace};\n" : ''; + $time = $options['build_time']; + $id = hash('crc32', $hash.$time); + + $code[$options['class'].'.php'] = <<<EOF +<?php +{$namespaceLine} +// This file has been auto-generated by the Symfony Dependency Injection Component for internal use. + +if (\\class_exists(\\Container{$hash}\\{$options['class']}::class, false)) { + // no-op +} elseif (!include __DIR__.'/Container{$hash}/{$options['class']}.php') { + touch(__DIR__.'/Container{$hash}.legacy'); + + return; +} + +if (!\\class_exists({$options['class']}::class, false)) { + \\class_alias(\\Container{$hash}\\{$options['class']}::class, {$options['class']}::class, false); +} + +return new \\Container{$hash}\\{$options['class']}(array( + 'container.build_hash' => '$hash', + 'container.build_id' => '$id', + 'container.build_time' => $time, +), __DIR__.\\DIRECTORY_SEPARATOR.'Container{$hash}'); + +EOF; } else { - $code .= $this->addConstructor(); + foreach ($this->generateProxyClasses() as $c) { + $code .= $c; + } } - $code .= - $this->addServices(). - $this->addDefaultParametersMethod(). - $this->endClass(). - $this->addProxyClasses() - ; $this->targetDirRegex = null; + $this->inlinedRequires = array(); + $this->circularReferences = array(); $unusedEnvs = array(); foreach ($this->container->getEnvCounters() as $env => $use) { @@ -189,100 +284,175 @@ /** * Generates Service local temp variables. * - * @param string $cId - * @param string $definition - * * @return string */ - private function addServiceLocalTempVariables($cId, $definition) + private function addServiceLocalTempVariables($cId, Definition $definition, \SplObjectStorage $inlinedDefinitions, \SplObjectStorage $allInlinedDefinitions) { - static $template = " \$%s = %s;\n"; + $allCalls = $calls = $behavior = array(); - $localDefinitions = array_merge( - array($definition), - $this->getInlinedDefinitions($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]); + } - $calls = $behavior = array(); - foreach ($localDefinitions as $iDefinition) { - $this->getServiceCallsFromArguments($iDefinition->getArguments(), $calls, $behavior); - $this->getServiceCallsFromArguments($iDefinition->getMethodCalls(), $calls, $behavior); - $this->getServiceCallsFromArguments($iDefinition->getProperties(), $calls, $behavior); - $this->getServiceCallsFromArguments(array($iDefinition->getConfigurator()), $calls, $behavior); - $this->getServiceCallsFromArguments(array($iDefinition->getFactory()), $calls, $behavior); + $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); + } + } + 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 ($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, ContainerInterface::NULL_ON_INVALID_REFERENCE))); - } - } + $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 ($isPreInstance) { + $code .= <<<EOTXT + + if (isset(\$this->services['$cId'])) { + return \$this->services['$cId']; + } + +EOTXT; + } + $code .= "\n"; } return $code; } - /** - * Generates code for the proxies to be attached after the container class. - * - * @return string - */ - private function addProxyClasses() + private function analyzeCircularReferences(array $edges, &$checkedNodes, &$currentPath) { - /* @var $definitions Definition[] */ - $definitions = array_filter( - $this->container->getDefinitions(), - array($this->getProxyDumper(), 'isProxyCandidate') - ); - $code = ''; + foreach ($edges as $edge) { + $node = $edge->getDestNode(); + $id = $node->getId(); + + if ($node->getValue() && ($edge->isLazy() || $edge->isWeak())) { + // no-op + } elseif (isset($currentPath[$id])) { + foreach (array_reverse($currentPath) as $parentId) { + $this->circularReferences[$parentId][$id] = $id; + $id = $parentId; + } + } elseif (!isset($checkedNodes[$id])) { + $checkedNodes[$id] = true; + $currentPath[$id] = $id; + $this->analyzeCircularReferences($node->getOutEdges(), $checkedNodes, $currentPath); + unset($currentPath[$id]); + } + } + } + + private function collectLineage($class, array &$lineage) + { + if (isset($lineage[$class])) { + return; + } + if (!$r = $this->container->getReflectionClass($class, false)) { + return; + } + if ($this->container instanceof $class) { + return; + } + $file = $r->getFileName(); + if (!$file || $this->doExport($file) === $exportedFile = $this->export($file)) { + return; + } + + if ($parent = $r->getParentClass()) { + $this->collectLineage($parent->name, $lineage); + } + + foreach ($r->getInterfaces() as $parent) { + $this->collectLineage($parent->name, $lineage); + } + + foreach ($r->getTraits() as $parent) { + $this->collectLineage($parent->name, $lineage); + } + + $lineage[$class] = substr($exportedFile, 1, -1); + } + + private function generateProxyClasses() + { + $definitions = $this->container->getDefinitions(); $strip = '' === $this->docStar && method_exists('Symfony\Component\HttpKernel\Kernel', 'stripComments'); - + $proxyDumper = $this->getProxyDumper(); + ksort($definitions); foreach ($definitions as $definition) { - $proxyCode = "\n".$this->getProxyDumper()->getProxyCode($definition); + if (!$proxyDumper->isProxyCandidate($definition)) { + continue; + } + // register class' reflector for resource tracking + $this->container->getReflectionClass($definition->getClass()); + $proxyCode = "\n".$proxyDumper->getProxyCode($definition); if ($strip) { $proxyCode = "<?php\n".$proxyCode; $proxyCode = substr(Kernel::stripComments($proxyCode), 5); } - $code .= $proxyCode; + yield sprintf('%s.php', explode(' ', $proxyCode, 3)[1]) => $proxyCode; } - - return $code; } /** * Generates the require_once statement for service includes. * - * @param Definition $definition - * * @return string */ - private function addServiceInclude($definition) + private function addServiceInclude($cId, Definition $definition, \SplObjectStorage $inlinedDefinitions) { - $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() && is_string($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)) + && is_string($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(" include_once %s;\n", $file); + } } - foreach ($this->getInlinedDefinitions($definition) as $definition) { - if (null !== $file = $definition->getFile()) { - $code .= sprintf($template, $this->dumpValue($file)); + foreach ($inlinedDefinitions as $def) { + if ($file = $def->getFile()) { + $code .= sprintf(" include_once %s;\n", $this->dumpValue($file)); } } @@ -296,97 +466,69 @@ /** * Generates the inline definition of a service. * - * @param string $id - * @param Definition $definition - * * @return string * * @throws RuntimeException When the factory definition is incomplete * @throws ServiceCircularReferenceException When a circular reference is detected */ - private function addServiceInlinedDefinitions($id, $definition) + private function addServiceInlinedDefinitions($id, Definition $definition, \SplObjectStorage $inlinedDefinitions, &$isSimpleInstance) { $code = ''; - $variableMap = $this->definitionVariables; - $nbOccurrences = new \SplObjectStorage(); - $processed = new \SplObjectStorage(); - $inlinedDefinitions = $this->getInlinedDefinitions($definition); - 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 $sDefinition) { - if ($processed->contains($sDefinition)) { + foreach ($inlinedDefinitions as $def) { + if ($definition === $def) { continue; } - $processed->offsetSet($sDefinition); + if ($inlinedDefinitions[$def] <= 1 && !$def->getMethodCalls() && !$def->getProperties() && !$def->getConfigurator() && false === strpos($this->dumpValue($def->getClass()), '$')) { + continue; + } + if (isset($this->definitionVariables[$def])) { + $name = $this->definitionVariables[$def]; + } else { + $name = $this->getNextVariableName(); + $this->definitionVariables[$def] = new Variable($name); + } - $class = $this->dumpValue($sDefinition->getClass()); - if ($nbOccurrences->offsetGet($sDefinition) > 1 || $sDefinition->getMethodCalls() || $sDefinition->getProperties() || null !== $sDefinition->getConfigurator() || false !== strpos($class, '$')) { - $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 (isset($inlinedDefinition[$definition]) && $this->hasReference($id, array($def->getArguments(), $def->getFactory()))) { + throw new ServiceCircularReferenceException($id, array($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, $sDefinition->getArguments())) { - throw new ServiceCircularReferenceException($id, array($id)); - } + $code .= $this->addNewInstance($def, '$'.$name, ' = ', $id); - $code .= $this->addNewInstance($sDefinition, '$'.$name, ' = ', $id); + 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; + } - 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 .= "\n"; - } + $code .= "\n"; } return $code; } /** - * Adds the service return statement. - * - * @param string $id Service id - * @param Definition $definition - * - * @return string - */ - private function addServiceReturn($id, $definition) - { - if ($this->isSimpleInstance($id, $definition)) { - return " }\n"; - } - - return "\n return \$instance;\n }\n"; - } - - /** * Generates the service instance. * * @param string $id * @param Definition $definition + * @param bool $isSimpleInstance * * @return string * * @throws InvalidArgumentException * @throws RuntimeException */ - private function addServiceInstance($id, Definition $definition) + private function addServiceInstance($id, Definition $definition, $isSimpleInstance) { $class = $this->dumpValue($definition->getClass()); @@ -394,18 +536,17 @@ throw new InvalidArgumentException(sprintf('"%s" is not a valid class name for the "%s" service.', $class, $id)); } - $simple = $this->isSimpleInstance($id, $definition); $isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition); $instantiation = ''; if (!$isProxyCandidate && $definition->isShared()) { - $instantiation = "\$this->services['$id'] = ".($simple ? '' : '$instance'); - } elseif (!$simple) { + $instantiation = "\$this->services['$id'] = ".($isSimpleInstance ? '' : '$instance'); + } elseif (!$isSimpleInstance) { $instantiation = '$instance'; } $return = ''; - if ($simple) { + if ($isSimpleInstance) { $return = 'return '; } else { $instantiation .= ' = '; @@ -413,7 +554,7 @@ $code = $this->addNewInstance($definition, $return, $instantiation, $id); - if (!$simple) { + if (!$isSimpleInstance) { $code .= "\n"; } @@ -421,25 +562,51 @@ } /** - * Checks if the definition is a simple instance. + * Checks if the definition is a trivial instance. * - * @param string $id * @param Definition $definition * * @return bool */ - private function isSimpleInstance($id, Definition $definition) + private function isTrivialInstance(Definition $definition) { - foreach (array_merge(array($definition), $this->getInlinedDefinitions($definition)) as $sDefinition) { - if ($definition !== $sDefinition && !$this->hasReference($id, $sDefinition->getMethodCalls())) { + if ($definition->isSynthetic() || $definition->getFile() || $definition->getMethodCalls() || $definition->getProperties() || $definition->getConfigurator()) { + return false; + } + if ($definition->isDeprecated() || $definition->isLazy() || $definition->getFactory() || 3 < count($definition->getArguments())) { + return false; + } + + foreach ($definition->getArguments() as $arg) { + if (!$arg || $arg instanceof Parameter) { continue; } - - if ($sDefinition->getMethodCalls() || $sDefinition->getProperties() || $sDefinition->getConfigurator()) { + if (is_array($arg) && 3 >= count($arg)) { + foreach ($arg as $k => $v) { + if ($this->dumpValue($k) !== $this->dumpValue($k, false)) { + return false; + } + if (!$v || $v instanceof Parameter) { + continue; + } + if ($v instanceof Reference && $this->container->has($id = (string) $v) && $this->container->findDefinition($id)->isSynthetic()) { + continue; + } + if (!is_scalar($v) || $this->dumpValue($v) !== $this->dumpValue($v, false)) { + return false; + } + } + } elseif ($arg instanceof Reference && $this->container->has($id = (string) $arg) && $this->container->findDefinition($id)->isSynthetic()) { + continue; + } elseif (!is_scalar($arg) || $this->dumpValue($arg) !== $this->dumpValue($arg, false)) { return false; } } + if (false !== strpos($this->dumpLiteralClass($this->dumpValue($definition->getClass())), '$')) { + return false; + } + return true; } @@ -479,42 +646,33 @@ /** * Generates the inline definition setup. * - * @param string $id - * @param Definition $definition - * * @return string * * @throws ServiceCircularReferenceException when the container contains a circular reference */ - private function addServiceInlinedDefinitionsSetup($id, Definition $definition) + private function addServiceInlinedDefinitionsSetup($id, Definition $definition, \SplObjectStorage $inlinedDefinitions, $isSimpleInstance) { $this->referenceVariables[$id] = new Variable('instance'); $code = ''; - $processed = new \SplObjectStorage(); - foreach ($this->getInlinedDefinitions($definition) 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; } // if the instance is simple, the return statement has already been generated // so, the only possible way to get there is because of a circular reference - if ($this->isSimpleInstance($id, $definition)) { + if ($isSimpleInstance) { 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"; } @@ -551,7 +709,7 @@ return sprintf(" (%s)->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); } - return sprintf(" call_user_func(array(%s, '%s'), \$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); + return sprintf(" \\call_user_func(array(%s, '%s'), \$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); } return sprintf(" %s(\$%s);\n", $callable, $variableName); @@ -562,10 +720,11 @@ * * @param string $id * @param Definition $definition + * @param string &$file * * @return string */ - private function addService($id, Definition $definition) + private function addService($id, Definition $definition, &$file = null) { $this->definitionVariables = new \SplObjectStorage(); $this->referenceVariables = array(); @@ -573,9 +732,7 @@ $return = array(); - if ($definition->isSynthetic()) { - $return[] = '@throws RuntimeException always since this service is expected to be injected dynamically'; - } elseif ($class = $definition->getClass()) { + if ($class = $definition->getClass()) { $class = $this->container->resolveEnvPlaceholders($class); $return[] = sprintf(0 === strpos($class, '%') ? '@return object A %1$s instance' : '@return \%s', ltrim($class, '\\')); } elseif ($definition->getFactory()) { @@ -612,42 +769,66 @@ $lazyInitialization = ''; } - // with proxies, for 5.3.3 compatibility, the getter must be public to be accessible to the initializer - $isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition); - $visibility = $isProxyCandidate ? 'public' : 'protected'; + $asFile = $this->asFiles && $definition->isShared() && !$this->isHotPath($definition); $methodName = $this->generateMethodName($id); - $code = <<<EOF + if ($asFile) { + $file = $methodName.'.php'; + $code = " // Returns the $public '$id'$shared$autowired service.\n\n"; + } else { + $code = <<<EOF /*{$this->docStar} * Gets the $public '$id'$shared$autowired service. * * $return */ - {$visibility} function {$methodName}($lazyInitialization) + protected function {$methodName}($lazyInitialization) { EOF; + } - $code .= $isProxyCandidate ? $this->getProxyDumper()->getProxyFactoryCode($definition, $id, $methodName) : ''; + if ($this->getProxyDumper()->isProxyCandidate($definition)) { + $factoryCode = $asFile ? "\$this->load('%s.php', false)" : '$this->%s(false)'; + $code .= $this->getProxyDumper()->getProxyFactoryCode($definition, $id, sprintf($factoryCode, $methodName)); + } - if ($definition->isSynthetic()) { - $code .= sprintf(" throw new RuntimeException('You have requested a synthetic service (\"%s\"). The DIC does not know how to construct this service.');\n }\n", $id); + if ($definition->isDeprecated()) { + $code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", $this->export($definition->getDeprecationMessage($id))); + } + + $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($id, $definition, $inlinedDefinitions). + $this->addServiceLocalTempVariables($id, $definition, $constructorDefinitions, $inlinedDefinitions). + $this->addServiceInlinedDefinitions($id, $definition, $constructorDefinitions, $isSimpleInstance). + $this->addServiceInstance($id, $definition, $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). + (!$isSimpleInstance ? "\n return \$instance;\n" : '') + ; + + if ($asFile) { + $code = implode("\n", array_map(function ($line) { return $line ? substr($line, 8) : $line; }, explode("\n", $code))); } else { - if ($definition->isDeprecated()) { - $code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", $this->export($definition->getDeprecationMessage($id))); - } - - $code .= - $this->addServiceInclude($definition). - $this->addServiceLocalTempVariables($id, $definition). - $this->addServiceInlinedDefinitions($id, $definition). - $this->addServiceInstance($id, $definition). - $this->addServiceInlinedDefinitionsSetup($id, $definition). - $this->addServiceProperties($definition). - $this->addServiceMethodCalls($definition). - $this->addServiceConfigurator($definition). - $this->addServiceReturn($id, $definition) - ; + $code .= " }\n"; } $this->definitionVariables = null; @@ -667,6 +848,9 @@ $definitions = $this->container->getDefinitions(); ksort($definitions); foreach ($definitions as $id => $definition) { + if ($definition->isSynthetic() || ($this->asFiles && $definition->isShared() && !$this->isHotPath($definition))) { + continue; + } if ($definition->isPublic()) { $publicServices .= $this->addService($id, $definition); } else { @@ -677,9 +861,22 @@ return $publicServices.$privateServices; } + private function generateServiceFiles() + { + $definitions = $this->container->getDefinitions(); + ksort($definitions); + foreach ($definitions as $id => $definition) { + if (!$definition->isSynthetic() && $definition->isShared() && !$this->isHotPath($definition)) { + $code = $this->addService($id, $definition, $file); + yield $file => $code; + } + } + } + private function addNewInstance(Definition $definition, $return, $instantiation, $id) { $class = $this->dumpValue($definition->getClass()); + $return = ' '.$return.$instantiation; $arguments = array(); foreach ($definition->getArguments() as $value) { @@ -695,7 +892,7 @@ if ($callable[0] instanceof Reference || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))) { - return sprintf(" $return{$instantiation}%s->%s(%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : ''); + return $return.sprintf("%s->%s(%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : ''); } $class = $this->dumpValue($callable[0]); @@ -705,43 +902,44 @@ throw new RuntimeException(sprintf('Cannot dump definition: The "%s" service is defined to be created by a factory but is missing the service reference, did you forget to define the factory service id or class?', $id)); } - return sprintf(" $return{$instantiation}%s::%s(%s);\n", $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : ''); + return $return.sprintf("%s::%s(%s);\n", $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : ''); } if (0 === strpos($class, 'new ')) { - return sprintf(" $return{$instantiation}(%s)->%s(%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : ''); + return $return.sprintf("(%s)->%s(%s);\n", $class, $callable[1], $arguments ? implode(', ', $arguments) : ''); } - return sprintf(" $return{$instantiation}call_user_func(array(%s, '%s')%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? ', '.implode(', ', $arguments) : ''); + return $return.sprintf("\\call_user_func(array(%s, '%s')%s);\n", $class, $callable[1], $arguments ? ', '.implode(', ', $arguments) : ''); } - return sprintf(" $return{$instantiation}%s(%s);\n", $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : ''); + return $return.sprintf("%s(%s);\n", $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : ''); } if (false !== strpos($class, '$')) { - return sprintf(" \$class = %s;\n\n $return{$instantiation}new \$class(%s);\n", $class, implode(', ', $arguments)); + return sprintf(" \$class = %s;\n\n%snew \$class(%s);\n", $class, $return, implode(', ', $arguments)); } - return sprintf(" $return{$instantiation}new %s(%s);\n", $this->dumpLiteralClass($class), implode(', ', $arguments)); + return $return.sprintf("new %s(%s);\n", $this->dumpLiteralClass($class), implode(', ', $arguments)); } /** * Adds the class headers. * - * @param string $class Class name - * @param string $baseClass The name of the base class - * @param string $namespace The class namespace + * @param string $class Class name + * @param string $baseClass The name of the base class + * @param string $baseClassWithNamespace Fully qualified base class name * * @return string */ - private function startClass($class, $baseClass, $namespace) + private function startClass($class, $baseClass, $baseClassWithNamespace) { - $bagClass = $this->container->isFrozen() ? 'use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;' : 'use Symfony\Component\DependencyInjection\ParameterBag\\ParameterBag;'; - $namespaceLine = $namespace ? "\nnamespace $namespace;\n" : ''; + $bagClass = $this->container->isCompiled() ? 'use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;' : 'use Symfony\Component\DependencyInjection\ParameterBag\\ParameterBag;'; + $namespaceLine = !$this->asFiles && $this->namespace ? "\nnamespace {$this->namespace};\n" : ''; - return <<<EOF + $code = <<<EOF <?php $namespaceLine +use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\DependencyInjection\Container; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; @@ -750,122 +948,202 @@ $bagClass /*{$this->docStar} - * $class. - * * This class has been auto-generated * by the Symfony Dependency Injection Component. + * + * @final since Symfony 3.3 */ class $class extends $baseClass { private \$parameters; private \$targetDirs = array(); -EOF; - } - - /** - * Adds the constructor. - * - * @return string - */ - private function addConstructor() + public function __construct() { - $targetDirs = $this->exportTargetDirs(); - $arguments = $this->container->getParameterBag()->all() ? 'new ParameterBag($this->getDefaultParameters())' : null; - - $code = <<<EOF - - /*{$this->docStar} - * Constructor. - */ - public function __construct() - {{$targetDirs} - parent::__construct($arguments); EOF; + if (null !== $this->targetDirRegex) { + $dir = $this->asFiles ? '$this->targetDirs[0] = \\dirname($containerDir)' : '__DIR__'; + $code .= <<<EOF + \$dir = {$dir}; + for (\$i = 1; \$i <= {$this->targetDirMaxMatches}; ++\$i) { + \$this->targetDirs[\$i] = \$dir = \\dirname(\$dir); + } +EOF; + } + if ($this->asFiles) { + $code = str_replace('$parameters', "\$buildParameters;\n private \$containerDir;\n private \$parameters", $code); + $code = str_replace('__construct()', '__construct(array $buildParameters = array(), $containerDir = __DIR__)', $code); + $code .= " \$this->buildParameters = \$buildParameters;\n"; + $code .= " \$this->containerDir = \$containerDir;\n"; + } + + if ($this->container->isCompiled()) { + if (Container::class !== $baseClassWithNamespace) { + $r = $this->container->getReflectionClass($baseClassWithNamespace, false); + if (null !== $r + && (null !== $constructor = $r->getConstructor()) + && 0 === $constructor->getNumberOfRequiredParameters() + && Container::class !== $constructor->getDeclaringClass()->name + ) { + $code .= " parent::__construct();\n"; + $code .= " \$this->parameterBag = null;\n\n"; + } + } + + if ($this->container->getParameterBag()->all()) { + $code .= " \$this->parameters = \$this->getDefaultParameters();\n\n"; + } + + $code .= " \$this->services = array();\n"; + } else { + $arguments = $this->container->getParameterBag()->all() ? 'new ParameterBag($this->getDefaultParameters())' : null; + $code .= " parent::__construct($arguments);\n"; + } + + $code .= $this->addNormalizedIds(); + $code .= $this->addSyntheticIds(); $code .= $this->addMethodMap(); + $code .= $this->asFiles ? $this->addFileMap() : ''; $code .= $this->addPrivateServices(); $code .= $this->addAliases(); - + $code .= $this->addInlineRequires(); $code .= <<<'EOF' } EOF; + $code .= $this->addRemovedIds(); + + if ($this->container->isCompiled()) { + $code .= <<<EOF + + public function compile() + { + throw new LogicException('You cannot compile a dumped container that was already compiled.'); + } + + public function isCompiled() + { + return true; + } + + public function isFrozen() + { + @trigger_error(sprintf('The %s() method is deprecated since Symfony 3.3 and will be removed in 4.0. Use the isCompiled() method instead.', __METHOD__), E_USER_DEPRECATED); + + return true; + } + +EOF; + } + + if ($this->asFiles) { + $code .= <<<EOF + + protected function load(\$file, \$lazyLoad = true) + { + return require \$this->containerDir.\\DIRECTORY_SEPARATOR.\$file; + } + +EOF; + } + + $proxyDumper = $this->getProxyDumper(); + foreach ($this->container->getDefinitions() as $definition) { + if (!$proxyDumper->isProxyCandidate($definition)) { + continue; + } + if ($this->asFiles) { + $proxyLoader = '$this->load("{$class}.php")'; + } elseif ($this->namespace) { + $proxyLoader = 'class_alias("'.$this->namespace.'\\\\{$class}", $class, false)'; + } else { + $proxyLoader = ''; + } + if ($proxyLoader) { + $proxyLoader = "class_exists(\$class, false) || {$proxyLoader};\n\n "; + } + $code .= <<<EOF + + protected function createProxy(\$class, \Closure \$factory) + { + {$proxyLoader}return \$factory(); + } + +EOF; + break; + } return $code; } /** - * Adds the constructor for a frozen container. + * Adds the normalizedIds property definition. * * @return string */ - private function addFrozenConstructor() + private function addNormalizedIds() { - $targetDirs = $this->exportTargetDirs(); - - $code = <<<EOF - - /*{$this->docStar} - * Constructor. - */ - public function __construct() - {{$targetDirs} -EOF; - - if ($this->container->getParameterBag()->all()) { - $code .= "\n \$this->parameters = \$this->getDefaultParameters();\n"; + $code = ''; + $normalizedIds = $this->container->getNormalizedIds(); + ksort($normalizedIds); + foreach ($normalizedIds as $id => $normalizedId) { + if ($this->container->has($normalizedId)) { + $code .= ' '.$this->doExport($id).' => '.$this->doExport($normalizedId).",\n"; + } } - $code .= "\n \$this->services = array();\n"; - $code .= $this->addMethodMap(); - $code .= $this->addPrivateServices(); - $code .= $this->addAliases(); - - $code .= <<<'EOF' - } - -EOF; - - return $code; + return $code ? " \$this->normalizedIds = array(\n".$code." );\n" : ''; } /** - * Adds the constructor for a frozen container. + * Adds the syntheticIds definition. * * @return string */ - private function addFrozenCompile() + private function addSyntheticIds() { - return <<<EOF + $code = ''; + $definitions = $this->container->getDefinitions(); + ksort($definitions); + foreach ($definitions as $id => $definition) { + if ($definition->isSynthetic() && 'service_container' !== $id) { + $code .= ' '.$this->doExport($id)." => true,\n"; + } + } - /*{$this->docStar} - * {@inheritdoc} - */ - public function compile() - { - throw new LogicException('You cannot compile a dumped frozen container.'); - } - -EOF; + return $code ? " \$this->syntheticIds = array(\n{$code} );\n" : ''; } /** - * Adds the isFrozen method for a frozen container. + * Adds the removedIds definition. * * @return string */ - private function addIsFrozenMethod() + private function addRemovedIds() { + if (!$ids = $this->container->getRemovedIds()) { + return ''; + } + if ($this->asFiles) { + $code = "require \$this->containerDir.\\DIRECTORY_SEPARATOR.'removed-ids.php'"; + } else { + $code = ''; + $ids = array_keys($ids); + sort($ids); + foreach ($ids as $id) { + $code .= ' '.$this->doExport($id)." => true,\n"; + } + + $code = "array(\n{$code} )"; + } + return <<<EOF - /*{$this->docStar} - * {@inheritdoc} - */ - public function isFrozen() + public function getRemovedIds() { - return true; + return {$code}; } EOF; @@ -878,17 +1156,35 @@ */ private function addMethodMap() { - if (!$definitions = $this->container->getDefinitions()) { - return ''; + $code = ''; + $definitions = $this->container->getDefinitions(); + ksort($definitions); + foreach ($definitions as $id => $definition) { + if (!$definition->isSynthetic() && (!$this->asFiles || !$definition->isShared() || $this->isHotPath($definition))) { + $code .= ' '.$this->doExport($id).' => '.$this->doExport($this->generateMethodName($id)).",\n"; + } } - $code = " \$this->methodMap = array(\n"; + return $code ? " \$this->methodMap = array(\n{$code} );\n" : ''; + } + + /** + * Adds the fileMap property definition. + * + * @return string + */ + private function addFileMap() + { + $code = ''; + $definitions = $this->container->getDefinitions(); ksort($definitions); foreach ($definitions as $id => $definition) { - $code .= ' '.$this->export($id).' => '.$this->export($this->generateMethodName($id)).",\n"; + if (!$definition->isSynthetic() && $definition->isShared() && !$this->isHotPath($definition)) { + $code .= sprintf(" %s => '%s.php',\n", $this->doExport($id), $this->generateMethodName($id)); + } } - return $code." );\n"; + return $code ? " \$this->fileMap = array(\n{$code} );\n" : ''; } /** @@ -898,15 +1194,21 @@ */ private function addPrivateServices() { - if (!$definitions = $this->container->getDefinitions()) { - return ''; + $code = ''; + + $aliases = $this->container->getAliases(); + ksort($aliases); + foreach ($aliases as $id => $alias) { + if ($alias->isPrivate()) { + $code .= ' '.$this->doExport($id)." => true,\n"; + } } - $code = ''; + $definitions = $this->container->getDefinitions(); ksort($definitions); foreach ($definitions as $id => $definition) { if (!$definition->isPublic()) { - $code .= ' '.$this->export($id)." => true,\n"; + $code .= ' '.$this->doExport($id)." => true,\n"; } } @@ -929,22 +1231,53 @@ private function addAliases() { if (!$aliases = $this->container->getAliases()) { - return $this->container->isFrozen() ? "\n \$this->aliases = array();\n" : ''; + return $this->container->isCompiled() ? "\n \$this->aliases = array();\n" : ''; } $code = " \$this->aliases = array(\n"; ksort($aliases); foreach ($aliases as $alias => $id) { - $id = (string) $id; + $id = $this->container->normalizeId($id); while (isset($aliases[$id])) { - $id = (string) $aliases[$id]; + $id = $this->container->normalizeId($aliases[$id]); } - $code .= ' '.$this->export($alias).' => '.$this->export($id).",\n"; + $code .= ' '.$this->doExport($alias).' => '.$this->doExport($id).",\n"; } return $code." );\n"; } + private function addInlineRequires() + { + if (!$this->hotPathTag || !$this->inlineRequires) { + return ''; + } + + $lineage = array(); + + foreach ($this->container->findTaggedServiceIds($this->hotPathTag) as $id => $tags) { + $definition = $this->container->getDefinition($id); + $inlinedDefinitions = $this->getDefinitionsFromArguments(array($definition)); + + foreach ($inlinedDefinitions as $def) { + if (is_string($class = is_array($factory = $def->getFactory()) && is_string($factory[0]) ? $factory[0] : $def->getClass())) { + $this->collectLineage($class, $lineage); + } + } + } + + $code = ''; + + foreach ($lineage as $file) { + if (!isset($this->inlinedRequires[$file])) { + $this->inlinedRequires[$file] = true; + $code .= sprintf("\n include_once %s;", $file); + } + } + + return $code ? sprintf("\n \$this->privates['service_container'] = function () {%s\n };\n", $code) : ''; + } + /** * Adds default parameters method. * @@ -958,15 +1291,19 @@ $php = array(); $dynamicPhp = array(); + $normalizedParams = array(); foreach ($this->container->getParameterBag()->all() as $key => $value) { if ($key !== $resolvedKey = $this->container->resolveEnvPlaceholders($key)) { throw new InvalidArgumentException(sprintf('Parameter name cannot use env parameters: %s.', $resolvedKey)); } + if ($key !== $lcKey = strtolower($key)) { + $normalizedParams[] = sprintf(' %s => %s,', $this->export($lcKey), $this->export($key)); + } $export = $this->exportParameters(array($value)); $export = explode('0 => ', substr(rtrim($export, " )\n"), 7, -1), 2); - if (preg_match("/\\\$this->(?:getEnv\('\w++'\)|targetDirs\[\d++\])/", $export[1])) { + if (preg_match("/\\\$this->(?:getEnv\('(?:\w++:)*+\w++'\)|targetDirs\[\d++\])/", $export[1])) { $dynamicPhp[$key] = sprintf('%scase %s: $value = %s; break;', $export[0], $this->export($key), $export[1]); } else { $php[] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]); @@ -975,18 +1312,21 @@ $parameters = sprintf("array(\n%s\n%s)", implode("\n", $php), str_repeat(' ', 8)); $code = ''; - if ($this->container->isFrozen()) { + if ($this->container->isCompiled()) { $code .= <<<'EOF' - /** - * {@inheritdoc} - */ public function getParameter($name) { - $name = strtolower($name); + $name = (string) $name; + if (isset($this->buildParameters[$name])) { + return $this->buildParameters[$name]; + } + if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters))) { + $name = $this->normalizeParameterName($name); - if (!(isset($this->parameters[$name]) || array_key_exists($name, $this->parameters) || isset($this->loadedDynamicParameters[$name]))) { - throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name)); + if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters))) { + throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name)); + } } if (isset($this->loadedDynamicParameters[$name])) { return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); @@ -995,27 +1335,22 @@ return $this->parameters[$name]; } - /** - * {@inheritdoc} - */ public function hasParameter($name) { - $name = strtolower($name); + $name = (string) $name; + if (isset($this->buildParameters[$name])) { + return true; + } + $name = $this->normalizeParameterName($name); - return isset($this->parameters[$name]) || array_key_exists($name, $this->parameters) || isset($this->loadedDynamicParameters[$name]); + return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters); } - /** - * {@inheritdoc} - */ public function setParameter($name, $value) { throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); } - /** - * {@inheritdoc} - */ public function getParameterBag() { if (null === $this->parameterBag) { @@ -1023,6 +1358,9 @@ foreach ($this->loadedDynamicParameters as $name => $loaded) { $parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); } + foreach ($this->buildParameters as $name => $value) { + $parameters[$name] = $value; + } $this->parameterBag = new FrozenParameterBag($parameters); } @@ -1030,8 +1368,8 @@ } EOF; - if ('' === $this->docStar) { - $code = str_replace('/**', '/*', $code); + if (!$this->asFiles) { + $code = preg_replace('/^.*buildParameters.*\n.*\n.*\n/m', '', $code); } if ($dynamicPhp) { @@ -1070,6 +1408,26 @@ {$getDynamicParameter} } + +EOF; + + $code .= ' private $normalizedParameterNames = '.($normalizedParams ? sprintf("array(\n%s\n );", implode("\n", $normalizedParams)) : 'array();')."\n"; + $code .= <<<'EOF' + + private function normalizeParameterName($name) + { + if (isset($this->normalizedParameterNames[$normalizedName = strtolower($name)]) || isset($this->parameters[$normalizedName]) || array_key_exists($normalizedName, $this->parameters)) { + $normalizedName = isset($this->normalizedParameterNames[$normalizedName]) ? $this->normalizedParameterNames[$normalizedName] : $normalizedName; + if ((string) $name !== $normalizedName) { + @trigger_error(sprintf('Parameter names will be made case sensitive in Symfony 4.0. Using "%s" instead of "%s" is deprecated since Symfony 3.4.', $name, $normalizedName), E_USER_DEPRECATED); + } + } else { + $normalizedName = $this->normalizedParameterNames[$normalizedName] = (string) $name; + } + + return $normalizedName; + } + EOF; } elseif ($dynamicPhp) { throw new RuntimeException('You cannot dump a not-frozen container with dynamic parameters.'); @@ -1109,6 +1467,8 @@ foreach ($parameters as $key => $value) { if (is_array($value)) { $value = $this->exportParameters($value, $path.'/'.$key, $indent + 4); + } elseif ($value instanceof ArgumentInterface) { + throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain special arguments. "%s" found in "%s".', get_class($value), $path.'/'.$key)); } elseif ($value instanceof Variable) { throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain variable references. Variable "%s" found in "%s".', $value, $path.'/'.$key)); } elseif ($value instanceof Definition) { @@ -1150,12 +1510,33 @@ */ private function wrapServiceConditionals($value, $code) { - if (!$services = ContainerBuilder::getServiceConditionals($value)) { + if (!$condition = $this->getServiceConditionals($value)) { return $code; } + // re-indent the wrapped code + $code = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $code))); + + return sprintf(" if (%s) {\n%s }\n", $condition, $code); + } + + /** + * Get the conditions to execute for conditional services. + * + * @param string $value + * + * @return null|string + */ + private function getServiceConditionals($value) + { $conditions = array(); - foreach ($services as $service) { + foreach (ContainerBuilder::getInitializedConditionals($value) as $service) { + if (!$this->container->hasDefinition($service)) { + return 'false'; + } + $conditions[] = sprintf("isset(\$this->services['%s'])", $service); + } + foreach (ContainerBuilder::getServiceConditionals($value) as $service) { if ($this->container->hasDefinition($service) && !$this->container->getDefinition($service)->isPublic()) { continue; } @@ -1164,89 +1545,61 @@ } if (!$conditions) { - return $code; + return ''; } - // re-indent the wrapped code - $code = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $code))); - - return sprintf(" if (%s) {\n%s }\n", implode(' && ', $conditions), $code); + return implode(' && ', $conditions); } /** * Builds service calls from arguments. - * - * @param array $arguments - * @param array &$calls By reference - * @param array &$behavior By reference */ - private function getServiceCallsFromArguments(array $arguments, array &$calls, array &$behavior) + private function getServiceCallsFromArguments(array $arguments, array &$calls, $isPreInstance, $callerId, array &$behavior = array(), $step = 1) { foreach ($arguments as $argument) { if (is_array($argument)) { - $this->getServiceCallsFromArguments($argument, $calls, $behavior); + $this->getServiceCallsFromArguments($argument, $calls, $isPreInstance, $callerId, $behavior, $step); } elseif ($argument instanceof Reference) { - $id = (string) $argument; + $id = $this->container->normalizeId($argument); if (!isset($calls[$id])) { - $calls[$id] = 0; + $calls[$id] = (int) ($isPreInstance && isset($this->circularReferences[$callerId][$id])); } if (!isset($behavior[$id])) { $behavior[$id] = $argument->getInvalidBehavior(); - } elseif (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $behavior[$id]) { - $behavior[$id] = $argument->getInvalidBehavior(); + } else { + $behavior[$id] = min($behavior[$id], $argument->getInvalidBehavior()); } - ++$calls[$id]; + $calls[$id] += $step; } } } - /** - * Returns the inline definition. - * - * @param Definition $definition - * - * @return array - */ - private function getInlinedDefinitions(Definition $definition) + private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage $definitions = null) { - 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); - } - - /** - * Gets the definition from arguments. - * - * @param array $arguments - * - * @return array - */ - private function getDefinitionsFromArguments(array $arguments) - { - $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; } } @@ -1265,34 +1618,44 @@ */ private function hasReference($id, array $arguments, $deep = false, array &$visited = array()) { + if (!isset($this->circularReferences[$id])) { + return false; + } + foreach ($arguments as $argument) { if (is_array($argument)) { if ($this->hasReference($id, $argument, $deep, $visited)) { return true; } + + continue; } elseif ($argument instanceof Reference) { - $argumentId = (string) $argument; + $argumentId = $this->container->normalizeId($argument); if ($id === $argumentId) { return true; } - if ($deep && !isset($visited[$argumentId]) && 'service_container' !== $argumentId) { - $visited[$argumentId] = true; + if (!$deep || isset($visited[$argumentId]) || !isset($this->circularReferences[$id][$argumentId])) { + continue; + } - $service = $this->container->getDefinition($argumentId); + $visited[$argumentId] = true; - // if the proxy manager is enabled, disable searching for references in lazy services, - // as these services will be instantiated lazily and don't have direct related references. - if ($service->isLazy() && !$this->getProxyDumper() instanceof NullDumper) { - continue; - } + $service = $this->container->getDefinition($argumentId); + } elseif ($argument instanceof Definition) { + $service = $argument; + } else { + continue; + } - $arguments = array_merge($service->getMethodCalls(), $service->getArguments(), $service->getProperties()); + // if the proxy manager is enabled, disable searching for references in lazy services, + // as these services will be instantiated lazily and don't have direct related references. + if ($service->isLazy() && !$this->getProxyDumper() instanceof NullDumper) { + continue; + } - if ($this->hasReference($id, $arguments, $deep, $visited)) { - return true; - } - } + if ($this->hasReference($id, array($service->getArguments(), $service->getFactory(), $service->getProperties(), $service->getMethodCalls(), $service->getConfigurator()), $deep, $visited)) { + return true; } } @@ -1312,15 +1675,68 @@ private function dumpValue($value, $interpolate = true) { if (is_array($value)) { + if ($value && $interpolate && false !== $param = array_search($value, $this->container->getParameterBag()->all(), true)) { + return $this->dumpValue("%$param%"); + } $code = array(); foreach ($value as $k => $v) { $code[] = sprintf('%s => %s', $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate)); } return sprintf('array(%s)', implode(', ', $code)); + } elseif ($value instanceof ArgumentInterface) { + $scope = array($this->definitionVariables, $this->referenceVariables, $this->variableCount); + $this->definitionVariables = $this->referenceVariables = null; + + try { + if ($value instanceof ServiceClosureArgument) { + $value = $value->getValues()[0]; + $code = $this->dumpValue($value, $interpolate); + + if ($value instanceof TypedReference) { + $code = sprintf('$f = function (\\%s $v%s) { return $v; }; return $f(%s);', $value->getType(), ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $value->getInvalidBehavior() ? ' = null' : '', $code); + } else { + $code = sprintf('return %s;', $code); + } + + return sprintf("function () {\n %s\n }", $code); + } + + if ($value instanceof IteratorArgument) { + $operands = array(0); + $code = array(); + $code[] = 'new RewindableGenerator(function () {'; + + if (!$values = $value->getValues()) { + $code[] = ' return new \EmptyIterator();'; + } else { + $countCode = array(); + $countCode[] = 'function () {'; + + foreach ($values as $k => $v) { + ($c = $this->getServiceConditionals($v)) ? $operands[] = "(int) ($c)" : ++$operands[0]; + $v = $this->wrapServiceConditionals($v, sprintf(" yield %s => %s;\n", $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate))); + foreach (explode("\n", $v) as $v) { + if ($v) { + $code[] = ' '.$v; + } + } + } + + $countCode[] = sprintf(' return %s;', implode(' + ', $operands)); + $countCode[] = ' }'; + } + + $code[] = sprintf(' }, %s)', count($operands) > 1 ? implode("\n", $countCode) : $operands[0]); + + return implode("\n", $code); + } + } finally { + list($this->definitionVariables, $this->referenceVariables, $this->variableCount) = $scope; + } } 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.'); @@ -1349,16 +1765,21 @@ throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s)', $factory[1] ?: 'n/a')); } + $class = $this->dumpValue($factory[0]); if (is_string($factory[0])) { - return sprintf('%s::%s(%s)', $this->dumpLiteralClass($this->dumpValue($factory[0])), $factory[1], implode(', ', $arguments)); + return sprintf('%s::%s(%s)', $this->dumpLiteralClass($class), $factory[1], implode(', ', $arguments)); } if ($factory[0] instanceof Definition) { - return sprintf("call_user_func(array(%s, '%s')%s)", $this->dumpValue($factory[0]), $factory[1], count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); + if (0 === strpos($class, 'new ')) { + return sprintf('(%s)->%s(%s)', $class, $factory[1], implode(', ', $arguments)); + } + + return sprintf("\\call_user_func(array(%s, '%s')%s)", $class, $factory[1], count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); } if ($factory[0] instanceof Reference) { - return sprintf('%s->%s(%s)', $this->dumpValue($factory[0]), $factory[1], implode(', ', $arguments)); + return sprintf('%s->%s(%s)', $class, $factory[1], implode(', ', $arguments)); } } @@ -1374,11 +1795,12 @@ } elseif ($value instanceof Variable) { return '$'.$value; } elseif ($value instanceof Reference) { - if (null !== $this->referenceVariables && isset($this->referenceVariables[$id = (string) $value])) { + $id = $this->container->normalizeId($value); + if (null !== $this->referenceVariables && isset($this->referenceVariables[$id])) { return $this->dumpValue($this->referenceVariables[$id], $interpolate); } - return $this->getServiceCall((string) $value, $value); + return $this->getServiceCall($id, $value); } elseif ($value instanceof Expression) { return $this->getExpressionLanguage()->compile((string) $value, array('this' => 'container')); } elseif ($value instanceof Parameter) { @@ -1387,10 +1809,10 @@ if (preg_match('/^%([^%]+)%$/', $value, $match)) { // we do this to deal with non string values (Boolean, integer, ...) // the preg_replace_callback converts them to strings - return $this->dumpParameter(strtolower($match[1])); + return $this->dumpParameter($match[1]); } else { $replaceParameters = function ($match) { - return "'.".$this->dumpParameter(strtolower($match[2])).".'"; + return "'.".$this->dumpParameter($match[2]).".'"; }; $code = str_replace('%%', '%', preg_replace_callback('/(?<!%)(%)([^%]+)\1/', $replaceParameters, $this->export($value))); @@ -1436,11 +1858,20 @@ */ private function dumpParameter($name) { - if ($this->container->isFrozen() && $this->container->hasParameter($name)) { - return $this->dumpValue($this->container->getParameter($name), false); + if ($this->container->isCompiled() && $this->container->hasParameter($name)) { + $value = $this->container->getParameter($name); + $dumpedValue = $this->dumpValue($value, false); + + if (!$value || !is_array($value)) { + return $dumpedValue; + } + + if (!preg_match("/\\\$this->(?:getEnv\('(?:\w++:)*+\w++'\)|targetDirs\[\d++\])/", $dumpedValue)) { + return sprintf("\$this->parameters['%s']", $name); + } } - return sprintf("\$this->getParameter('%s')", strtolower($name)); + return sprintf("\$this->getParameter('%s')", $name); } /** @@ -1456,21 +1887,36 @@ while ($this->container->hasAlias($id)) { $id = (string) $this->container->getAlias($id); } + $id = $this->container->normalizeId($id); if ('service_container' === $id) { return '$this'; } - if ($this->container->hasDefinition($id) && !$this->container->getDefinition($id)->isPublic()) { - // The following is PHP 5.5 syntax for what could be written as "(\$this->services['$id'] ?? \$this->{$this->generateMethodName($id)}())" on PHP>=7.0 - - return "\${(\$_ = isset(\$this->services['$id']) ? \$this->services['$id'] : \$this->{$this->generateMethodName($id)}()) && false ?: '_'}"; - } - if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) { - return sprintf('$this->get(\'%s\', ContainerInterface::NULL_ON_INVALID_REFERENCE)', $id); + if ($this->container->hasDefinition($id) && ($definition = $this->container->getDefinition($id)) && !$definition->isSynthetic()) { + if (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) { + $code = 'null'; + } elseif ($this->isTrivialInstance($definition)) { + $code = substr($this->addNewInstance($definition, '', '', $id), 8, -2); + if ($definition->isShared()) { + $code = sprintf('$this->services[\'%s\'] = %s', $id, $code); + } + } elseif ($this->asFiles && $definition->isShared() && !$this->isHotPath($definition)) { + $code = sprintf("\$this->load('%s.php')", $this->generateMethodName($id)); + } else { + $code = sprintf('$this->%s()', $this->generateMethodName($id)); + } + } elseif (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) { + return 'null'; + } elseif (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) { + $code = sprintf('$this->get(\'%s\', /* ContainerInterface::NULL_ON_INVALID_REFERENCE */ %d)', $id, ContainerInterface::NULL_ON_INVALID_REFERENCE); + } else { + $code = sprintf('$this->get(\'%s\')', $id); } - return sprintf('$this->get(\'%s\')', $id); + // The following is PHP 5.5 syntax for what could be written as "(\$this->services['$id'] ?? $code)" on PHP>=7.0 + + return "\${(\$_ = isset(\$this->services['$id']) ? \$this->services['$id'] : $code) && false ?: '_'}"; } /** @@ -1483,12 +1929,10 @@ $this->serviceIdToMethodNameMap = array(); $this->usedMethodNames = array(); - try { - $reflectionClass = new \ReflectionClass($class); + if ($reflectionClass = $this->container->getReflectionClass($class)) { foreach ($reflectionClass->getMethods() as $method) { $this->usedMethodNames[strtolower($method->getName())] = true; } - } catch (\ReflectionException $e) { } } @@ -1507,7 +1951,8 @@ return $this->serviceIdToMethodNameMap[$id]; } - $name = Container::camelize($id); + $i = strrpos($id, '\\'); + $name = Container::camelize(false !== $i && isset($id[1 + $i]) ? substr($id, 1 + $i) : $id); $name = preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '', $name); $methodName = 'get'.$name.'Service'; $suffix = 1; @@ -1588,26 +2033,21 @@ return $this->expressionLanguage; } - private function exportTargetDirs() + private function isHotPath(Definition $definition) { - return null === $this->targetDirRegex ? '' : <<<EOF - - \$dir = __DIR__; - for (\$i = 1; \$i <= {$this->targetDirMaxMatches}; ++\$i) { - \$this->targetDirs[\$i] = \$dir = dirname(\$dir); - } -EOF; + return $this->hotPathTag && $definition->hasTag($this->hotPathTag) && !$definition->isDeprecated(); } private function export($value) { if (null !== $this->targetDirRegex && is_string($value) && preg_match($this->targetDirRegex, $value, $matches, PREG_OFFSET_CAPTURE)) { - $prefix = $matches[0][1] ? $this->doExport(substr($value, 0, $matches[0][1])).'.' : ''; + $prefix = $matches[0][1] ? $this->doExport(substr($value, 0, $matches[0][1]), true).'.' : ''; $suffix = $matches[0][1] + strlen($matches[0][0]); - $suffix = isset($value[$suffix]) ? '.'.$this->doExport(substr($value, $suffix)) : ''; - $dirname = '__DIR__'; + $suffix = isset($value[$suffix]) ? '.'.$this->doExport(substr($value, $suffix), true) : ''; + $dirname = $this->asFiles ? '$this->containerDir' : '__DIR__'; + $offset = 1 + $this->targetDirMaxMatches - count($matches); - if (0 < $offset = 1 + $this->targetDirMaxMatches - count($matches)) { + if ($this->asFiles || 0 < $offset) { $dirname = sprintf('$this->targetDirs[%d]', $offset); } @@ -1618,21 +2058,30 @@ return $dirname; } - return $this->doExport($value); + return $this->doExport($value, true); } - private function doExport($value) + private function doExport($value, $resolveEnv = false) { - $export = var_export($value, true); + if (is_string($value) && false !== strpos($value, "\n")) { + $cleanParts = explode("\n", $value); + $cleanParts = array_map(function ($part) { return var_export($part, true); }, $cleanParts); + $export = implode('."\n".', $cleanParts); + } else { + $export = var_export($value, true); + } - if ("'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$this->getEnv('%s').'")) { + if ($resolveEnv && "'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$this->getEnv('string:%s').'")) { $export = $resolvedExport; + if (".''" === substr($export, -3)) { + $export = substr($export, 0, -3); + if ("'" === $export[1]) { + $export = substr_replace($export, '', 18, 7); + } + } if ("'" === $export[1]) { $export = substr($export, 3); } - if (".''" === substr($export, -3)) { - $export = substr($export, 0, -3); - } } return $export;