X-Git-Url: http://aleph1.co.uk/gitweb/?a=blobdiff_plain;f=vendor%2Fsymfony%2Fdependency-injection%2FDumper%2FPhpDumper.php;h=fb12f99dace63b861bab3c2a5fc2e73bcd7af771;hb=4e1bfbf98b844da83b18aca92ef00f11a4735806;hp=6c131aae1977003f0a8986033c42d13659d91864;hpb=eba34333e3c89f208d2f72fa91351ad019a71583;p=yaffs-website diff --git a/vendor/symfony/dependency-injection/Dumper/PhpDumper.php b/vendor/symfony/dependency-injection/Dumper/PhpDumper.php index 6c131aae1..fb12f99da 100644 --- a/vendor/symfony/dependency-injection/Dumper/PhpDumper.php +++ b/vendor/symfony/dependency-injection/Dumper/PhpDumper.php @@ -11,21 +11,27 @@ namespace Symfony\Component\DependencyInjection\Dumper; -use Symfony\Component\DependencyInjection\Variable; -use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; +use Symfony\Component\DependencyInjection\Argument\IteratorArgument; +use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; +use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass; +use Symfony\Component\DependencyInjection\Compiler\CheckCircularReferencesPass; use Symfony\Component\DependencyInjection\Container; +use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\ContainerInterface; -use Symfony\Component\DependencyInjection\Reference; -use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\Definition; +use Symfony\Component\DependencyInjection\Exception\EnvParameterException; use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; use Symfony\Component\DependencyInjection\Exception\RuntimeException; use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; +use Symfony\Component\DependencyInjection\ExpressionLanguage; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface as ProxyDumper; use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper; -use Symfony\Component\DependencyInjection\ExpressionLanguage; +use Symfony\Component\DependencyInjection\Parameter; +use Symfony\Component\DependencyInjection\Reference; +use Symfony\Component\DependencyInjection\TypedReference; +use Symfony\Component\DependencyInjection\Variable; use Symfony\Component\ExpressionLanguage\Expression; -use Symfony\Component\ExpressionLanguage\ExpressionFunctionProviderInterface; use Symfony\Component\HttpKernel\Kernel; /** @@ -38,35 +44,35 @@ class PhpDumper extends Dumper { /** * 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; - private $reservedVariables = array('instance', 'class'); + private $inlinedDefinitions; + private $serviceCalls; + private $reservedVariables = array('instance', 'class', 'this'); private $expressionLanguage; private $targetDirRegex; private $targetDirMaxMatches; private $docStar; + private $serviceIdToMethodNameMap; + private $usedMethodNames; + private $namespace; + private $asFiles; + private $hotPathTag; + private $inlineRequires; + private $inlinedRequires = array(); + private $circularReferences = array(); /** - * @var ExpressionFunctionProviderInterface[] - */ - private $expressionLanguageProviders = array(); - - /** - * @var \Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface + * @var ProxyDumper */ private $proxyDumper; @@ -75,15 +81,15 @@ class PhpDumper extends Dumper */ public function __construct(ContainerBuilder $container) { - parent::__construct($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); + } - $this->inlinedDefinitions = new \SplObjectStorage(); + parent::__construct($container); } /** * Sets the dumper to be used when dumping proxies in the generated container. - * - * @param ProxyDumper $proxyDumper */ public function setProxyDumper(ProxyDumper $proxyDumper) { @@ -98,29 +104,74 @@ class PhpDumper extends Dumper * * 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|array A PHP class representing the service container or an array of PHP files if the "as_files" option is set * - * @return string A PHP class representing of the service container + * @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->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); + + if ($this->getProxyDumper() instanceof NullDumper) { + (new AnalyzeServiceReferencesPass(true, false))->process($this->container); + try { + (new CheckCircularReferencesPass())->process($this->container); + } catch (ServiceCircularReferenceException $e) { + $path = $e->getPath(); + end($path); + $path[key($path)] .= '". Try running "composer require symfony/proxy-manager-bridge'; + + throw new ServiceCircularReferenceException($e->getServiceId(), $path); + } + } + + (new AnalyzeServiceReferencesPass(false))->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'] ? '*' : ''; - if (!empty($options['file']) && is_dir($dir = dirname($options['file']))) { + if (!empty($options['file']) && is_dir($dir = \dirname($options['file']))) { // Build a regexp where the first root dirs are mandatory, // but every other sub-dir is optional up to the full path in $dir // Mandate at least 2 root dirs and not more that 5 optional dirs. - $dir = explode(DIRECTORY_SEPARATOR, realpath($dir)); - $i = count($dir); + $dir = explode(\DIRECTORY_SEPARATOR, realpath($dir)); + $i = \count($dir); if (3 <= $i) { $regex = ''; @@ -128,34 +179,106 @@ class PhpDumper extends Dumper $this->targetDirMaxMatches = $i - $lastOptionalDir; while (--$i >= $lastOptionalDir) { - $regex = sprintf('(%s%s)?', preg_quote(DIRECTORY_SEPARATOR.$dir[$i], '#'), $regex); + $regex = sprintf('(%s%s)?', preg_quote(\DIRECTORY_SEPARATOR.$dir[$i], '#'), $regex); } do { - $regex = preg_quote(DIRECTORY_SEPARATOR.$dir[$i], '#').$regex; + $regex = preg_quote(\DIRECTORY_SEPARATOR.$dir[$i], '#').$regex; } while (0 < --$i); $this->targetDirRegex = '#'.preg_quote($dir[0], '#').$regex.'#'; } } - $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->asFiles) { + $fileStart = <<container->getRemovedIds())) { + sort($ids); + $c = "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] = " $c) { + $code["Container{$hash}/{$file}"] = $c; + } + array_pop($code); + $code["Container{$hash}/{$options['class']}.php"] = substr_replace($files[$options['class'].'.php'], "namespace ? "\nnamespace {$this->namespace};\n" : ''; + $time = $options['build_time']; + $id = hash('crc32', $hash.$time); + + $code[$options['class'].'.php'] = << '$hash', + 'container.build_id' => '$id', + 'container.build_time' => $time, +), __DIR__.\\DIRECTORY_SEPARATOR.'Container{$hash}'); - if ($this->container->isFrozen()) { - $code .= $this->addFrozenConstructor(); - $code .= $this->addFrozenCompile(); - $code .= $this->addIsFrozenMethod(); +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) { + if (!$use) { + $unusedEnvs[] = $env; + } + } + if ($unusedEnvs) { + throw new EnvParameterException($unusedEnvs, null, 'Environment variables "%s" are never used. Please, check your container\'s configuration.'); + } return $code; } @@ -174,194 +297,131 @@ class PhpDumper extends Dumper return $this->proxyDumper; } - /** - * Generates Service local temp variables. - * - * @param string $cId - * @param string $definition - * - * @return string - */ - private function addServiceLocalTempVariables($cId, $definition) + private function analyzeCircularReferences(array $edges, &$checkedNodes, &$currentPath) { - static $template = " \$%s = %s;\n"; - - $localDefinitions = array_merge( - array($definition), - $this->getInlinedDefinitions($definition) - ); - - $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); + 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]); + } } + } - $code = ''; - foreach ($calls as $id => $callCount) { - if ('service_container' === $id || $id === $cId) { - continue; - } + 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 ($callCount > 1) { - $name = $this->getNextVariableName(); - $this->referenceVariables[$id] = new Variable($name); + if ($parent = $r->getParentClass()) { + $this->collectLineage($parent->name, $lineage); + } - 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))); - } - } + foreach ($r->getInterfaces() as $parent) { + $this->collectLineage($parent->name, $lineage); } - if ('' !== $code) { - $code .= "\n"; + foreach ($r->getTraits() as $parent) { + $this->collectLineage($parent->name, $lineage); } - return $code; + $lineage[$class] = substr($exportedFile, 1, -1); } - /** - * Generates code for the proxies to be attached after the container class. - * - * @return string - */ - private function addProxyClasses() + private function generateProxyClasses() { - /* @var $definitions Definition[] */ - $definitions = array_filter( - $this->container->getDefinitions(), - array($this->getProxyDumper(), 'isProxyCandidate') - ); - $code = ''; + $alreadyGenerated = array(); + $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; + } + if (isset($alreadyGenerated[$class = $definition->getClass()])) { + continue; + } + $alreadyGenerated[$class] = true; + // register class' reflector for resource tracking + $this->container->getReflectionClass($class); + if ("\n" === $proxyCode = "\n".$proxyDumper->getProxyCode($definition)) { + continue; + } if ($strip) { $proxyCode = " $proxyCode; } - - return $code; } /** * Generates the require_once statement for service includes. * - * @param string $id The service id - * @param Definition $definition - * * @return string */ - private function addServiceInclude($id, $definition) + private function addServiceInclude($cId, Definition $definition) { - $template = " require_once %s;\n"; $code = ''; - if (null !== $file = $definition->getFile()) { - $code .= sprintf($template, $this->dumpValue($file)); - } - - foreach ($this->getInlinedDefinitions($definition) as $definition) { - if (null !== $file = $definition->getFile()) { - $code .= sprintf($template, $this->dumpValue($file)); + if ($this->inlineRequires && !$this->isHotPath($definition)) { + $lineage = array(); + foreach ($this->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); + } } - } - if ('' !== $code) { - $code .= "\n"; - } - - return $code; - } - - /** - * 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) - { - $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 ($this->serviceCalls as $id => list($callCount, $behavior)) { + if ('service_container' !== $id && $id !== $cId + && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $behavior + && $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 ($inlinedDefinitions as $sDefinition) { - if ($processed->contains($sDefinition)) { - continue; + foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) { + $code .= sprintf(" include_once %s;\n", $file); } - $processed->offsetSet($sDefinition); - - $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 ($this->hasReference($id, $sDefinition->getArguments())) { - throw new ServiceCircularReferenceException($id, array($id)); - } - - $code .= $this->addNewInstance($id, $sDefinition, '$'.$name, ' = '); - - if (!$this->hasReference($id, $sDefinition->getMethodCalls(), true) && !$this->hasReference($id, $sDefinition->getProperties(), true)) { - $code .= $this->addServiceProperties(null, $sDefinition, $name); - $code .= $this->addServiceMethodCalls(null, $sDefinition, $name); - $code .= $this->addServiceConfigurator(null, $sDefinition, $name); - } + } - $code .= "\n"; + foreach ($this->inlinedDefinitions as $def) { + if ($file = $def->getFile()) { + $code .= sprintf(" include_once %s;\n", $this->dumpValue($file)); } } - 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"; + if ('' !== $code) { + $code .= "\n"; } - return "\n return \$instance;\n }\n"; + return $code; } /** @@ -369,70 +429,78 @@ class PhpDumper extends Dumper * * @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 = $definition->getClass(); - - if ('\\' === substr($class, 0, 1)) { - $class = substr($class, 1); - } - - $class = $this->dumpValue($class); + $class = $this->dumpValue($definition->getClass()); - if (0 === strpos($class, "'") && !preg_match('/^\'[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) { + if (0 === strpos($class, "'") && false === strpos($class, '$') && !preg_match('/^\'(?:\\\{2})?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) { 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() && ContainerInterface::SCOPE_CONTAINER === $definition->getScope(false)) { - $instantiation = "\$this->services['$id'] = ".($simple ? '' : '$instance'); - } elseif (!$isProxyCandidate && $definition->isShared() && ContainerInterface::SCOPE_PROTOTYPE !== $scope = $definition->getScope(false)) { - $instantiation = "\$this->services['$id'] = \$this->scopedServices['$scope']['$id'] = ".($simple ? '' : '$instance'); - } elseif (!$simple) { + if (!$isProxyCandidate && $definition->isShared()) { + $instantiation = "\$this->services['$id'] = ".($isSimpleInstance ? '' : '$instance'); + } elseif (!$isSimpleInstance) { $instantiation = '$instance'; } $return = ''; - if ($simple) { + if ($isSimpleInstance) { $return = 'return '; } else { $instantiation .= ' = '; } - $code = $this->addNewInstance($id, $definition, $return, $instantiation); - - if (!$simple) { - $code .= "\n"; - } - - return $code; + return $this->addNewInstance($definition, $return, $instantiation, $id); } /** - * 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; } } @@ -443,13 +511,12 @@ class PhpDumper extends Dumper /** * Adds method calls to a service definition. * - * @param string $id * @param Definition $definition * @param string $variableName * * @return string */ - private function addServiceMethodCalls($id, Definition $definition, $variableName = 'instance') + private function addServiceMethodCalls(Definition $definition, $variableName = 'instance') { $calls = ''; foreach ($definition->getMethodCalls() as $call) { @@ -464,7 +531,7 @@ class PhpDumper extends Dumper return $calls; } - private function addServiceProperties($id, Definition $definition, $variableName = 'instance') + private function addServiceProperties(Definition $definition, $variableName = 'instance') { $code = ''; foreach ($definition->getProperties() as $name => $value) { @@ -474,67 +541,21 @@ class PhpDumper extends Dumper return $code; } - /** - * 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) - { - $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)) { - 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)) { - throw new ServiceCircularReferenceException($id, array($id)); - } - - $name = (string) $this->definitionVariables->offsetGet($iDefinition); - $code .= $this->addServiceProperties(null, $iDefinition, $name); - $code .= $this->addServiceMethodCalls(null, $iDefinition, $name); - $code .= $this->addServiceConfigurator(null, $iDefinition, $name); - } - - if ('' !== $code) { - $code .= "\n"; - } - - return $code; - } - /** * Adds configurator definition. * - * @param string $id * @param Definition $definition * @param string $variableName * * @return string */ - private function addServiceConfigurator($id, Definition $definition, $variableName = 'instance') + private function addServiceConfigurator(Definition $definition, $variableName = 'instance') { if (!$callable = $definition->getConfigurator()) { return ''; } - if (is_array($callable)) { + if (\is_array($callable)) { if ($callable[0] instanceof Reference || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))) { return sprintf(" %s->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); @@ -542,11 +563,15 @@ class PhpDumper extends Dumper $class = $this->dumpValue($callable[0]); // If the class is a string we can optimize call_user_func away - if (strpos($class, "'") === 0) { + if (0 === strpos($class, "'") && false === strpos($class, '$')) { return sprintf(" %s::%s(\$%s);\n", $this->dumpLiteralClass($class), $callable[1], $variableName); } - return sprintf(" call_user_func(array(%s, '%s'), \$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); + if (0 === strpos($class, 'new ')) { + 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(" %s(\$%s);\n", $callable, $variableName); @@ -557,48 +582,35 @@ class PhpDumper extends Dumper * * @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(); $this->variableCount = 0; + $this->definitionVariables[$definition] = $this->referenceVariables[$id] = new Variable('instance'); $return = array(); - if ($definition->isSynthetic()) { - $return[] = '@throws RuntimeException always since this service is expected to be injected dynamically'; - } elseif ($class = $definition->getClass()) { - $return[] = sprintf('@return %s A %s instance', 0 === strpos($class, '%') ? 'object' : '\\'.ltrim($class, '\\'), ltrim($class, '\\')); + if ($class = $definition->getClass()) { + $class = $class instanceof Parameter ? '%'.$class.'%' : $this->container->resolveEnvPlaceholders($class); + $return[] = sprintf(0 === strpos($class, '%') ? '@return object A %1$s instance' : '@return \%s', ltrim($class, '\\')); } elseif ($definition->getFactory()) { $factory = $definition->getFactory(); - if (is_string($factory)) { + if (\is_string($factory)) { $return[] = sprintf('@return object An instance returned by %s()', $factory); - } elseif (is_array($factory) && (is_string($factory[0]) || $factory[0] instanceof Definition || $factory[0] instanceof Reference)) { - if (is_string($factory[0]) || $factory[0] instanceof Reference) { - $return[] = sprintf('@return object An instance returned by %s::%s()', (string) $factory[0], $factory[1]); - } elseif ($factory[0] instanceof Definition) { - $return[] = sprintf('@return object An instance returned by %s::%s()', $factory[0]->getClass(), $factory[1]); - } + } elseif (\is_array($factory) && (\is_string($factory[0]) || $factory[0] instanceof Definition || $factory[0] instanceof Reference)) { + $class = $factory[0] instanceof Definition ? $factory[0]->getClass() : (string) $factory[0]; + $class = $class instanceof Parameter ? '%'.$class.'%' : $this->container->resolveEnvPlaceholders($class); + $return[] = sprintf('@return object An instance returned by %s::%s()', $class, $factory[1]); } - } elseif ($definition->getFactoryClass(false)) { - $return[] = sprintf('@return object An instance returned by %s::%s()', $definition->getFactoryClass(false), $definition->getFactoryMethod(false)); - } elseif ($definition->getFactoryService(false)) { - $return[] = sprintf('@return object An instance returned by %s::%s()', $definition->getFactoryService(false), $definition->getFactoryMethod(false)); - } - - $scope = $definition->getScope(false); - if (!in_array($scope, array(ContainerInterface::SCOPE_CONTAINER, ContainerInterface::SCOPE_PROTOTYPE))) { - if ($return && 0 === strpos($return[count($return) - 1], '@return')) { - $return[] = ''; - } - $return[] = sprintf("@throws InactiveScopeException when the '%s' service is requested while the '%s' scope is not active", $id, $scope); } if ($definition->isDeprecated()) { - if ($return && 0 === strpos($return[count($return) - 1], '@return')) { + if ($return && 0 === strpos($return[\count($return) - 1], '@return')) { $return[] = ''; } @@ -606,94 +618,173 @@ class PhpDumper extends Dumper } $return = str_replace("\n * \n", "\n *\n", implode("\n * ", $return)); + $return = $this->container->resolveEnvPlaceholders($return); - $doc = ''; - if ($definition->isShared() && ContainerInterface::SCOPE_PROTOTYPE !== $scope) { - $doc .= <<<'EOF' + $shared = $definition->isShared() ? ' shared' : ''; + $public = $definition->isPublic() ? 'public' : 'private'; + $autowired = $definition->isAutowired() ? ' autowired' : ''; - * - * This service is shared. - * This method always returns the same instance of the service. -EOF; + if ($definition->isLazy()) { + $lazyInitialization = '$lazyLoad = true'; + } else { + $lazyInitialization = ''; } - if (!$definition->isPublic()) { - $doc .= <<<'EOF' + $asFile = $this->asFiles && $definition->isShared() && !$this->isHotPath($definition); + $methodName = $this->generateMethodName($id); + if ($asFile) { + $file = $methodName.'.php'; + $code = " // Returns the $public '$id'$shared$autowired service.\n\n"; + } else { + $code = <<docStar} + * Gets the $public '$id'$shared$autowired service. * - * This service is private. - * If you want to be able to request this service from the container directly, - * make it public, otherwise you might end up with broken code. + * $return + */ + protected function {$methodName}($lazyInitialization) + { + EOF; } - if ($definition->isAutowired()) { - $doc .= <<serviceCalls = array(); + $this->inlinedDefinitions = $this->getDefinitionsFromArguments(array($definition), null, $this->serviceCalls); - * - * This service is autowired. -EOF; + $code .= $this->addServiceInclude($id, $definition); + + 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->isLazy()) { - $lazyInitialization = '$lazyLoad = true'; - $lazyInitializationDoc = "\n * @param bool \$lazyLoad whether to try lazy-loading the service with a proxy\n *"; + if ($definition->isDeprecated()) { + $code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", $this->export($definition->getDeprecationMessage($id))); + } + + $head = $tail = ''; + $arguments = array($definition->getArguments(), $definition->getFactory()); + $this->addInlineVariables($head, $tail, $id, $arguments, true); + $code .= '' !== $head ? $head."\n" : ''; + + if ($arguments = array_filter(array($definition->getProperties(), $definition->getMethodCalls(), $definition->getConfigurator()))) { + $this->addInlineVariables($tail, $tail, $id, $arguments, false); + + $tail .= '' !== $tail ? "\n" : ''; + $tail .= $this->addServiceProperties($definition); + $tail .= $this->addServiceMethodCalls($definition); + $tail .= $this->addServiceConfigurator($definition); + } + + $code .= $this->addServiceInstance($id, $definition, '' === $tail) + .('' !== $tail ? "\n".$tail."\n return \$instance;\n" : ''); + + if ($asFile) { + $code = implode("\n", array_map(function ($line) { return $line ? substr($line, 8) : $line; }, explode("\n", $code))); } else { - $lazyInitialization = ''; - $lazyInitializationDoc = ''; + $code .= " }\n"; } - // 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'; - $code = <<definitionVariables = $this->inlinedDefinitions = null; + $this->referenceVariables = $this->serviceCalls = null; - /*{$this->docStar} - * Gets the '$id' service.$doc - *$lazyInitializationDoc - * $return - */ - {$visibility} function get{$this->camelize($id)}Service($lazyInitialization) + return $code; + } + + private function addInlineVariables(&$head, &$tail, $id, array $arguments, $forConstructor) { + $hasSelfRef = false; -EOF; + foreach ($arguments as $argument) { + if (\is_array($argument)) { + $hasSelfRef = $this->addInlineVariables($head, $tail, $id, $argument, $forConstructor) || $hasSelfRef; + } elseif ($argument instanceof Reference) { + $hasSelfRef = $this->addInlineReference($head, $id, $this->container->normalizeId($argument), $forConstructor) || $hasSelfRef; + } elseif ($argument instanceof Definition) { + $hasSelfRef = $this->addInlineService($head, $tail, $id, $argument, $forConstructor) || $hasSelfRef; + } + } - $code .= $isProxyCandidate ? $this->getProxyDumper()->getProxyFactoryCode($definition, $id) : ''; + return $hasSelfRef; + } - if (!in_array($scope, array(ContainerInterface::SCOPE_CONTAINER, ContainerInterface::SCOPE_PROTOTYPE))) { - $code .= <<scopedServices['$scope'])) { - throw new InactiveScopeException('$id', '$scope'); + private function addInlineReference(&$code, $id, $targetId, $forConstructor) + { + $hasSelfRef = isset($this->circularReferences[$id][$targetId]); + + if ('service_container' === $targetId || isset($this->referenceVariables[$targetId])) { + return $hasSelfRef; } + list($callCount, $behavior) = $this->serviceCalls[$targetId]; -EOF; + if (2 > $callCount && (!$hasSelfRef || !$forConstructor)) { + return $hasSelfRef; } - 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); - } else { - if ($definition->isDeprecated()) { - $code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", var_export($definition->getDeprecationMessage($id), true)); - } + $name = $this->getNextVariableName(); + $this->referenceVariables[$targetId] = new Variable($name); + + $reference = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $behavior ? new Reference($targetId, $behavior) : null; + $code .= sprintf(" \$%s = %s;\n", $name, $this->getServiceCall($targetId, $reference)); - $code .= - $this->addServiceInclude($id, $definition). - $this->addServiceLocalTempVariables($id, $definition). - $this->addServiceInlinedDefinitions($id, $definition). - $this->addServiceInstance($id, $definition). - $this->addServiceInlinedDefinitionsSetup($id, $definition). - $this->addServiceProperties($id, $definition). - $this->addServiceMethodCalls($id, $definition). - $this->addServiceConfigurator($id, $definition). - $this->addServiceReturn($id, $definition) - ; + if (!$hasSelfRef || !$forConstructor) { + return $hasSelfRef; } - $this->definitionVariables = null; - $this->referenceVariables = null; + $code .= sprintf(<<<'EOTXT' - return $code; + if (isset($this->%s['%s'])) { + return $this->%1$s['%2$s']; + } + +EOTXT + , + 'services', + $id + ); + + return false; + } + + private function addInlineService(&$head, &$tail, $id, Definition $definition, $forConstructor) + { + if (isset($this->definitionVariables[$definition])) { + return false; + } + + $arguments = array($definition->getArguments(), $definition->getFactory()); + + if (2 > $this->inlinedDefinitions[$definition] && !$definition->getMethodCalls() && !$definition->getProperties() && !$definition->getConfigurator()) { + return $this->addInlineVariables($head, $tail, $id, $arguments, $forConstructor); + } + + $name = $this->getNextVariableName(); + $this->definitionVariables[$definition] = new Variable($name); + + $code = ''; + if ($forConstructor) { + $hasSelfRef = $this->addInlineVariables($code, $tail, $id, $arguments, $forConstructor); + } else { + $hasSelfRef = $this->addInlineVariables($code, $code, $id, $arguments, $forConstructor); + } + $code .= $this->addNewInstance($definition, '$'.$name, ' = ', $id); + $hasSelfRef && !$forConstructor ? $tail .= ('' !== $tail ? "\n" : '').$code : $head .= ('' !== $head ? "\n" : '').$code; + + $code = ''; + $arguments = array($definition->getProperties(), $definition->getMethodCalls(), $definition->getConfigurator()); + $hasSelfRef = $this->addInlineVariables($code, $code, $id, $arguments, false) || $hasSelfRef; + + $code .= '' !== $code ? "\n" : ''; + $code .= $this->addServiceProperties($definition, $name); + $code .= $this->addServiceMethodCalls($definition, $name); + $code .= $this->addServiceConfigurator($definition, $name); + if ('' !== $code) { + $hasSelfRef ? $tail .= ('' !== $tail ? "\n" : '').$code : $head .= $code; + } + + return $hasSelfRef; } /** @@ -703,84 +794,39 @@ EOF; */ private function addServices() { - $publicServices = $privateServices = $synchronizers = ''; + $publicServices = $privateServices = ''; $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 { $privateServices .= $this->addService($id, $definition); } - - $synchronizers .= $this->addServiceSynchronizer($id, $definition); } - return $publicServices.$synchronizers.$privateServices; + return $publicServices.$privateServices; } - /** - * Adds synchronizer methods. - * - * @param string $id A service identifier - * @param Definition $definition A Definition instance - * - * @return string|null - * - * @deprecated since version 2.7, will be removed in 3.0. - */ - private function addServiceSynchronizer($id, Definition $definition) + private function generateServiceFiles() { - if (!$definition->isSynchronized(false)) { - return; - } - - if ('request' !== $id) { - @trigger_error('Synchronized services were deprecated in version 2.7 and won\'t work anymore in 3.0.', E_USER_DEPRECATED); - } - - $code = ''; - foreach ($this->container->getDefinitions() as $definitionId => $definition) { - foreach ($definition->getMethodCalls() as $call) { - foreach ($call[1] as $argument) { - if ($argument instanceof Reference && $id == (string) $argument) { - $arguments = array(); - foreach ($call[1] as $value) { - $arguments[] = $this->dumpValue($value); - } - - $call = $this->wrapServiceConditionals($call[1], sprintf("\$this->get('%s')->%s(%s);", $definitionId, $call[0], implode(', ', $arguments))); - - $code .= <<initialized('$definitionId')) { - $call - } - -EOF; - } - } + $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; } } - - if (!$code) { - return; - } - - return <<docStar} - * Updates the '$id' service. - */ - protected function synchronize{$this->camelize($id)}Service() - { -$code } - -EOF; } - private function addNewInstance($id, Definition $definition, $return, $instantiation) + private function addNewInstance(Definition $definition, $return, $instantiation, $id) { $class = $this->dumpValue($definition->getClass()); + $return = ' '.$return.$instantiation; $arguments = array(); foreach ($definition->getArguments() as $value) { @@ -789,236 +835,345 @@ EOF; if (null !== $definition->getFactory()) { $callable = $definition->getFactory(); - if (is_array($callable)) { + if (\is_array($callable)) { if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $callable[1])) { throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s)', $callable[1] ?: 'n/a')); } 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]); // If the class is a string we can optimize call_user_func away - if (strpos($class, "'") === 0) { - return sprintf(" $return{$instantiation}%s::%s(%s);\n", $this->dumpLiteralClass($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 sprintf(" $return{$instantiation}\\%s(%s);\n", $callable, $arguments ? implode(', ', $arguments) : ''); - } elseif (null !== $definition->getFactoryMethod(false)) { - if (null !== $definition->getFactoryClass(false)) { - $class = $this->dumpValue($definition->getFactoryClass(false)); + if (0 === strpos($class, "'") && false === strpos($class, '$')) { + if ("''" === $class) { + 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)); + } - // If the class is a string we can optimize call_user_func away - if (strpos($class, "'") === 0) { - return sprintf(" $return{$instantiation}%s::%s(%s);\n", $this->dumpLiteralClass($class), $definition->getFactoryMethod(false), $arguments ? implode(', ', $arguments) : ''); + return $return.sprintf("%s::%s(%s);\n", $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : ''); } - return sprintf(" $return{$instantiation}call_user_func(array(%s, '%s')%s);\n", $this->dumpValue($definition->getFactoryClass(false)), $definition->getFactoryMethod(false), $arguments ? ', '.implode(', ', $arguments) : ''); - } + if (0 === strpos($class, 'new ')) { + return $return.sprintf("(%s)->%s(%s);\n", $class, $callable[1], $arguments ? implode(', ', $arguments) : ''); + } - if (null !== $definition->getFactoryService(false)) { - return sprintf(" $return{$instantiation}%s->%s(%s);\n", $this->getServiceCall($definition->getFactoryService(false)), $definition->getFactoryMethod(false), implode(', ', $arguments)); + return $return.sprintf("\\call_user_func(array(%s, '%s')%s);\n", $class, $callable[1], $arguments ? ', '.implode(', ', $arguments) : ''); } - throw new RuntimeException(sprintf('Factory method requires a factory service or factory class in service definition for %s', $id)); + 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 <<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 = <<docStar} - * Constructor. - */ - public function __construct() - {{$targetDirs} - parent::__construct($arguments); +EOF; + if (null !== $this->targetDirRegex) { + $dir = $this->asFiles ? '$this->targetDirs[0] = \\dirname($containerDir)' : '__DIR__'; + $code .= <<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 (count($scopes = $this->container->getScopes(false)) > 0) { - $code .= "\n"; - $code .= ' $this->scopes = '.$this->dumpValue($scopes).";\n"; - $code .= ' $this->scopeChildren = '.$this->dumpValue($this->container->getScopeChildren(false)).";\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(); - return $code; + if ($this->container->isCompiled()) { + $code .= <<exportTargetDirs(); + return true; + } - $code = <<docStar} - * Constructor. - */ - public function __construct() - {{$targetDirs} -EOF; + return true; + } - if ($this->container->getParameterBag()->all()) { - $code .= "\n \$this->parameters = \$this->getDefaultParameters();\n"; +EOF; } - $code .= <<<'EOF' + if ($this->asFiles) { + $code .= <<services = - $this->scopedServices = - $this->scopeStacks = array(); -EOF; + protected function load(\$file, \$lazyLoad = true) + { + return require \$this->containerDir.\\DIRECTORY_SEPARATOR.\$file; + } - $code .= "\n"; - if (count($scopes = $this->container->getScopes(false)) > 0) { - $code .= ' $this->scopes = '.$this->dumpValue($scopes).";\n"; - $code .= ' $this->scopeChildren = '.$this->dumpValue($this->container->getScopeChildren(false)).";\n"; - } else { - $code .= " \$this->scopes = array();\n"; - $code .= " \$this->scopeChildren = array();\n"; +EOF; } - $code .= $this->addMethodMap(); - $code .= $this->addAliases(); + $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 .= <<container->getNormalizedIds(); + ksort($normalizedIds); + foreach ($normalizedIds as $id => $normalizedId) { + if ($this->container->has($normalizedId)) { + $code .= ' '.$this->doExport($id).' => '.$this->doExport($normalizedId).",\n"; + } + } + + return $code ? " \$this->normalizedIds = array(\n".$code." );\n" : ''; + } + + /** + * Adds the syntheticIds definition. + * + * @return string + */ + private function addSyntheticIds() + { + $code = ''; + $definitions = $this->container->getDefinitions(); + ksort($definitions); + foreach ($definitions as $id => $definition) { + if ($definition->isSynthetic() && 'service_container' !== $id) { + $code .= ' '.$this->doExport($id)." => true,\n"; + } + } + + return $code ? " \$this->syntheticIds = array(\n{$code} );\n" : ''; + } + + /** + * Adds the removedIds definition. * * @return string */ - private function addFrozenCompile() + 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) { + if (preg_match('/^\d+_[^~]++~[._a-zA-Z\d]{7}$/', $id)) { + continue; + } + $code .= ' '.$this->doExport($id)." => true,\n"; + } + + $code = "array(\n{$code} )"; + } + return <<docStar} - * {@inheritdoc} - */ - public function compile() + public function getRemovedIds() { - throw new LogicException('You cannot compile a dumped frozen container.'); + return {$code}; } EOF; } /** - * Adds the isFrozen method for a frozen container. + * Adds the methodMap property definition. * * @return string */ - private function addIsFrozenMethod() + private function addMethodMap() { - return <<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"; + } + } - /*{$this->docStar} - * {@inheritdoc} + return $code ? " \$this->methodMap = array(\n{$code} );\n" : ''; + } + + /** + * Adds the fileMap property definition. + * + * @return string */ - public function isFrozen() + private function addFileMap() { - return true; - } + $code = ''; + $definitions = $this->container->getDefinitions(); + ksort($definitions); + foreach ($definitions as $id => $definition) { + if (!$definition->isSynthetic() && $definition->isShared() && !$this->isHotPath($definition)) { + $code .= sprintf(" %s => '%s.php',\n", $this->doExport($id), $this->generateMethodName($id)); + } + } -EOF; + return $code ? " \$this->fileMap = array(\n{$code} );\n" : ''; } /** - * Adds the methodMap property definition. + * Adds the privates property definition. * * @return string */ - private function addMethodMap() + 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 = " \$this->methodMap = array(\n"; + $definitions = $this->container->getDefinitions(); ksort($definitions); foreach ($definitions as $id => $definition) { - $code .= ' '.var_export($id, true).' => '.var_export('get'.$this->camelize($id).'Service', true).",\n"; + if (!$definition->isPublic()) { + $code .= ' '.$this->doExport($id)." => true,\n"; + } } - return $code." );\n"; + if (empty($code)) { + return ''; + } + + $out = " \$this->privates = array(\n"; + $out .= $code; + $out .= " );\n"; + + return $out; } /** @@ -1029,22 +1184,53 @@ EOF; 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 .= ' '.var_export($alias, true).' => '.var_export($id, true).",\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. * @@ -1056,60 +1242,148 @@ EOF; return ''; } - $parameters = $this->exportParameters($this->container->getParameterBag()->all()); + $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++:)*+\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]); + } + } + $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))) { - 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); } 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); + 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) { - $this->parameterBag = new FrozenParameterBag($this->parameters); + $parameters = $this->parameters; + foreach ($this->loadedDynamicParameters as $name => $loaded) { + $parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); + } + foreach ($this->buildParameters as $name => $value) { + $parameters[$name] = $value; + } + $this->parameterBag = new FrozenParameterBag($parameters); } return $this->parameterBag; } EOF; - if ('' === $this->docStar) { - $code = str_replace('/**', '/*', $code); + if (!$this->asFiles) { + $code = preg_replace('/^.*buildParameters.*\n.*\n.*\n/m', '', $code); + } + + if ($dynamicPhp) { + $loadedDynamicParameters = $this->exportParameters(array_combine(array_keys($dynamicPhp), array_fill(0, \count($dynamicPhp), false)), '', 8); + $getDynamicParameter = <<<'EOF' + switch ($name) { +%s + default: throw new InvalidArgumentException(sprintf('The dynamic parameter "%%s" must be defined.', $name)); + } + $this->loadedDynamicParameters[$name] = true; + + return $this->dynamicParameters[$name] = $value; +EOF; + $getDynamicParameter = sprintf($getDynamicParameter, implode("\n", $dynamicPhp)); + } else { + $loadedDynamicParameters = 'array()'; + $getDynamicParameter = str_repeat(' ', 8).'throw new InvalidArgumentException(sprintf(\'The dynamic parameter "%s" must be defined.\', $name));'; + } + + $code .= <<docStar} + * Computes a dynamic parameter. + * + * @param string The name of the dynamic parameter to load + * + * @return mixed The value of the dynamic parameter + * + * @throws InvalidArgumentException When the dynamic parameter does not exist + */ + private function getDynamicParameter(\$name) + { +{$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.'); } $code .= << $value) { - if (is_array($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) { @@ -1158,7 +1434,7 @@ EOF; $value = $this->export($value); } - $php[] = sprintf('%s%s => %s,', str_repeat(' ', $indent), var_export($key, true), $value); + $php[] = sprintf('%s%s => %s,', str_repeat(' ', $indent), $this->export($key), $value); } return sprintf("array(\n%s\n%s)", implode("\n", $php), str_repeat(' ', $indent - 4)); @@ -1187,145 +1463,78 @@ EOF; */ private function wrapServiceConditionals($value, $code) { - if (!$services = ContainerBuilder::getServiceConditionals($value)) { + if (!$condition = $this->getServiceConditionals($value)) { return $code; } - $conditions = array(); - foreach ($services as $service) { - $conditions[] = sprintf("\$this->has('%s')", $service); - } - // 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 sprintf(" if (%s) {\n%s }\n", $condition, $code); } /** - * Builds service calls from arguments. + * Get the conditions to execute for conditional services. + * + * @param string $value * - * @param array $arguments - * @param array &$calls By reference - * @param array &$behavior By reference + * @return string|null */ - private function getServiceCallsFromArguments(array $arguments, array &$calls, array &$behavior) + private function getServiceConditionals($value) { - foreach ($arguments as $argument) { - if (is_array($argument)) { - $this->getServiceCallsFromArguments($argument, $calls, $behavior); - } elseif ($argument instanceof Reference) { - $id = (string) $argument; - - if (!isset($calls[$id])) { - $calls[$id] = 0; - } - if (!isset($behavior[$id])) { - $behavior[$id] = $argument->getInvalidBehavior(); - } elseif (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $behavior[$id]) { - $behavior[$id] = $argument->getInvalidBehavior(); - } - - ++$calls[$id]; + $conditions = array(); + foreach (ContainerBuilder::getInitializedConditionals($value) as $service) { + if (!$this->container->hasDefinition($service)) { + return 'false'; } + $conditions[] = sprintf("isset(\$this->services['%s'])", $service); } - } - - /** - * Returns the inline definition. - * - * @param Definition $definition - * - * @return array - */ - private function getInlinedDefinitions(Definition $definition) - { - 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())) - ); + foreach (ContainerBuilder::getServiceConditionals($value) as $service) { + if ($this->container->hasDefinition($service) && !$this->container->getDefinition($service)->isPublic()) { + continue; + } - $this->inlinedDefinitions->offsetSet($definition, $definitions); + $conditions[] = sprintf("\$this->has('%s')", $service); + } - return $definitions; + if (!$conditions) { + return ''; } - return $this->inlinedDefinitions->offsetGet($definition); + return implode(' && ', $conditions); } - /** - * Gets the definition from arguments. - * - * @param array $arguments - * - * @return array - */ - private function getDefinitionsFromArguments(array $arguments) + private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage $definitions = null, array &$calls = 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) - ); - } + if (null === $definitions) { + $definitions = new \SplObjectStorage(); } - return $definitions; - } - - /** - * Checks if a service id has a reference. - * - * @param string $id - * @param array $arguments - * @param bool $deep - * @param array $visited - * - * @return bool - */ - private function hasReference($id, array $arguments, $deep = false, array &$visited = array()) - { foreach ($arguments as $argument) { - if (is_array($argument)) { - if ($this->hasReference($id, $argument, $deep, $visited)) { - return true; - } + if (\is_array($argument)) { + $this->getDefinitionsFromArguments($argument, $definitions, $calls); } elseif ($argument instanceof Reference) { - $argumentId = (string) $argument; - if ($id === $argumentId) { - return true; - } - - if ($deep && !isset($visited[$argumentId]) && 'service_container' !== $argumentId) { - $visited[$argumentId] = true; - - $service = $this->container->getDefinition($argumentId); - - // 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; - } + $id = $this->container->normalizeId($argument); - $arguments = array_merge($service->getMethodCalls(), $service->getArguments(), $service->getProperties()); - - if ($this->hasReference($id, $arguments, $deep, $visited)) { - return true; - } + if (!isset($calls[$id])) { + $calls[$id] = array(0, $argument->getInvalidBehavior()); + } else { + $calls[$id][1] = min($calls[$id][1], $argument->getInvalidBehavior()); } + + ++$calls[$id][0]; + } elseif (!$argument instanceof Definition) { + // no-op + } elseif (isset($definitions[$argument])) { + $definitions[$argument] = 1 + $definitions[$argument]; + } else { + $definitions[$argument] = 1; + $arguments = array($argument->getArguments(), $argument->getFactory(), $argument->getProperties(), $argument->getMethodCalls(), $argument->getConfigurator()); + $this->getDefinitionsFromArguments($arguments, $definitions, $calls); } } - return false; + return $definitions; } /** @@ -1340,16 +1549,69 @@ EOF; */ private function dumpValue($value, $interpolate = true) { - if (is_array($value)) { + 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->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) = $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.'); @@ -1369,43 +1631,36 @@ EOF; if (null !== $value->getFactory()) { $factory = $value->getFactory(); - if (is_string($factory)) { - return sprintf('\\%s(%s)', $factory, implode(', ', $arguments)); + if (\is_string($factory)) { + return sprintf('%s(%s)', $this->dumpLiteralClass($this->dumpValue($factory)), implode(', ', $arguments)); } - if (is_array($factory)) { + if (\is_array($factory)) { if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $factory[1])) { throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s)', $factory[1] ?: 'n/a')); } - if (is_string($factory[0])) { - return sprintf('%s::%s(%s)', $this->dumpLiteralClass($this->dumpValue($factory[0])), $factory[1], implode(', ', $arguments)); + $class = $this->dumpValue($factory[0]); + if (\is_string($factory[0])) { + 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)); } } throw new RuntimeException('Cannot dump definition because of invalid factory'); } - if (null !== $value->getFactoryMethod(false)) { - if (null !== $value->getFactoryClass(false)) { - return sprintf("call_user_func(array(%s, '%s')%s)", $this->dumpValue($value->getFactoryClass(false)), $value->getFactoryMethod(false), count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); - } elseif (null !== $value->getFactoryService(false)) { - $service = $this->dumpValue($value->getFactoryService(false)); - - return sprintf('%s->%s(%s)', 0 === strpos($service, '$') ? sprintf('$this->get(%s)', $service) : $this->getServiceCall($value->getFactoryService(false)), $value->getFactoryMethod(false), implode(', ', $arguments)); - } - - throw new RuntimeException('Cannot dump definitions which have factory method without factory service or factory class.'); - } - $class = $value->getClass(); if (null === $class) { throw new RuntimeException('Cannot dump definitions which have no class nor factory.'); @@ -1415,31 +1670,31 @@ EOF; } 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) { return $this->dumpParameter($value); - } elseif (true === $interpolate && is_string($value)) { + } elseif (true === $interpolate && \is_string($value)) { 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 { - $that = $this; - $replaceParameters = function ($match) use ($that) { - return "'.".$that->dumpParameter(strtolower($match[2])).".'"; + $replaceParameters = function ($match) { + return "'.".$this->dumpParameter($match[2]).".'"; }; $code = str_replace('%%', '%', preg_replace_callback('/(?export($value))); return $code; } - } elseif (is_object($value) || is_resource($value)) { + } elseif (\is_object($value) || \is_resource($value)) { throw new RuntimeException('Unable to dump a service container if a parameter is an object or a resource.'); } @@ -1458,13 +1713,15 @@ EOF; private function dumpLiteralClass($class) { if (false !== strpos($class, '$')) { - throw new RuntimeException('Cannot dump definitions which have a variable class name.'); + return sprintf('${($_ = %s) && false ?: "_"}', $class); } - if (0 !== strpos($class, "'") || !preg_match('/^\'[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) { + if (0 !== strpos($class, "'") || !preg_match('/^\'(?:\\\{2})?[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*(?:\\\{2}[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*)*\'$/', $class)) { throw new RuntimeException(sprintf('Cannot dump definition because of invalid class name (%s)', $class ?: 'n/a')); } - return '\\'.substr(str_replace('\\\\', '\\', $class), 1, -1); + $class = substr(str_replace('\\\\', '\\', $class), 1, -1); + + return 0 === strpos($class, '\\') ? $class : '\\'.$class; } /** @@ -1474,26 +1731,22 @@ EOF; * * @return string */ - public function dumpParameter($name) + 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); - return sprintf("\$this->getParameter('%s')", strtolower($name)); - } + if (!$value || !\is_array($value)) { + return $dumpedValue; + } - /** - * @deprecated since version 2.6.2, to be removed in 3.0. - * Use \Symfony\Component\DependencyInjection\ContainerBuilder::addExpressionLanguageProvider instead. - * - * @param ExpressionFunctionProviderInterface $provider - */ - public function addExpressionLanguageProvider(ExpressionFunctionProviderInterface $provider) - { - @trigger_error('The '.__METHOD__.' method is deprecated since version 2.6.2 and will be removed in 3.0. Use the Symfony\Component\DependencyInjection\ContainerBuilder::addExpressionLanguageProvider method instead.', E_USER_DEPRECATED); + if (!preg_match("/\\\$this->(?:getEnv\('(?:\w++:)*+\w++'\)|targetDirs\[\d++\])/", $dumpedValue)) { + return sprintf("\$this->parameters['%s']", $name); + } + } - $this->expressionLanguageProviders[] = $provider; + return sprintf("\$this->getParameter('%s')", $name); } /** @@ -1506,19 +1759,61 @@ EOF; */ private function getServiceCall($id, Reference $reference = null) { + while ($this->container->hasAlias($id)) { + $id = (string) $this->container->getAlias($id); + } + $id = $this->container->normalizeId($id); + if ('service_container' === $id) { return '$this'; } - 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)) { + if ($definition->isSynthetic()) { + $code = sprintf('$this->get(\'%s\'%s)', $id, null !== $reference ? ', '.$reference->getInvalidBehavior() : ''); + } elseif (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) { + $code = 'null'; + if (!$definition->isShared()) { + return $code; + } + } 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); } - if ($this->container->hasAlias($id)) { - $id = (string) $this->container->getAlias($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 ?: '_'}"; + } + + /** + * Initializes the method names map to avoid conflicts with the Container methods. + * + * @param string $class the container base class + */ + private function initializeMethodNamesMap($class) + { + $this->serviceIdToMethodNameMap = array(); + $this->usedMethodNames = array(); - return sprintf('$this->get(\'%s\')', $id); + if ($reflectionClass = $this->container->getReflectionClass($class)) { + foreach ($reflectionClass->getMethods() as $method) { + $this->usedMethodNames[strtolower($method->getName())] = true; + } + } } /** @@ -1530,15 +1825,27 @@ EOF; * * @throws InvalidArgumentException */ - private function camelize($id) + private function generateMethodName($id) { - $name = Container::camelize($id); + if (isset($this->serviceIdToMethodNameMap[$id])) { + return $this->serviceIdToMethodNameMap[$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; - if (!preg_match('/^[a-zA-Z0-9_\x7f-\xff]+$/', $name)) { - throw new InvalidArgumentException(sprintf('Service id "%s" cannot be converted to a valid PHP method name.', $id)); + while (isset($this->usedMethodNames[strtolower($methodName)])) { + ++$suffix; + $methodName = 'get'.$name.$suffix.'Service'; } - return $name; + $this->serviceIdToMethodNameMap[$id] = $methodName; + $this->usedMethodNames[strtolower($methodName)] = true; + + return $methodName; } /** @@ -1549,9 +1856,9 @@ EOF; private function getNextVariableName() { $firstChars = self::FIRST_CHARS; - $firstCharsLength = strlen($firstChars); + $firstCharsLength = \strlen($firstChars); $nonFirstChars = self::NON_FIRST_CHARS; - $nonFirstCharsLength = strlen($nonFirstChars); + $nonFirstCharsLength = \strlen($nonFirstChars); while (true) { $name = ''; @@ -1571,7 +1878,7 @@ EOF; ++$this->variableCount; // check that the name is not reserved - if (in_array($name, $this->reservedVariables, true)) { + if (\in_array($name, $this->reservedVariables, true)) { continue; } @@ -1585,8 +1892,16 @@ EOF; if (!class_exists('Symfony\Component\ExpressionLanguage\ExpressionLanguage')) { throw new RuntimeException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed.'); } - $providers = array_merge($this->container->getExpressionLanguageProviders(), $this->expressionLanguageProviders); - $this->expressionLanguage = new ExpressionLanguage(null, $providers); + $providers = $this->container->getExpressionLanguageProviders(); + $this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) { + $id = '""' === substr_replace($arg, '', 1, -1) ? stripcslashes(substr($arg, 1, -1)) : null; + + if (null !== $id && ($this->container->hasAlias($id) || $this->container->hasDefinition($id))) { + return $this->getServiceCall($id); + } + + return sprintf('$this->get(%s)', $arg); + }); if ($this->container->isTrackingResources()) { foreach ($providers as $provider) { @@ -1598,26 +1913,21 @@ EOF; return $this->expressionLanguage; } - private function exportTargetDirs() + private function isHotPath(Definition $definition) { - return null === $this->targetDirRegex ? '' : <<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] ? var_export(substr($value, 0, $matches[0][1]), true).'.' : ''; - $suffix = $matches[0][1] + strlen($matches[0][0]); - $suffix = isset($value[$suffix]) ? '.'.var_export(substr($value, $suffix), true) : ''; - $dirname = '__DIR__'; - - if (0 < $offset = 1 + $this->targetDirMaxMatches - count($matches)) { + 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]), true).'.' : ''; + $suffix = $matches[0][1] + \strlen($matches[0][0]); + $suffix = isset($value[$suffix]) ? '.'.$this->doExport(substr($value, $suffix), true) : ''; + $dirname = $this->asFiles ? '$this->containerDir' : '__DIR__'; + $offset = 1 + $this->targetDirMaxMatches - \count($matches); + + if ($this->asFiles || 0 < $offset) { $dirname = sprintf('$this->targetDirs[%d]', $offset); } @@ -1628,6 +1938,32 @@ EOF; return $dirname; } - return var_export($value, true); + return $this->doExport($value, true); + } + + private function doExport($value, $resolveEnv = false) + { + 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 ($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); + } + } + + return $export; } }