comparison 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
comparison
equal deleted inserted replaced
13:5fb285c0d0e3 14:1fec387a4317
9 * file that was distributed with this source code. 9 * file that was distributed with this source code.
10 */ 10 */
11 11
12 namespace Symfony\Component\DependencyInjection\Dumper; 12 namespace Symfony\Component\DependencyInjection\Dumper;
13 13
14 use Symfony\Component\DependencyInjection\Argument\ArgumentInterface;
15 use Symfony\Component\DependencyInjection\Argument\IteratorArgument;
16 use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument;
14 use Symfony\Component\DependencyInjection\Variable; 17 use Symfony\Component\DependencyInjection\Variable;
15 use Symfony\Component\DependencyInjection\Definition; 18 use Symfony\Component\DependencyInjection\Definition;
19 use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass;
16 use Symfony\Component\DependencyInjection\ContainerBuilder; 20 use Symfony\Component\DependencyInjection\ContainerBuilder;
17 use Symfony\Component\DependencyInjection\Container; 21 use Symfony\Component\DependencyInjection\Container;
18 use Symfony\Component\DependencyInjection\ContainerInterface; 22 use Symfony\Component\DependencyInjection\ContainerInterface;
19 use Symfony\Component\DependencyInjection\Reference; 23 use Symfony\Component\DependencyInjection\Reference;
24 use Symfony\Component\DependencyInjection\TypedReference;
20 use Symfony\Component\DependencyInjection\Parameter; 25 use Symfony\Component\DependencyInjection\Parameter;
21 use Symfony\Component\DependencyInjection\Exception\EnvParameterException; 26 use Symfony\Component\DependencyInjection\Exception\EnvParameterException;
22 use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; 27 use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
23 use Symfony\Component\DependencyInjection\Exception\RuntimeException; 28 use Symfony\Component\DependencyInjection\Exception\RuntimeException;
24 use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException; 29 use Symfony\Component\DependencyInjection\Exception\ServiceCircularReferenceException;
36 */ 41 */
37 class PhpDumper extends Dumper 42 class PhpDumper extends Dumper
38 { 43 {
39 /** 44 /**
40 * Characters that might appear in the generated variable name as first character. 45 * Characters that might appear in the generated variable name as first character.
41 *
42 * @var string
43 */ 46 */
44 const FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz'; 47 const FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz';
45 48
46 /** 49 /**
47 * Characters that might appear in the generated variable name as any but the first character. 50 * Characters that might appear in the generated variable name as any but the first character.
48 *
49 * @var string
50 */ 51 */
51 const NON_FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_'; 52 const NON_FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_';
52 53
53 private $inlinedDefinitions;
54 private $definitionVariables; 54 private $definitionVariables;
55 private $referenceVariables; 55 private $referenceVariables;
56 private $variableCount; 56 private $variableCount;
57 private $reservedVariables = array('instance', 'class'); 57 private $reservedVariables = array('instance', 'class');
58 private $expressionLanguage; 58 private $expressionLanguage;
59 private $targetDirRegex; 59 private $targetDirRegex;
60 private $targetDirMaxMatches; 60 private $targetDirMaxMatches;
61 private $docStar; 61 private $docStar;
62 private $serviceIdToMethodNameMap; 62 private $serviceIdToMethodNameMap;
63 private $usedMethodNames; 63 private $usedMethodNames;
64 64 private $namespace;
65 /** 65 private $asFiles;
66 * @var \Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface 66 private $hotPathTag;
67 private $inlineRequires;
68 private $inlinedRequires = array();
69 private $circularReferences = array();
70
71 /**
72 * @var ProxyDumper
67 */ 73 */
68 private $proxyDumper; 74 private $proxyDumper;
69 75
70 /** 76 /**
71 * {@inheritdoc} 77 * {@inheritdoc}
72 */ 78 */
73 public function __construct(ContainerBuilder $container) 79 public function __construct(ContainerBuilder $container)
74 { 80 {
81 if (!$container->isCompiled()) {
82 @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);
83 }
84
75 parent::__construct($container); 85 parent::__construct($container);
76
77 $this->inlinedDefinitions = new \SplObjectStorage();
78 } 86 }
79 87
80 /** 88 /**
81 * Sets the dumper to be used when dumping proxies in the generated container. 89 * Sets the dumper to be used when dumping proxies in the generated container.
82 *
83 * @param ProxyDumper $proxyDumper
84 */ 90 */
85 public function setProxyDumper(ProxyDumper $proxyDumper) 91 public function setProxyDumper(ProxyDumper $proxyDumper)
86 { 92 {
87 $this->proxyDumper = $proxyDumper; 93 $this->proxyDumper = $proxyDumper;
88 } 94 }
93 * Available options: 99 * Available options:
94 * 100 *
95 * * class: The class name 101 * * class: The class name
96 * * base_class: The base class name 102 * * base_class: The base class name
97 * * namespace: The class namespace 103 * * namespace: The class namespace
98 * 104 * * as_files: To split the container in several files
99 * @param array $options An array of options 105 *
100 * 106 * @return string|array A PHP class representing the service container or an array of PHP files if the "as_files" option is set
101 * @return string A PHP class representing of the service container
102 * 107 *
103 * @throws EnvParameterException When an env var exists but has not been dumped 108 * @throws EnvParameterException When an env var exists but has not been dumped
104 */ 109 */
105 public function dump(array $options = array()) 110 public function dump(array $options = array())
106 { 111 {
107 $this->targetDirRegex = null; 112 $this->targetDirRegex = null;
113 $this->inlinedRequires = array();
108 $options = array_merge(array( 114 $options = array_merge(array(
109 'class' => 'ProjectServiceContainer', 115 'class' => 'ProjectServiceContainer',
110 'base_class' => 'Container', 116 'base_class' => 'Container',
111 'namespace' => '', 117 'namespace' => '',
118 'as_files' => false,
112 'debug' => true, 119 'debug' => true,
120 'hot_path_tag' => 'container.hot_path',
121 'inline_class_loader_parameter' => 'container.dumper.inline_class_loader',
122 'build_time' => time(),
113 ), $options); 123 ), $options);
114 124
115 $this->initializeMethodNamesMap($options['base_class']); 125 $this->namespace = $options['namespace'];
126 $this->asFiles = $options['as_files'];
127 $this->hotPathTag = $options['hot_path_tag'];
128 $this->inlineRequires = $options['inline_class_loader_parameter'] && $this->container->hasParameter($options['inline_class_loader_parameter']) && $this->container->getParameter($options['inline_class_loader_parameter']);
129
130 if (0 !== strpos($baseClass = $options['base_class'], '\\') && 'Container' !== $baseClass) {
131 $baseClass = sprintf('%s\%s', $options['namespace'] ? '\\'.$options['namespace'] : '', $baseClass);
132 $baseClassWithNamespace = $baseClass;
133 } elseif ('Container' === $baseClass) {
134 $baseClassWithNamespace = Container::class;
135 } else {
136 $baseClassWithNamespace = $baseClass;
137 }
138
139 $this->initializeMethodNamesMap('Container' === $baseClass ? Container::class : $baseClass);
140
141 (new AnalyzeServiceReferencesPass())->process($this->container);
142 $this->circularReferences = array();
143 $checkedNodes = array();
144 foreach ($this->container->getCompiler()->getServiceReferenceGraph()->getNodes() as $id => $node) {
145 $currentPath = array($id => $id);
146 $this->analyzeCircularReferences($node->getOutEdges(), $checkedNodes, $currentPath);
147 }
148 $this->container->getCompiler()->getServiceReferenceGraph()->clear();
116 149
117 $this->docStar = $options['debug'] ? '*' : ''; 150 $this->docStar = $options['debug'] ? '*' : '';
118 151
119 if (!empty($options['file']) && is_dir($dir = dirname($options['file']))) { 152 if (!empty($options['file']) && is_dir($dir = dirname($options['file']))) {
120 // Build a regexp where the first root dirs are mandatory, 153 // Build a regexp where the first root dirs are mandatory,
139 172
140 $this->targetDirRegex = '#'.preg_quote($dir[0], '#').$regex.'#'; 173 $this->targetDirRegex = '#'.preg_quote($dir[0], '#').$regex.'#';
141 } 174 }
142 } 175 }
143 176
144 $code = $this->startClass($options['class'], $options['base_class'], $options['namespace']); 177 $code =
145 178 $this->startClass($options['class'], $baseClass, $baseClassWithNamespace).
146 if ($this->container->isFrozen()) {
147 $code .= $this->addFrozenConstructor();
148 $code .= $this->addFrozenCompile();
149 $code .= $this->addIsFrozenMethod();
150 } else {
151 $code .= $this->addConstructor();
152 }
153
154 $code .=
155 $this->addServices(). 179 $this->addServices().
156 $this->addDefaultParametersMethod(). 180 $this->addDefaultParametersMethod().
157 $this->endClass(). 181 $this->endClass()
158 $this->addProxyClasses()
159 ; 182 ;
183
184 if ($this->asFiles) {
185 $fileStart = <<<EOF
186 <?php
187
188 use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
189
190 // This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
191
192 EOF;
193 $files = array();
194
195 if ($ids = array_keys($this->container->getRemovedIds())) {
196 sort($ids);
197 $c = "<?php\n\nreturn array(\n";
198 foreach ($ids as $id) {
199 $c .= ' '.$this->doExport($id)." => true,\n";
200 }
201 $files['removed-ids.php'] = $c .= ");\n";
202 }
203
204 foreach ($this->generateServiceFiles() as $file => $c) {
205 $files[$file] = $fileStart.$c;
206 }
207 foreach ($this->generateProxyClasses() as $file => $c) {
208 $files[$file] = "<?php\n".$c;
209 }
210 $files[$options['class'].'.php'] = $code;
211 $hash = ucfirst(strtr(ContainerBuilder::hash($files), '._', 'xx'));
212 $code = array();
213
214 foreach ($files as $file => $c) {
215 $code["Container{$hash}/{$file}"] = $c;
216 }
217 array_pop($code);
218 $code["Container{$hash}/{$options['class']}.php"] = substr_replace($files[$options['class'].'.php'], "<?php\n\nnamespace Container{$hash};\n", 0, 6);
219 $namespaceLine = $this->namespace ? "\nnamespace {$this->namespace};\n" : '';
220 $time = $options['build_time'];
221 $id = hash('crc32', $hash.$time);
222
223 $code[$options['class'].'.php'] = <<<EOF
224 <?php
225 {$namespaceLine}
226 // This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
227
228 if (\\class_exists(\\Container{$hash}\\{$options['class']}::class, false)) {
229 // no-op
230 } elseif (!include __DIR__.'/Container{$hash}/{$options['class']}.php') {
231 touch(__DIR__.'/Container{$hash}.legacy');
232
233 return;
234 }
235
236 if (!\\class_exists({$options['class']}::class, false)) {
237 \\class_alias(\\Container{$hash}\\{$options['class']}::class, {$options['class']}::class, false);
238 }
239
240 return new \\Container{$hash}\\{$options['class']}(array(
241 'container.build_hash' => '$hash',
242 'container.build_id' => '$id',
243 'container.build_time' => $time,
244 ), __DIR__.\\DIRECTORY_SEPARATOR.'Container{$hash}');
245
246 EOF;
247 } else {
248 foreach ($this->generateProxyClasses() as $c) {
249 $code .= $c;
250 }
251 }
252
160 $this->targetDirRegex = null; 253 $this->targetDirRegex = null;
254 $this->inlinedRequires = array();
255 $this->circularReferences = array();
161 256
162 $unusedEnvs = array(); 257 $unusedEnvs = array();
163 foreach ($this->container->getEnvCounters() as $env => $use) { 258 foreach ($this->container->getEnvCounters() as $env => $use) {
164 if (!$use) { 259 if (!$use) {
165 $unusedEnvs[] = $env; 260 $unusedEnvs[] = $env;
187 } 282 }
188 283
189 /** 284 /**
190 * Generates Service local temp variables. 285 * Generates Service local temp variables.
191 * 286 *
192 * @param string $cId 287 * @return string
193 * @param string $definition 288 */
194 * 289 private function addServiceLocalTempVariables($cId, Definition $definition, \SplObjectStorage $inlinedDefinitions, \SplObjectStorage $allInlinedDefinitions)
195 * @return string 290 {
196 */ 291 $allCalls = $calls = $behavior = array();
197 private function addServiceLocalTempVariables($cId, $definition) 292
198 { 293 foreach ($allInlinedDefinitions as $def) {
199 static $template = " \$%s = %s;\n"; 294 $arguments = array($def->getArguments(), $def->getFactory(), $def->getProperties(), $def->getMethodCalls(), $def->getConfigurator());
200 295 $this->getServiceCallsFromArguments($arguments, $allCalls, false, $cId, $behavior, $allInlinedDefinitions[$def]);
201 $localDefinitions = array_merge( 296 }
202 array($definition), 297
203 $this->getInlinedDefinitions($definition) 298 $isPreInstance = isset($inlinedDefinitions[$definition]) && isset($this->circularReferences[$cId]) && !$this->getProxyDumper()->isProxyCandidate($definition) && $definition->isShared();
204 ); 299 foreach ($inlinedDefinitions as $def) {
205 300 $this->getServiceCallsFromArguments(array($def->getArguments(), $def->getFactory()), $calls, $isPreInstance, $cId);
206 $calls = $behavior = array(); 301 if ($def !== $definition) {
207 foreach ($localDefinitions as $iDefinition) { 302 $arguments = array($def->getProperties(), $def->getMethodCalls(), $def->getConfigurator());
208 $this->getServiceCallsFromArguments($iDefinition->getArguments(), $calls, $behavior); 303 $this->getServiceCallsFromArguments($arguments, $calls, $isPreInstance && !$this->hasReference($cId, $arguments, true), $cId);
209 $this->getServiceCallsFromArguments($iDefinition->getMethodCalls(), $calls, $behavior); 304 }
210 $this->getServiceCallsFromArguments($iDefinition->getProperties(), $calls, $behavior); 305 }
211 $this->getServiceCallsFromArguments(array($iDefinition->getConfigurator()), $calls, $behavior); 306 if (!isset($inlinedDefinitions[$definition])) {
212 $this->getServiceCallsFromArguments(array($iDefinition->getFactory()), $calls, $behavior); 307 $arguments = array($definition->getProperties(), $definition->getMethodCalls(), $definition->getConfigurator());
308 $this->getServiceCallsFromArguments($arguments, $calls, false, $cId);
213 } 309 }
214 310
215 $code = ''; 311 $code = '';
216 foreach ($calls as $id => $callCount) { 312 foreach ($calls as $id => $callCount) {
217 if ('service_container' === $id || $id === $cId) { 313 if ('service_container' === $id || $id === $cId || isset($this->referenceVariables[$id])) {
218 continue; 314 continue;
219 } 315 }
220 316 if ($callCount <= 1 && $allCalls[$id] <= 1) {
221 if ($callCount > 1) { 317 continue;
222 $name = $this->getNextVariableName(); 318 }
223 $this->referenceVariables[$id] = new Variable($name); 319
224 320 $name = $this->getNextVariableName();
225 if (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $behavior[$id]) { 321 $this->referenceVariables[$id] = new Variable($name);
226 $code .= sprintf($template, $name, $this->getServiceCall($id)); 322
227 } else { 323 $reference = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE === $behavior[$id] ? new Reference($id, $behavior[$id]) : null;
228 $code .= sprintf($template, $name, $this->getServiceCall($id, new Reference($id, ContainerInterface::NULL_ON_INVALID_REFERENCE))); 324 $code .= sprintf(" \$%s = %s;\n", $name, $this->getServiceCall($id, $reference));
229 }
230 }
231 } 325 }
232 326
233 if ('' !== $code) { 327 if ('' !== $code) {
328 if ($isPreInstance) {
329 $code .= <<<EOTXT
330
331 if (isset(\$this->services['$cId'])) {
332 return \$this->services['$cId'];
333 }
334
335 EOTXT;
336 }
337
234 $code .= "\n"; 338 $code .= "\n";
235 } 339 }
236 340
237 return $code; 341 return $code;
238 } 342 }
239 343
240 /** 344 private function analyzeCircularReferences(array $edges, &$checkedNodes, &$currentPath)
241 * Generates code for the proxies to be attached after the container class. 345 {
242 * 346 foreach ($edges as $edge) {
243 * @return string 347 $node = $edge->getDestNode();
244 */ 348 $id = $node->getId();
245 private function addProxyClasses() 349
246 { 350 if ($node->getValue() && ($edge->isLazy() || $edge->isWeak())) {
247 /* @var $definitions Definition[] */ 351 // no-op
248 $definitions = array_filter( 352 } elseif (isset($currentPath[$id])) {
249 $this->container->getDefinitions(), 353 foreach (array_reverse($currentPath) as $parentId) {
250 array($this->getProxyDumper(), 'isProxyCandidate') 354 $this->circularReferences[$parentId][$id] = $id;
251 ); 355 $id = $parentId;
252 $code = ''; 356 }
357 } elseif (!isset($checkedNodes[$id])) {
358 $checkedNodes[$id] = true;
359 $currentPath[$id] = $id;
360 $this->analyzeCircularReferences($node->getOutEdges(), $checkedNodes, $currentPath);
361 unset($currentPath[$id]);
362 }
363 }
364 }
365
366 private function collectLineage($class, array &$lineage)
367 {
368 if (isset($lineage[$class])) {
369 return;
370 }
371 if (!$r = $this->container->getReflectionClass($class, false)) {
372 return;
373 }
374 if ($this->container instanceof $class) {
375 return;
376 }
377 $file = $r->getFileName();
378 if (!$file || $this->doExport($file) === $exportedFile = $this->export($file)) {
379 return;
380 }
381
382 if ($parent = $r->getParentClass()) {
383 $this->collectLineage($parent->name, $lineage);
384 }
385
386 foreach ($r->getInterfaces() as $parent) {
387 $this->collectLineage($parent->name, $lineage);
388 }
389
390 foreach ($r->getTraits() as $parent) {
391 $this->collectLineage($parent->name, $lineage);
392 }
393
394 $lineage[$class] = substr($exportedFile, 1, -1);
395 }
396
397 private function generateProxyClasses()
398 {
399 $definitions = $this->container->getDefinitions();
253 $strip = '' === $this->docStar && method_exists('Symfony\Component\HttpKernel\Kernel', 'stripComments'); 400 $strip = '' === $this->docStar && method_exists('Symfony\Component\HttpKernel\Kernel', 'stripComments');
254 401 $proxyDumper = $this->getProxyDumper();
402 ksort($definitions);
255 foreach ($definitions as $definition) { 403 foreach ($definitions as $definition) {
256 $proxyCode = "\n".$this->getProxyDumper()->getProxyCode($definition); 404 if (!$proxyDumper->isProxyCandidate($definition)) {
405 continue;
406 }
407 // register class' reflector for resource tracking
408 $this->container->getReflectionClass($definition->getClass());
409 $proxyCode = "\n".$proxyDumper->getProxyCode($definition);
257 if ($strip) { 410 if ($strip) {
258 $proxyCode = "<?php\n".$proxyCode; 411 $proxyCode = "<?php\n".$proxyCode;
259 $proxyCode = substr(Kernel::stripComments($proxyCode), 5); 412 $proxyCode = substr(Kernel::stripComments($proxyCode), 5);
260 } 413 }
261 $code .= $proxyCode; 414 yield sprintf('%s.php', explode(' ', $proxyCode, 3)[1]) => $proxyCode;
262 } 415 }
263
264 return $code;
265 } 416 }
266 417
267 /** 418 /**
268 * Generates the require_once statement for service includes. 419 * Generates the require_once statement for service includes.
269 * 420 *
270 * @param Definition $definition 421 * @return string
271 * 422 */
272 * @return string 423 private function addServiceInclude($cId, Definition $definition, \SplObjectStorage $inlinedDefinitions)
273 */ 424 {
274 private function addServiceInclude($definition)
275 {
276 $template = " require_once %s;\n";
277 $code = ''; 425 $code = '';
278 426
279 if (null !== $file = $definition->getFile()) { 427 if ($this->inlineRequires && !$this->isHotPath($definition)) {
280 $code .= sprintf($template, $this->dumpValue($file)); 428 $lineage = $calls = $behavior = array();
281 } 429 foreach ($inlinedDefinitions as $def) {
282 430 if (!$def->isDeprecated() && is_string($class = is_array($factory = $def->getFactory()) && is_string($factory[0]) ? $factory[0] : $def->getClass())) {
283 foreach ($this->getInlinedDefinitions($definition) as $definition) { 431 $this->collectLineage($class, $lineage);
284 if (null !== $file = $definition->getFile()) { 432 }
285 $code .= sprintf($template, $this->dumpValue($file)); 433 $arguments = array($def->getArguments(), $def->getFactory(), $def->getProperties(), $def->getMethodCalls(), $def->getConfigurator());
434 $this->getServiceCallsFromArguments($arguments, $calls, false, $cId, $behavior, $inlinedDefinitions[$def]);
435 }
436
437 foreach ($calls as $id => $callCount) {
438 if ('service_container' !== $id && $id !== $cId
439 && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $behavior[$id]
440 && $this->container->has($id)
441 && $this->isTrivialInstance($def = $this->container->findDefinition($id))
442 && is_string($class = is_array($factory = $def->getFactory()) && is_string($factory[0]) ? $factory[0] : $def->getClass())
443 ) {
444 $this->collectLineage($class, $lineage);
445 }
446 }
447
448 foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) {
449 $code .= sprintf(" include_once %s;\n", $file);
450 }
451 }
452
453 foreach ($inlinedDefinitions as $def) {
454 if ($file = $def->getFile()) {
455 $code .= sprintf(" include_once %s;\n", $this->dumpValue($file));
286 } 456 }
287 } 457 }
288 458
289 if ('' !== $code) { 459 if ('' !== $code) {
290 $code .= "\n"; 460 $code .= "\n";
294 } 464 }
295 465
296 /** 466 /**
297 * Generates the inline definition of a service. 467 * Generates the inline definition of a service.
298 * 468 *
469 * @return string
470 *
471 * @throws RuntimeException When the factory definition is incomplete
472 * @throws ServiceCircularReferenceException When a circular reference is detected
473 */
474 private function addServiceInlinedDefinitions($id, Definition $definition, \SplObjectStorage $inlinedDefinitions, &$isSimpleInstance)
475 {
476 $code = '';
477
478 foreach ($inlinedDefinitions as $def) {
479 if ($definition === $def) {
480 continue;
481 }
482 if ($inlinedDefinitions[$def] <= 1 && !$def->getMethodCalls() && !$def->getProperties() && !$def->getConfigurator() && false === strpos($this->dumpValue($def->getClass()), '$')) {
483 continue;
484 }
485 if (isset($this->definitionVariables[$def])) {
486 $name = $this->definitionVariables[$def];
487 } else {
488 $name = $this->getNextVariableName();
489 $this->definitionVariables[$def] = new Variable($name);
490 }
491
492 // a construct like:
493 // $a = new ServiceA(ServiceB $b); $b = new ServiceB(ServiceA $a);
494 // this is an indication for a wrong implementation, you can circumvent this problem
495 // by setting up your service structure like this:
496 // $b = new ServiceB();
497 // $a = new ServiceA(ServiceB $b);
498 // $b->setServiceA(ServiceA $a);
499 if (isset($inlinedDefinition[$definition]) && $this->hasReference($id, array($def->getArguments(), $def->getFactory()))) {
500 throw new ServiceCircularReferenceException($id, array($id));
501 }
502
503 $code .= $this->addNewInstance($def, '$'.$name, ' = ', $id);
504
505 if (!$this->hasReference($id, array($def->getProperties(), $def->getMethodCalls(), $def->getConfigurator()), true)) {
506 $code .= $this->addServiceProperties($def, $name);
507 $code .= $this->addServiceMethodCalls($def, $name);
508 $code .= $this->addServiceConfigurator($def, $name);
509 } else {
510 $isSimpleInstance = false;
511 }
512
513 $code .= "\n";
514 }
515
516 return $code;
517 }
518
519 /**
520 * Generates the service instance.
521 *
299 * @param string $id 522 * @param string $id
300 * @param Definition $definition 523 * @param Definition $definition
301 * 524 * @param bool $isSimpleInstance
302 * @return string
303 *
304 * @throws RuntimeException When the factory definition is incomplete
305 * @throws ServiceCircularReferenceException When a circular reference is detected
306 */
307 private function addServiceInlinedDefinitions($id, $definition)
308 {
309 $code = '';
310 $variableMap = $this->definitionVariables;
311 $nbOccurrences = new \SplObjectStorage();
312 $processed = new \SplObjectStorage();
313 $inlinedDefinitions = $this->getInlinedDefinitions($definition);
314
315 foreach ($inlinedDefinitions as $definition) {
316 if (false === $nbOccurrences->contains($definition)) {
317 $nbOccurrences->offsetSet($definition, 1);
318 } else {
319 $i = $nbOccurrences->offsetGet($definition);
320 $nbOccurrences->offsetSet($definition, $i + 1);
321 }
322 }
323
324 foreach ($inlinedDefinitions as $sDefinition) {
325 if ($processed->contains($sDefinition)) {
326 continue;
327 }
328 $processed->offsetSet($sDefinition);
329
330 $class = $this->dumpValue($sDefinition->getClass());
331 if ($nbOccurrences->offsetGet($sDefinition) > 1 || $sDefinition->getMethodCalls() || $sDefinition->getProperties() || null !== $sDefinition->getConfigurator() || false !== strpos($class, '$')) {
332 $name = $this->getNextVariableName();
333 $variableMap->offsetSet($sDefinition, new Variable($name));
334
335 // a construct like:
336 // $a = new ServiceA(ServiceB $b); $b = new ServiceB(ServiceA $a);
337 // this is an indication for a wrong implementation, you can circumvent this problem
338 // by setting up your service structure like this:
339 // $b = new ServiceB();
340 // $a = new ServiceA(ServiceB $b);
341 // $b->setServiceA(ServiceA $a);
342 if ($this->hasReference($id, $sDefinition->getArguments())) {
343 throw new ServiceCircularReferenceException($id, array($id));
344 }
345
346 $code .= $this->addNewInstance($sDefinition, '$'.$name, ' = ', $id);
347
348 if (!$this->hasReference($id, $sDefinition->getMethodCalls(), true) && !$this->hasReference($id, $sDefinition->getProperties(), true)) {
349 $code .= $this->addServiceProperties($sDefinition, $name);
350 $code .= $this->addServiceMethodCalls($sDefinition, $name);
351 $code .= $this->addServiceConfigurator($sDefinition, $name);
352 }
353
354 $code .= "\n";
355 }
356 }
357
358 return $code;
359 }
360
361 /**
362 * Adds the service return statement.
363 *
364 * @param string $id Service id
365 * @param Definition $definition
366 *
367 * @return string
368 */
369 private function addServiceReturn($id, $definition)
370 {
371 if ($this->isSimpleInstance($id, $definition)) {
372 return " }\n";
373 }
374
375 return "\n return \$instance;\n }\n";
376 }
377
378 /**
379 * Generates the service instance.
380 *
381 * @param string $id
382 * @param Definition $definition
383 * 525 *
384 * @return string 526 * @return string
385 * 527 *
386 * @throws InvalidArgumentException 528 * @throws InvalidArgumentException
387 * @throws RuntimeException 529 * @throws RuntimeException
388 */ 530 */
389 private function addServiceInstance($id, Definition $definition) 531 private function addServiceInstance($id, Definition $definition, $isSimpleInstance)
390 { 532 {
391 $class = $this->dumpValue($definition->getClass()); 533 $class = $this->dumpValue($definition->getClass());
392 534
393 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)) { 535 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)) {
394 throw new InvalidArgumentException(sprintf('"%s" is not a valid class name for the "%s" service.', $class, $id)); 536 throw new InvalidArgumentException(sprintf('"%s" is not a valid class name for the "%s" service.', $class, $id));
395 } 537 }
396 538
397 $simple = $this->isSimpleInstance($id, $definition);
398 $isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition); 539 $isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition);
399 $instantiation = ''; 540 $instantiation = '';
400 541
401 if (!$isProxyCandidate && $definition->isShared()) { 542 if (!$isProxyCandidate && $definition->isShared()) {
402 $instantiation = "\$this->services['$id'] = ".($simple ? '' : '$instance'); 543 $instantiation = "\$this->services['$id'] = ".($isSimpleInstance ? '' : '$instance');
403 } elseif (!$simple) { 544 } elseif (!$isSimpleInstance) {
404 $instantiation = '$instance'; 545 $instantiation = '$instance';
405 } 546 }
406 547
407 $return = ''; 548 $return = '';
408 if ($simple) { 549 if ($isSimpleInstance) {
409 $return = 'return '; 550 $return = 'return ';
410 } else { 551 } else {
411 $instantiation .= ' = '; 552 $instantiation .= ' = ';
412 } 553 }
413 554
414 $code = $this->addNewInstance($definition, $return, $instantiation, $id); 555 $code = $this->addNewInstance($definition, $return, $instantiation, $id);
415 556
416 if (!$simple) { 557 if (!$isSimpleInstance) {
417 $code .= "\n"; 558 $code .= "\n";
418 } 559 }
419 560
420 return $code; 561 return $code;
421 } 562 }
422 563
423 /** 564 /**
424 * Checks if the definition is a simple instance. 565 * Checks if the definition is a trivial instance.
425 * 566 *
426 * @param string $id
427 * @param Definition $definition 567 * @param Definition $definition
428 * 568 *
429 * @return bool 569 * @return bool
430 */ 570 */
431 private function isSimpleInstance($id, Definition $definition) 571 private function isTrivialInstance(Definition $definition)
432 { 572 {
433 foreach (array_merge(array($definition), $this->getInlinedDefinitions($definition)) as $sDefinition) { 573 if ($definition->isSynthetic() || $definition->getFile() || $definition->getMethodCalls() || $definition->getProperties() || $definition->getConfigurator()) {
434 if ($definition !== $sDefinition && !$this->hasReference($id, $sDefinition->getMethodCalls())) { 574 return false;
575 }
576 if ($definition->isDeprecated() || $definition->isLazy() || $definition->getFactory() || 3 < count($definition->getArguments())) {
577 return false;
578 }
579
580 foreach ($definition->getArguments() as $arg) {
581 if (!$arg || $arg instanceof Parameter) {
435 continue; 582 continue;
436 } 583 }
437 584 if (is_array($arg) && 3 >= count($arg)) {
438 if ($sDefinition->getMethodCalls() || $sDefinition->getProperties() || $sDefinition->getConfigurator()) { 585 foreach ($arg as $k => $v) {
586 if ($this->dumpValue($k) !== $this->dumpValue($k, false)) {
587 return false;
588 }
589 if (!$v || $v instanceof Parameter) {
590 continue;
591 }
592 if ($v instanceof Reference && $this->container->has($id = (string) $v) && $this->container->findDefinition($id)->isSynthetic()) {
593 continue;
594 }
595 if (!is_scalar($v) || $this->dumpValue($v) !== $this->dumpValue($v, false)) {
596 return false;
597 }
598 }
599 } elseif ($arg instanceof Reference && $this->container->has($id = (string) $arg) && $this->container->findDefinition($id)->isSynthetic()) {
600 continue;
601 } elseif (!is_scalar($arg) || $this->dumpValue($arg) !== $this->dumpValue($arg, false)) {
439 return false; 602 return false;
440 } 603 }
604 }
605
606 if (false !== strpos($this->dumpLiteralClass($this->dumpValue($definition->getClass())), '$')) {
607 return false;
441 } 608 }
442 609
443 return true; 610 return true;
444 } 611 }
445 612
477 } 644 }
478 645
479 /** 646 /**
480 * Generates the inline definition setup. 647 * Generates the inline definition setup.
481 * 648 *
482 * @param string $id
483 * @param Definition $definition
484 *
485 * @return string 649 * @return string
486 * 650 *
487 * @throws ServiceCircularReferenceException when the container contains a circular reference 651 * @throws ServiceCircularReferenceException when the container contains a circular reference
488 */ 652 */
489 private function addServiceInlinedDefinitionsSetup($id, Definition $definition) 653 private function addServiceInlinedDefinitionsSetup($id, Definition $definition, \SplObjectStorage $inlinedDefinitions, $isSimpleInstance)
490 { 654 {
491 $this->referenceVariables[$id] = new Variable('instance'); 655 $this->referenceVariables[$id] = new Variable('instance');
492 656
493 $code = ''; 657 $code = '';
494 $processed = new \SplObjectStorage(); 658 foreach ($inlinedDefinitions as $def) {
495 foreach ($this->getInlinedDefinitions($definition) as $iDefinition) { 659 if ($definition === $def || !$this->hasReference($id, array($def->getProperties(), $def->getMethodCalls(), $def->getConfigurator()), true)) {
496 if ($processed->contains($iDefinition)) {
497 continue;
498 }
499 $processed->offsetSet($iDefinition);
500
501 if (!$this->hasReference($id, $iDefinition->getMethodCalls(), true) && !$this->hasReference($id, $iDefinition->getProperties(), true)) {
502 continue; 660 continue;
503 } 661 }
504 662
505 // if the instance is simple, the return statement has already been generated 663 // if the instance is simple, the return statement has already been generated
506 // so, the only possible way to get there is because of a circular reference 664 // so, the only possible way to get there is because of a circular reference
507 if ($this->isSimpleInstance($id, $definition)) { 665 if ($isSimpleInstance) {
508 throw new ServiceCircularReferenceException($id, array($id)); 666 throw new ServiceCircularReferenceException($id, array($id));
509 } 667 }
510 668
511 $name = (string) $this->definitionVariables->offsetGet($iDefinition); 669 $name = (string) $this->definitionVariables[$def];
512 $code .= $this->addServiceProperties($iDefinition, $name); 670 $code .= $this->addServiceProperties($def, $name);
513 $code .= $this->addServiceMethodCalls($iDefinition, $name); 671 $code .= $this->addServiceMethodCalls($def, $name);
514 $code .= $this->addServiceConfigurator($iDefinition, $name); 672 $code .= $this->addServiceConfigurator($def, $name);
515 } 673 }
516 674
517 if ('' !== $code) { 675 if ('' !== $code && ($definition->getProperties() || $definition->getMethodCalls() || $definition->getConfigurator())) {
518 $code .= "\n"; 676 $code .= "\n";
519 } 677 }
520 678
521 return $code; 679 return $code;
522 } 680 }
549 707
550 if (0 === strpos($class, 'new ')) { 708 if (0 === strpos($class, 'new ')) {
551 return sprintf(" (%s)->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); 709 return sprintf(" (%s)->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName);
552 } 710 }
553 711
554 return sprintf(" call_user_func(array(%s, '%s'), \$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); 712 return sprintf(" \\call_user_func(array(%s, '%s'), \$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName);
555 } 713 }
556 714
557 return sprintf(" %s(\$%s);\n", $callable, $variableName); 715 return sprintf(" %s(\$%s);\n", $callable, $variableName);
558 } 716 }
559 717
560 /** 718 /**
561 * Adds a service. 719 * Adds a service.
562 * 720 *
563 * @param string $id 721 * @param string $id
564 * @param Definition $definition 722 * @param Definition $definition
565 * 723 * @param string &$file
566 * @return string 724 *
567 */ 725 * @return string
568 private function addService($id, Definition $definition) 726 */
727 private function addService($id, Definition $definition, &$file = null)
569 { 728 {
570 $this->definitionVariables = new \SplObjectStorage(); 729 $this->definitionVariables = new \SplObjectStorage();
571 $this->referenceVariables = array(); 730 $this->referenceVariables = array();
572 $this->variableCount = 0; 731 $this->variableCount = 0;
573 732
574 $return = array(); 733 $return = array();
575 734
576 if ($definition->isSynthetic()) { 735 if ($class = $definition->getClass()) {
577 $return[] = '@throws RuntimeException always since this service is expected to be injected dynamically';
578 } elseif ($class = $definition->getClass()) {
579 $class = $this->container->resolveEnvPlaceholders($class); 736 $class = $this->container->resolveEnvPlaceholders($class);
580 $return[] = sprintf(0 === strpos($class, '%') ? '@return object A %1$s instance' : '@return \%s', ltrim($class, '\\')); 737 $return[] = sprintf(0 === strpos($class, '%') ? '@return object A %1$s instance' : '@return \%s', ltrim($class, '\\'));
581 } elseif ($definition->getFactory()) { 738 } elseif ($definition->getFactory()) {
582 $factory = $definition->getFactory(); 739 $factory = $definition->getFactory();
583 if (is_string($factory)) { 740 if (is_string($factory)) {
610 $lazyInitialization = '$lazyLoad = true'; 767 $lazyInitialization = '$lazyLoad = true';
611 } else { 768 } else {
612 $lazyInitialization = ''; 769 $lazyInitialization = '';
613 } 770 }
614 771
615 // with proxies, for 5.3.3 compatibility, the getter must be public to be accessible to the initializer 772 $asFile = $this->asFiles && $definition->isShared() && !$this->isHotPath($definition);
616 $isProxyCandidate = $this->getProxyDumper()->isProxyCandidate($definition);
617 $visibility = $isProxyCandidate ? 'public' : 'protected';
618 $methodName = $this->generateMethodName($id); 773 $methodName = $this->generateMethodName($id);
619 $code = <<<EOF 774 if ($asFile) {
775 $file = $methodName.'.php';
776 $code = " // Returns the $public '$id'$shared$autowired service.\n\n";
777 } else {
778 $code = <<<EOF
620 779
621 /*{$this->docStar} 780 /*{$this->docStar}
622 * Gets the $public '$id'$shared$autowired service. 781 * Gets the $public '$id'$shared$autowired service.
623 * 782 *
624 * $return 783 * $return
625 */ 784 */
626 {$visibility} function {$methodName}($lazyInitialization) 785 protected function {$methodName}($lazyInitialization)
627 { 786 {
628 787
629 EOF; 788 EOF;
630 789 }
631 $code .= $isProxyCandidate ? $this->getProxyDumper()->getProxyFactoryCode($definition, $id, $methodName) : ''; 790
632 791 if ($this->getProxyDumper()->isProxyCandidate($definition)) {
633 if ($definition->isSynthetic()) { 792 $factoryCode = $asFile ? "\$this->load('%s.php', false)" : '$this->%s(false)';
634 $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); 793 $code .= $this->getProxyDumper()->getProxyFactoryCode($definition, $id, sprintf($factoryCode, $methodName));
794 }
795
796 if ($definition->isDeprecated()) {
797 $code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", $this->export($definition->getDeprecationMessage($id)));
798 }
799
800 $inlinedDefinitions = $this->getDefinitionsFromArguments(array($definition));
801 $constructorDefinitions = $this->getDefinitionsFromArguments(array($definition->getArguments(), $definition->getFactory()));
802 $otherDefinitions = new \SplObjectStorage();
803
804 foreach ($inlinedDefinitions as $def) {
805 if ($def === $definition || isset($constructorDefinitions[$def])) {
806 $constructorDefinitions[$def] = $inlinedDefinitions[$def];
807 } else {
808 $otherDefinitions[$def] = $inlinedDefinitions[$def];
809 }
810 }
811
812 $isSimpleInstance = !$definition->getProperties() && !$definition->getMethodCalls() && !$definition->getConfigurator();
813
814 $code .=
815 $this->addServiceInclude($id, $definition, $inlinedDefinitions).
816 $this->addServiceLocalTempVariables($id, $definition, $constructorDefinitions, $inlinedDefinitions).
817 $this->addServiceInlinedDefinitions($id, $definition, $constructorDefinitions, $isSimpleInstance).
818 $this->addServiceInstance($id, $definition, $isSimpleInstance).
819 $this->addServiceLocalTempVariables($id, $definition, $otherDefinitions, $inlinedDefinitions).
820 $this->addServiceInlinedDefinitions($id, $definition, $otherDefinitions, $isSimpleInstance).
821 $this->addServiceInlinedDefinitionsSetup($id, $definition, $inlinedDefinitions, $isSimpleInstance).
822 $this->addServiceProperties($definition).
823 $this->addServiceMethodCalls($definition).
824 $this->addServiceConfigurator($definition).
825 (!$isSimpleInstance ? "\n return \$instance;\n" : '')
826 ;
827
828 if ($asFile) {
829 $code = implode("\n", array_map(function ($line) { return $line ? substr($line, 8) : $line; }, explode("\n", $code)));
635 } else { 830 } else {
636 if ($definition->isDeprecated()) { 831 $code .= " }\n";
637 $code .= sprintf(" @trigger_error(%s, E_USER_DEPRECATED);\n\n", $this->export($definition->getDeprecationMessage($id)));
638 }
639
640 $code .=
641 $this->addServiceInclude($definition).
642 $this->addServiceLocalTempVariables($id, $definition).
643 $this->addServiceInlinedDefinitions($id, $definition).
644 $this->addServiceInstance($id, $definition).
645 $this->addServiceInlinedDefinitionsSetup($id, $definition).
646 $this->addServiceProperties($definition).
647 $this->addServiceMethodCalls($definition).
648 $this->addServiceConfigurator($definition).
649 $this->addServiceReturn($id, $definition)
650 ;
651 } 832 }
652 833
653 $this->definitionVariables = null; 834 $this->definitionVariables = null;
654 $this->referenceVariables = null; 835 $this->referenceVariables = null;
655 836
665 { 846 {
666 $publicServices = $privateServices = ''; 847 $publicServices = $privateServices = '';
667 $definitions = $this->container->getDefinitions(); 848 $definitions = $this->container->getDefinitions();
668 ksort($definitions); 849 ksort($definitions);
669 foreach ($definitions as $id => $definition) { 850 foreach ($definitions as $id => $definition) {
851 if ($definition->isSynthetic() || ($this->asFiles && $definition->isShared() && !$this->isHotPath($definition))) {
852 continue;
853 }
670 if ($definition->isPublic()) { 854 if ($definition->isPublic()) {
671 $publicServices .= $this->addService($id, $definition); 855 $publicServices .= $this->addService($id, $definition);
672 } else { 856 } else {
673 $privateServices .= $this->addService($id, $definition); 857 $privateServices .= $this->addService($id, $definition);
674 } 858 }
675 } 859 }
676 860
677 return $publicServices.$privateServices; 861 return $publicServices.$privateServices;
678 } 862 }
679 863
864 private function generateServiceFiles()
865 {
866 $definitions = $this->container->getDefinitions();
867 ksort($definitions);
868 foreach ($definitions as $id => $definition) {
869 if (!$definition->isSynthetic() && $definition->isShared() && !$this->isHotPath($definition)) {
870 $code = $this->addService($id, $definition, $file);
871 yield $file => $code;
872 }
873 }
874 }
875
680 private function addNewInstance(Definition $definition, $return, $instantiation, $id) 876 private function addNewInstance(Definition $definition, $return, $instantiation, $id)
681 { 877 {
682 $class = $this->dumpValue($definition->getClass()); 878 $class = $this->dumpValue($definition->getClass());
879 $return = ' '.$return.$instantiation;
683 880
684 $arguments = array(); 881 $arguments = array();
685 foreach ($definition->getArguments() as $value) { 882 foreach ($definition->getArguments() as $value) {
686 $arguments[] = $this->dumpValue($value); 883 $arguments[] = $this->dumpValue($value);
687 } 884 }
693 throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s)', $callable[1] ?: 'n/a')); 890 throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s)', $callable[1] ?: 'n/a'));
694 } 891 }
695 892
696 if ($callable[0] instanceof Reference 893 if ($callable[0] instanceof Reference
697 || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))) { 894 || ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0]))) {
698 return sprintf(" $return{$instantiation}%s->%s(%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : ''); 895 return $return.sprintf("%s->%s(%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : '');
699 } 896 }
700 897
701 $class = $this->dumpValue($callable[0]); 898 $class = $this->dumpValue($callable[0]);
702 // If the class is a string we can optimize call_user_func away 899 // If the class is a string we can optimize call_user_func away
703 if (0 === strpos($class, "'") && false === strpos($class, '$')) { 900 if (0 === strpos($class, "'") && false === strpos($class, '$')) {
704 if ("''" === $class) { 901 if ("''" === $class) {
705 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)); 902 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));
706 } 903 }
707 904
708 return sprintf(" $return{$instantiation}%s::%s(%s);\n", $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : ''); 905 return $return.sprintf("%s::%s(%s);\n", $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : '');
709 } 906 }
710 907
711 if (0 === strpos($class, 'new ')) { 908 if (0 === strpos($class, 'new ')) {
712 return sprintf(" $return{$instantiation}(%s)->%s(%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : ''); 909 return $return.sprintf("(%s)->%s(%s);\n", $class, $callable[1], $arguments ? implode(', ', $arguments) : '');
713 } 910 }
714 911
715 return sprintf(" $return{$instantiation}call_user_func(array(%s, '%s')%s);\n", $this->dumpValue($callable[0]), $callable[1], $arguments ? ', '.implode(', ', $arguments) : ''); 912 return $return.sprintf("\\call_user_func(array(%s, '%s')%s);\n", $class, $callable[1], $arguments ? ', '.implode(', ', $arguments) : '');
716 } 913 }
717 914
718 return sprintf(" $return{$instantiation}%s(%s);\n", $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : ''); 915 return $return.sprintf("%s(%s);\n", $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : '');
719 } 916 }
720 917
721 if (false !== strpos($class, '$')) { 918 if (false !== strpos($class, '$')) {
722 return sprintf(" \$class = %s;\n\n $return{$instantiation}new \$class(%s);\n", $class, implode(', ', $arguments)); 919 return sprintf(" \$class = %s;\n\n%snew \$class(%s);\n", $class, $return, implode(', ', $arguments));
723 } 920 }
724 921
725 return sprintf(" $return{$instantiation}new %s(%s);\n", $this->dumpLiteralClass($class), implode(', ', $arguments)); 922 return $return.sprintf("new %s(%s);\n", $this->dumpLiteralClass($class), implode(', ', $arguments));
726 } 923 }
727 924
728 /** 925 /**
729 * Adds the class headers. 926 * Adds the class headers.
730 * 927 *
731 * @param string $class Class name 928 * @param string $class Class name
732 * @param string $baseClass The name of the base class 929 * @param string $baseClass The name of the base class
733 * @param string $namespace The class namespace 930 * @param string $baseClassWithNamespace Fully qualified base class name
734 * 931 *
735 * @return string 932 * @return string
736 */ 933 */
737 private function startClass($class, $baseClass, $namespace) 934 private function startClass($class, $baseClass, $baseClassWithNamespace)
738 { 935 {
739 $bagClass = $this->container->isFrozen() ? 'use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;' : 'use Symfony\Component\DependencyInjection\ParameterBag\\ParameterBag;'; 936 $bagClass = $this->container->isCompiled() ? 'use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag;' : 'use Symfony\Component\DependencyInjection\ParameterBag\\ParameterBag;';
740 $namespaceLine = $namespace ? "\nnamespace $namespace;\n" : ''; 937 $namespaceLine = !$this->asFiles && $this->namespace ? "\nnamespace {$this->namespace};\n" : '';
741 938
742 return <<<EOF 939 $code = <<<EOF
743 <?php 940 <?php
744 $namespaceLine 941 $namespaceLine
942 use Symfony\Component\DependencyInjection\Argument\RewindableGenerator;
745 use Symfony\Component\DependencyInjection\ContainerInterface; 943 use Symfony\Component\DependencyInjection\ContainerInterface;
746 use Symfony\Component\DependencyInjection\Container; 944 use Symfony\Component\DependencyInjection\Container;
747 use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; 945 use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException;
748 use Symfony\Component\DependencyInjection\Exception\LogicException; 946 use Symfony\Component\DependencyInjection\Exception\LogicException;
749 use Symfony\Component\DependencyInjection\Exception\RuntimeException; 947 use Symfony\Component\DependencyInjection\Exception\RuntimeException;
750 $bagClass 948 $bagClass
751 949
752 /*{$this->docStar} 950 /*{$this->docStar}
753 * $class.
754 *
755 * This class has been auto-generated 951 * This class has been auto-generated
756 * by the Symfony Dependency Injection Component. 952 * by the Symfony Dependency Injection Component.
953 *
954 * @final since Symfony 3.3
757 */ 955 */
758 class $class extends $baseClass 956 class $class extends $baseClass
759 { 957 {
760 private \$parameters; 958 private \$parameters;
761 private \$targetDirs = array(); 959 private \$targetDirs = array();
762 960
961 public function __construct()
962 {
963
763 EOF; 964 EOF;
764 } 965 if (null !== $this->targetDirRegex) {
765 966 $dir = $this->asFiles ? '$this->targetDirs[0] = \\dirname($containerDir)' : '__DIR__';
766 /** 967 $code .= <<<EOF
767 * Adds the constructor. 968 \$dir = {$dir};
768 * 969 for (\$i = 1; \$i <= {$this->targetDirMaxMatches}; ++\$i) {
769 * @return string 970 \$this->targetDirs[\$i] = \$dir = \\dirname(\$dir);
770 */ 971 }
771 private function addConstructor()
772 {
773 $targetDirs = $this->exportTargetDirs();
774 $arguments = $this->container->getParameterBag()->all() ? 'new ParameterBag($this->getDefaultParameters())' : null;
775
776 $code = <<<EOF
777
778 /*{$this->docStar}
779 * Constructor.
780 */
781 public function __construct()
782 {{$targetDirs}
783 parent::__construct($arguments);
784 972
785 EOF; 973 EOF;
786 974 }
975 if ($this->asFiles) {
976 $code = str_replace('$parameters', "\$buildParameters;\n private \$containerDir;\n private \$parameters", $code);
977 $code = str_replace('__construct()', '__construct(array $buildParameters = array(), $containerDir = __DIR__)', $code);
978 $code .= " \$this->buildParameters = \$buildParameters;\n";
979 $code .= " \$this->containerDir = \$containerDir;\n";
980 }
981
982 if ($this->container->isCompiled()) {
983 if (Container::class !== $baseClassWithNamespace) {
984 $r = $this->container->getReflectionClass($baseClassWithNamespace, false);
985 if (null !== $r
986 && (null !== $constructor = $r->getConstructor())
987 && 0 === $constructor->getNumberOfRequiredParameters()
988 && Container::class !== $constructor->getDeclaringClass()->name
989 ) {
990 $code .= " parent::__construct();\n";
991 $code .= " \$this->parameterBag = null;\n\n";
992 }
993 }
994
995 if ($this->container->getParameterBag()->all()) {
996 $code .= " \$this->parameters = \$this->getDefaultParameters();\n\n";
997 }
998
999 $code .= " \$this->services = array();\n";
1000 } else {
1001 $arguments = $this->container->getParameterBag()->all() ? 'new ParameterBag($this->getDefaultParameters())' : null;
1002 $code .= " parent::__construct($arguments);\n";
1003 }
1004
1005 $code .= $this->addNormalizedIds();
1006 $code .= $this->addSyntheticIds();
787 $code .= $this->addMethodMap(); 1007 $code .= $this->addMethodMap();
1008 $code .= $this->asFiles ? $this->addFileMap() : '';
788 $code .= $this->addPrivateServices(); 1009 $code .= $this->addPrivateServices();
789 $code .= $this->addAliases(); 1010 $code .= $this->addAliases();
790 1011 $code .= $this->addInlineRequires();
791 $code .= <<<'EOF' 1012 $code .= <<<'EOF'
792 } 1013 }
793 1014
794 EOF; 1015 EOF;
1016 $code .= $this->addRemovedIds();
1017
1018 if ($this->container->isCompiled()) {
1019 $code .= <<<EOF
1020
1021 public function compile()
1022 {
1023 throw new LogicException('You cannot compile a dumped container that was already compiled.');
1024 }
1025
1026 public function isCompiled()
1027 {
1028 return true;
1029 }
1030
1031 public function isFrozen()
1032 {
1033 @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);
1034
1035 return true;
1036 }
1037
1038 EOF;
1039 }
1040
1041 if ($this->asFiles) {
1042 $code .= <<<EOF
1043
1044 protected function load(\$file, \$lazyLoad = true)
1045 {
1046 return require \$this->containerDir.\\DIRECTORY_SEPARATOR.\$file;
1047 }
1048
1049 EOF;
1050 }
1051
1052 $proxyDumper = $this->getProxyDumper();
1053 foreach ($this->container->getDefinitions() as $definition) {
1054 if (!$proxyDumper->isProxyCandidate($definition)) {
1055 continue;
1056 }
1057 if ($this->asFiles) {
1058 $proxyLoader = '$this->load("{$class}.php")';
1059 } elseif ($this->namespace) {
1060 $proxyLoader = 'class_alias("'.$this->namespace.'\\\\{$class}", $class, false)';
1061 } else {
1062 $proxyLoader = '';
1063 }
1064 if ($proxyLoader) {
1065 $proxyLoader = "class_exists(\$class, false) || {$proxyLoader};\n\n ";
1066 }
1067 $code .= <<<EOF
1068
1069 protected function createProxy(\$class, \Closure \$factory)
1070 {
1071 {$proxyLoader}return \$factory();
1072 }
1073
1074 EOF;
1075 break;
1076 }
795 1077
796 return $code; 1078 return $code;
797 } 1079 }
798 1080
799 /** 1081 /**
800 * Adds the constructor for a frozen container. 1082 * Adds the normalizedIds property definition.
801 * 1083 *
802 * @return string 1084 * @return string
803 */ 1085 */
804 private function addFrozenConstructor() 1086 private function addNormalizedIds()
805 { 1087 {
806 $targetDirs = $this->exportTargetDirs(); 1088 $code = '';
807 1089 $normalizedIds = $this->container->getNormalizedIds();
808 $code = <<<EOF 1090 ksort($normalizedIds);
809 1091 foreach ($normalizedIds as $id => $normalizedId) {
810 /*{$this->docStar} 1092 if ($this->container->has($normalizedId)) {
811 * Constructor. 1093 $code .= ' '.$this->doExport($id).' => '.$this->doExport($normalizedId).",\n";
812 */ 1094 }
813 public function __construct() 1095 }
814 {{$targetDirs} 1096
815 EOF; 1097 return $code ? " \$this->normalizedIds = array(\n".$code." );\n" : '';
816 1098 }
817 if ($this->container->getParameterBag()->all()) { 1099
818 $code .= "\n \$this->parameters = \$this->getDefaultParameters();\n"; 1100 /**
819 } 1101 * Adds the syntheticIds definition.
820 1102 *
821 $code .= "\n \$this->services = array();\n"; 1103 * @return string
822 $code .= $this->addMethodMap(); 1104 */
823 $code .= $this->addPrivateServices(); 1105 private function addSyntheticIds()
824 $code .= $this->addAliases(); 1106 {
825 1107 $code = '';
826 $code .= <<<'EOF' 1108 $definitions = $this->container->getDefinitions();
827 }
828
829 EOF;
830
831 return $code;
832 }
833
834 /**
835 * Adds the constructor for a frozen container.
836 *
837 * @return string
838 */
839 private function addFrozenCompile()
840 {
841 return <<<EOF
842
843 /*{$this->docStar}
844 * {@inheritdoc}
845 */
846 public function compile()
847 {
848 throw new LogicException('You cannot compile a dumped frozen container.');
849 }
850
851 EOF;
852 }
853
854 /**
855 * Adds the isFrozen method for a frozen container.
856 *
857 * @return string
858 */
859 private function addIsFrozenMethod()
860 {
861 return <<<EOF
862
863 /*{$this->docStar}
864 * {@inheritdoc}
865 */
866 public function isFrozen()
867 {
868 return true;
869 }
870
871 EOF;
872 }
873
874 /**
875 * Adds the methodMap property definition.
876 *
877 * @return string
878 */
879 private function addMethodMap()
880 {
881 if (!$definitions = $this->container->getDefinitions()) {
882 return '';
883 }
884
885 $code = " \$this->methodMap = array(\n";
886 ksort($definitions); 1109 ksort($definitions);
887 foreach ($definitions as $id => $definition) { 1110 foreach ($definitions as $id => $definition) {
888 $code .= ' '.$this->export($id).' => '.$this->export($this->generateMethodName($id)).",\n"; 1111 if ($definition->isSynthetic() && 'service_container' !== $id) {
889 } 1112 $code .= ' '.$this->doExport($id)." => true,\n";
890 1113 }
891 return $code." );\n"; 1114 }
1115
1116 return $code ? " \$this->syntheticIds = array(\n{$code} );\n" : '';
1117 }
1118
1119 /**
1120 * Adds the removedIds definition.
1121 *
1122 * @return string
1123 */
1124 private function addRemovedIds()
1125 {
1126 if (!$ids = $this->container->getRemovedIds()) {
1127 return '';
1128 }
1129 if ($this->asFiles) {
1130 $code = "require \$this->containerDir.\\DIRECTORY_SEPARATOR.'removed-ids.php'";
1131 } else {
1132 $code = '';
1133 $ids = array_keys($ids);
1134 sort($ids);
1135 foreach ($ids as $id) {
1136 $code .= ' '.$this->doExport($id)." => true,\n";
1137 }
1138
1139 $code = "array(\n{$code} )";
1140 }
1141
1142 return <<<EOF
1143
1144 public function getRemovedIds()
1145 {
1146 return {$code};
1147 }
1148
1149 EOF;
1150 }
1151
1152 /**
1153 * Adds the methodMap property definition.
1154 *
1155 * @return string
1156 */
1157 private function addMethodMap()
1158 {
1159 $code = '';
1160 $definitions = $this->container->getDefinitions();
1161 ksort($definitions);
1162 foreach ($definitions as $id => $definition) {
1163 if (!$definition->isSynthetic() && (!$this->asFiles || !$definition->isShared() || $this->isHotPath($definition))) {
1164 $code .= ' '.$this->doExport($id).' => '.$this->doExport($this->generateMethodName($id)).",\n";
1165 }
1166 }
1167
1168 return $code ? " \$this->methodMap = array(\n{$code} );\n" : '';
1169 }
1170
1171 /**
1172 * Adds the fileMap property definition.
1173 *
1174 * @return string
1175 */
1176 private function addFileMap()
1177 {
1178 $code = '';
1179 $definitions = $this->container->getDefinitions();
1180 ksort($definitions);
1181 foreach ($definitions as $id => $definition) {
1182 if (!$definition->isSynthetic() && $definition->isShared() && !$this->isHotPath($definition)) {
1183 $code .= sprintf(" %s => '%s.php',\n", $this->doExport($id), $this->generateMethodName($id));
1184 }
1185 }
1186
1187 return $code ? " \$this->fileMap = array(\n{$code} );\n" : '';
892 } 1188 }
893 1189
894 /** 1190 /**
895 * Adds the privates property definition. 1191 * Adds the privates property definition.
896 * 1192 *
897 * @return string 1193 * @return string
898 */ 1194 */
899 private function addPrivateServices() 1195 private function addPrivateServices()
900 { 1196 {
901 if (!$definitions = $this->container->getDefinitions()) {
902 return '';
903 }
904
905 $code = ''; 1197 $code = '';
1198
1199 $aliases = $this->container->getAliases();
1200 ksort($aliases);
1201 foreach ($aliases as $id => $alias) {
1202 if ($alias->isPrivate()) {
1203 $code .= ' '.$this->doExport($id)." => true,\n";
1204 }
1205 }
1206
1207 $definitions = $this->container->getDefinitions();
906 ksort($definitions); 1208 ksort($definitions);
907 foreach ($definitions as $id => $definition) { 1209 foreach ($definitions as $id => $definition) {
908 if (!$definition->isPublic()) { 1210 if (!$definition->isPublic()) {
909 $code .= ' '.$this->export($id)." => true,\n"; 1211 $code .= ' '.$this->doExport($id)." => true,\n";
910 } 1212 }
911 } 1213 }
912 1214
913 if (empty($code)) { 1215 if (empty($code)) {
914 return ''; 1216 return '';
927 * @return string 1229 * @return string
928 */ 1230 */
929 private function addAliases() 1231 private function addAliases()
930 { 1232 {
931 if (!$aliases = $this->container->getAliases()) { 1233 if (!$aliases = $this->container->getAliases()) {
932 return $this->container->isFrozen() ? "\n \$this->aliases = array();\n" : ''; 1234 return $this->container->isCompiled() ? "\n \$this->aliases = array();\n" : '';
933 } 1235 }
934 1236
935 $code = " \$this->aliases = array(\n"; 1237 $code = " \$this->aliases = array(\n";
936 ksort($aliases); 1238 ksort($aliases);
937 foreach ($aliases as $alias => $id) { 1239 foreach ($aliases as $alias => $id) {
938 $id = (string) $id; 1240 $id = $this->container->normalizeId($id);
939 while (isset($aliases[$id])) { 1241 while (isset($aliases[$id])) {
940 $id = (string) $aliases[$id]; 1242 $id = $this->container->normalizeId($aliases[$id]);
941 } 1243 }
942 $code .= ' '.$this->export($alias).' => '.$this->export($id).",\n"; 1244 $code .= ' '.$this->doExport($alias).' => '.$this->doExport($id).",\n";
943 } 1245 }
944 1246
945 return $code." );\n"; 1247 return $code." );\n";
1248 }
1249
1250 private function addInlineRequires()
1251 {
1252 if (!$this->hotPathTag || !$this->inlineRequires) {
1253 return '';
1254 }
1255
1256 $lineage = array();
1257
1258 foreach ($this->container->findTaggedServiceIds($this->hotPathTag) as $id => $tags) {
1259 $definition = $this->container->getDefinition($id);
1260 $inlinedDefinitions = $this->getDefinitionsFromArguments(array($definition));
1261
1262 foreach ($inlinedDefinitions as $def) {
1263 if (is_string($class = is_array($factory = $def->getFactory()) && is_string($factory[0]) ? $factory[0] : $def->getClass())) {
1264 $this->collectLineage($class, $lineage);
1265 }
1266 }
1267 }
1268
1269 $code = '';
1270
1271 foreach ($lineage as $file) {
1272 if (!isset($this->inlinedRequires[$file])) {
1273 $this->inlinedRequires[$file] = true;
1274 $code .= sprintf("\n include_once %s;", $file);
1275 }
1276 }
1277
1278 return $code ? sprintf("\n \$this->privates['service_container'] = function () {%s\n };\n", $code) : '';
946 } 1279 }
947 1280
948 /** 1281 /**
949 * Adds default parameters method. 1282 * Adds default parameters method.
950 * 1283 *
956 return ''; 1289 return '';
957 } 1290 }
958 1291
959 $php = array(); 1292 $php = array();
960 $dynamicPhp = array(); 1293 $dynamicPhp = array();
1294 $normalizedParams = array();
961 1295
962 foreach ($this->container->getParameterBag()->all() as $key => $value) { 1296 foreach ($this->container->getParameterBag()->all() as $key => $value) {
963 if ($key !== $resolvedKey = $this->container->resolveEnvPlaceholders($key)) { 1297 if ($key !== $resolvedKey = $this->container->resolveEnvPlaceholders($key)) {
964 throw new InvalidArgumentException(sprintf('Parameter name cannot use env parameters: %s.', $resolvedKey)); 1298 throw new InvalidArgumentException(sprintf('Parameter name cannot use env parameters: %s.', $resolvedKey));
965 } 1299 }
1300 if ($key !== $lcKey = strtolower($key)) {
1301 $normalizedParams[] = sprintf(' %s => %s,', $this->export($lcKey), $this->export($key));
1302 }
966 $export = $this->exportParameters(array($value)); 1303 $export = $this->exportParameters(array($value));
967 $export = explode('0 => ', substr(rtrim($export, " )\n"), 7, -1), 2); 1304 $export = explode('0 => ', substr(rtrim($export, " )\n"), 7, -1), 2);
968 1305
969 if (preg_match("/\\\$this->(?:getEnv\('\w++'\)|targetDirs\[\d++\])/", $export[1])) { 1306 if (preg_match("/\\\$this->(?:getEnv\('(?:\w++:)*+\w++'\)|targetDirs\[\d++\])/", $export[1])) {
970 $dynamicPhp[$key] = sprintf('%scase %s: $value = %s; break;', $export[0], $this->export($key), $export[1]); 1307 $dynamicPhp[$key] = sprintf('%scase %s: $value = %s; break;', $export[0], $this->export($key), $export[1]);
971 } else { 1308 } else {
972 $php[] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]); 1309 $php[] = sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]);
973 } 1310 }
974 } 1311 }
975 $parameters = sprintf("array(\n%s\n%s)", implode("\n", $php), str_repeat(' ', 8)); 1312 $parameters = sprintf("array(\n%s\n%s)", implode("\n", $php), str_repeat(' ', 8));
976 1313
977 $code = ''; 1314 $code = '';
978 if ($this->container->isFrozen()) { 1315 if ($this->container->isCompiled()) {
979 $code .= <<<'EOF' 1316 $code .= <<<'EOF'
980 1317
981 /**
982 * {@inheritdoc}
983 */
984 public function getParameter($name) 1318 public function getParameter($name)
985 { 1319 {
986 $name = strtolower($name); 1320 $name = (string) $name;
987 1321 if (isset($this->buildParameters[$name])) {
988 if (!(isset($this->parameters[$name]) || array_key_exists($name, $this->parameters) || isset($this->loadedDynamicParameters[$name]))) { 1322 return $this->buildParameters[$name];
989 throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name)); 1323 }
1324 if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters))) {
1325 $name = $this->normalizeParameterName($name);
1326
1327 if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters))) {
1328 throw new InvalidArgumentException(sprintf('The parameter "%s" must be defined.', $name));
1329 }
990 } 1330 }
991 if (isset($this->loadedDynamicParameters[$name])) { 1331 if (isset($this->loadedDynamicParameters[$name])) {
992 return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); 1332 return $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
993 } 1333 }
994 1334
995 return $this->parameters[$name]; 1335 return $this->parameters[$name];
996 } 1336 }
997 1337
998 /**
999 * {@inheritdoc}
1000 */
1001 public function hasParameter($name) 1338 public function hasParameter($name)
1002 { 1339 {
1003 $name = strtolower($name); 1340 $name = (string) $name;
1004 1341 if (isset($this->buildParameters[$name])) {
1005 return isset($this->parameters[$name]) || array_key_exists($name, $this->parameters) || isset($this->loadedDynamicParameters[$name]); 1342 return true;
1006 } 1343 }
1007 1344 $name = $this->normalizeParameterName($name);
1008 /** 1345
1009 * {@inheritdoc} 1346 return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || array_key_exists($name, $this->parameters);
1010 */ 1347 }
1348
1011 public function setParameter($name, $value) 1349 public function setParameter($name, $value)
1012 { 1350 {
1013 throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); 1351 throw new LogicException('Impossible to call set() on a frozen ParameterBag.');
1014 } 1352 }
1015 1353
1016 /**
1017 * {@inheritdoc}
1018 */
1019 public function getParameterBag() 1354 public function getParameterBag()
1020 { 1355 {
1021 if (null === $this->parameterBag) { 1356 if (null === $this->parameterBag) {
1022 $parameters = $this->parameters; 1357 $parameters = $this->parameters;
1023 foreach ($this->loadedDynamicParameters as $name => $loaded) { 1358 foreach ($this->loadedDynamicParameters as $name => $loaded) {
1024 $parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); 1359 $parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name);
1025 } 1360 }
1361 foreach ($this->buildParameters as $name => $value) {
1362 $parameters[$name] = $value;
1363 }
1026 $this->parameterBag = new FrozenParameterBag($parameters); 1364 $this->parameterBag = new FrozenParameterBag($parameters);
1027 } 1365 }
1028 1366
1029 return $this->parameterBag; 1367 return $this->parameterBag;
1030 } 1368 }
1031 1369
1032 EOF; 1370 EOF;
1033 if ('' === $this->docStar) { 1371 if (!$this->asFiles) {
1034 $code = str_replace('/**', '/*', $code); 1372 $code = preg_replace('/^.*buildParameters.*\n.*\n.*\n/m', '', $code);
1035 } 1373 }
1036 1374
1037 if ($dynamicPhp) { 1375 if ($dynamicPhp) {
1038 $loadedDynamicParameters = $this->exportParameters(array_combine(array_keys($dynamicPhp), array_fill(0, count($dynamicPhp), false)), '', 8); 1376 $loadedDynamicParameters = $this->exportParameters(array_combine(array_keys($dynamicPhp), array_fill(0, count($dynamicPhp), false)), '', 8);
1039 $getDynamicParameter = <<<'EOF' 1377 $getDynamicParameter = <<<'EOF'
1068 private function getDynamicParameter(\$name) 1406 private function getDynamicParameter(\$name)
1069 { 1407 {
1070 {$getDynamicParameter} 1408 {$getDynamicParameter}
1071 } 1409 }
1072 1410
1411
1412 EOF;
1413
1414 $code .= ' private $normalizedParameterNames = '.($normalizedParams ? sprintf("array(\n%s\n );", implode("\n", $normalizedParams)) : 'array();')."\n";
1415 $code .= <<<'EOF'
1416
1417 private function normalizeParameterName($name)
1418 {
1419 if (isset($this->normalizedParameterNames[$normalizedName = strtolower($name)]) || isset($this->parameters[$normalizedName]) || array_key_exists($normalizedName, $this->parameters)) {
1420 $normalizedName = isset($this->normalizedParameterNames[$normalizedName]) ? $this->normalizedParameterNames[$normalizedName] : $normalizedName;
1421 if ((string) $name !== $normalizedName) {
1422 @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);
1423 }
1424 } else {
1425 $normalizedName = $this->normalizedParameterNames[$normalizedName] = (string) $name;
1426 }
1427
1428 return $normalizedName;
1429 }
1430
1073 EOF; 1431 EOF;
1074 } elseif ($dynamicPhp) { 1432 } elseif ($dynamicPhp) {
1075 throw new RuntimeException('You cannot dump a not-frozen container with dynamic parameters.'); 1433 throw new RuntimeException('You cannot dump a not-frozen container with dynamic parameters.');
1076 } 1434 }
1077 1435
1107 { 1465 {
1108 $php = array(); 1466 $php = array();
1109 foreach ($parameters as $key => $value) { 1467 foreach ($parameters as $key => $value) {
1110 if (is_array($value)) { 1468 if (is_array($value)) {
1111 $value = $this->exportParameters($value, $path.'/'.$key, $indent + 4); 1469 $value = $this->exportParameters($value, $path.'/'.$key, $indent + 4);
1470 } elseif ($value instanceof ArgumentInterface) {
1471 throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain special arguments. "%s" found in "%s".', get_class($value), $path.'/'.$key));
1112 } elseif ($value instanceof Variable) { 1472 } elseif ($value instanceof Variable) {
1113 throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain variable references. Variable "%s" found in "%s".', $value, $path.'/'.$key)); 1473 throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain variable references. Variable "%s" found in "%s".', $value, $path.'/'.$key));
1114 } elseif ($value instanceof Definition) { 1474 } elseif ($value instanceof Definition) {
1115 throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain service definitions. Definition for "%s" found in "%s".', $value->getClass(), $path.'/'.$key)); 1475 throw new InvalidArgumentException(sprintf('You cannot dump a container with parameters that contain service definitions. Definition for "%s" found in "%s".', $value->getClass(), $path.'/'.$key));
1116 } elseif ($value instanceof Reference) { 1476 } elseif ($value instanceof Reference) {
1148 * 1508 *
1149 * @return string 1509 * @return string
1150 */ 1510 */
1151 private function wrapServiceConditionals($value, $code) 1511 private function wrapServiceConditionals($value, $code)
1152 { 1512 {
1153 if (!$services = ContainerBuilder::getServiceConditionals($value)) { 1513 if (!$condition = $this->getServiceConditionals($value)) {
1154 return $code; 1514 return $code;
1155 } 1515 }
1156 1516
1517 // re-indent the wrapped code
1518 $code = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $code)));
1519
1520 return sprintf(" if (%s) {\n%s }\n", $condition, $code);
1521 }
1522
1523 /**
1524 * Get the conditions to execute for conditional services.
1525 *
1526 * @param string $value
1527 *
1528 * @return null|string
1529 */
1530 private function getServiceConditionals($value)
1531 {
1157 $conditions = array(); 1532 $conditions = array();
1158 foreach ($services as $service) { 1533 foreach (ContainerBuilder::getInitializedConditionals($value) as $service) {
1534 if (!$this->container->hasDefinition($service)) {
1535 return 'false';
1536 }
1537 $conditions[] = sprintf("isset(\$this->services['%s'])", $service);
1538 }
1539 foreach (ContainerBuilder::getServiceConditionals($value) as $service) {
1159 if ($this->container->hasDefinition($service) && !$this->container->getDefinition($service)->isPublic()) { 1540 if ($this->container->hasDefinition($service) && !$this->container->getDefinition($service)->isPublic()) {
1160 continue; 1541 continue;
1161 } 1542 }
1162 1543
1163 $conditions[] = sprintf("\$this->has('%s')", $service); 1544 $conditions[] = sprintf("\$this->has('%s')", $service);
1164 } 1545 }
1165 1546
1166 if (!$conditions) { 1547 if (!$conditions) {
1167 return $code; 1548 return '';
1168 } 1549 }
1169 1550
1170 // re-indent the wrapped code 1551 return implode(' && ', $conditions);
1171 $code = implode("\n", array_map(function ($line) { return $line ? ' '.$line : $line; }, explode("\n", $code)));
1172
1173 return sprintf(" if (%s) {\n%s }\n", implode(' && ', $conditions), $code);
1174 } 1552 }
1175 1553
1176 /** 1554 /**
1177 * Builds service calls from arguments. 1555 * Builds service calls from arguments.
1178 * 1556 */
1179 * @param array $arguments 1557 private function getServiceCallsFromArguments(array $arguments, array &$calls, $isPreInstance, $callerId, array &$behavior = array(), $step = 1)
1180 * @param array &$calls By reference
1181 * @param array &$behavior By reference
1182 */
1183 private function getServiceCallsFromArguments(array $arguments, array &$calls, array &$behavior)
1184 { 1558 {
1185 foreach ($arguments as $argument) { 1559 foreach ($arguments as $argument) {
1186 if (is_array($argument)) { 1560 if (is_array($argument)) {
1187 $this->getServiceCallsFromArguments($argument, $calls, $behavior); 1561 $this->getServiceCallsFromArguments($argument, $calls, $isPreInstance, $callerId, $behavior, $step);
1188 } elseif ($argument instanceof Reference) { 1562 } elseif ($argument instanceof Reference) {
1189 $id = (string) $argument; 1563 $id = $this->container->normalizeId($argument);
1190 1564
1191 if (!isset($calls[$id])) { 1565 if (!isset($calls[$id])) {
1192 $calls[$id] = 0; 1566 $calls[$id] = (int) ($isPreInstance && isset($this->circularReferences[$callerId][$id]));
1193 } 1567 }
1194 if (!isset($behavior[$id])) { 1568 if (!isset($behavior[$id])) {
1195 $behavior[$id] = $argument->getInvalidBehavior(); 1569 $behavior[$id] = $argument->getInvalidBehavior();
1196 } elseif (ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $behavior[$id]) { 1570 } else {
1197 $behavior[$id] = $argument->getInvalidBehavior(); 1571 $behavior[$id] = min($behavior[$id], $argument->getInvalidBehavior());
1198 } 1572 }
1199 1573
1200 ++$calls[$id]; 1574 $calls[$id] += $step;
1201 } 1575 }
1202 } 1576 }
1203 } 1577 }
1204 1578
1205 /** 1579 private function getDefinitionsFromArguments(array $arguments, \SplObjectStorage $definitions = null)
1206 * Returns the inline definition. 1580 {
1207 * 1581 if (null === $definitions) {
1208 * @param Definition $definition 1582 $definitions = new \SplObjectStorage();
1209 * 1583 }
1210 * @return array 1584
1211 */
1212 private function getInlinedDefinitions(Definition $definition)
1213 {
1214 if (false === $this->inlinedDefinitions->contains($definition)) {
1215 $definitions = array_merge(
1216 $this->getDefinitionsFromArguments($definition->getArguments()),
1217 $this->getDefinitionsFromArguments($definition->getMethodCalls()),
1218 $this->getDefinitionsFromArguments($definition->getProperties()),
1219 $this->getDefinitionsFromArguments(array($definition->getConfigurator())),
1220 $this->getDefinitionsFromArguments(array($definition->getFactory()))
1221 );
1222
1223 $this->inlinedDefinitions->offsetSet($definition, $definitions);
1224
1225 return $definitions;
1226 }
1227
1228 return $this->inlinedDefinitions->offsetGet($definition);
1229 }
1230
1231 /**
1232 * Gets the definition from arguments.
1233 *
1234 * @param array $arguments
1235 *
1236 * @return array
1237 */
1238 private function getDefinitionsFromArguments(array $arguments)
1239 {
1240 $definitions = array();
1241 foreach ($arguments as $argument) { 1585 foreach ($arguments as $argument) {
1242 if (is_array($argument)) { 1586 if (is_array($argument)) {
1243 $definitions = array_merge($definitions, $this->getDefinitionsFromArguments($argument)); 1587 $this->getDefinitionsFromArguments($argument, $definitions);
1244 } elseif ($argument instanceof Definition) { 1588 } elseif (!$argument instanceof Definition) {
1245 $definitions = array_merge( 1589 // no-op
1246 $definitions, 1590 } elseif (isset($definitions[$argument])) {
1247 $this->getInlinedDefinitions($argument), 1591 $definitions[$argument] = 1 + $definitions[$argument];
1248 array($argument) 1592 } else {
1249 ); 1593 $definitions[$argument] = 1;
1594 $this->getDefinitionsFromArguments($argument->getArguments(), $definitions);
1595 $this->getDefinitionsFromArguments(array($argument->getFactory()), $definitions);
1596 $this->getDefinitionsFromArguments($argument->getProperties(), $definitions);
1597 $this->getDefinitionsFromArguments($argument->getMethodCalls(), $definitions);
1598 $this->getDefinitionsFromArguments(array($argument->getConfigurator()), $definitions);
1599 // move current definition last in the list
1600 $nbOccurences = $definitions[$argument];
1601 unset($definitions[$argument]);
1602 $definitions[$argument] = $nbOccurences;
1250 } 1603 }
1251 } 1604 }
1252 1605
1253 return $definitions; 1606 return $definitions;
1254 } 1607 }
1263 * 1616 *
1264 * @return bool 1617 * @return bool
1265 */ 1618 */
1266 private function hasReference($id, array $arguments, $deep = false, array &$visited = array()) 1619 private function hasReference($id, array $arguments, $deep = false, array &$visited = array())
1267 { 1620 {
1621 if (!isset($this->circularReferences[$id])) {
1622 return false;
1623 }
1624
1268 foreach ($arguments as $argument) { 1625 foreach ($arguments as $argument) {
1269 if (is_array($argument)) { 1626 if (is_array($argument)) {
1270 if ($this->hasReference($id, $argument, $deep, $visited)) { 1627 if ($this->hasReference($id, $argument, $deep, $visited)) {
1271 return true; 1628 return true;
1272 } 1629 }
1630
1631 continue;
1273 } elseif ($argument instanceof Reference) { 1632 } elseif ($argument instanceof Reference) {
1274 $argumentId = (string) $argument; 1633 $argumentId = $this->container->normalizeId($argument);
1275 if ($id === $argumentId) { 1634 if ($id === $argumentId) {
1276 return true; 1635 return true;
1277 } 1636 }
1278 1637
1279 if ($deep && !isset($visited[$argumentId]) && 'service_container' !== $argumentId) { 1638 if (!$deep || isset($visited[$argumentId]) || !isset($this->circularReferences[$id][$argumentId])) {
1280 $visited[$argumentId] = true; 1639 continue;
1281 1640 }
1282 $service = $this->container->getDefinition($argumentId); 1641
1283 1642 $visited[$argumentId] = true;
1284 // if the proxy manager is enabled, disable searching for references in lazy services, 1643
1285 // as these services will be instantiated lazily and don't have direct related references. 1644 $service = $this->container->getDefinition($argumentId);
1286 if ($service->isLazy() && !$this->getProxyDumper() instanceof NullDumper) { 1645 } elseif ($argument instanceof Definition) {
1287 continue; 1646 $service = $argument;
1288 } 1647 } else {
1289 1648 continue;
1290 $arguments = array_merge($service->getMethodCalls(), $service->getArguments(), $service->getProperties()); 1649 }
1291 1650
1292 if ($this->hasReference($id, $arguments, $deep, $visited)) { 1651 // if the proxy manager is enabled, disable searching for references in lazy services,
1293 return true; 1652 // as these services will be instantiated lazily and don't have direct related references.
1294 } 1653 if ($service->isLazy() && !$this->getProxyDumper() instanceof NullDumper) {
1295 } 1654 continue;
1655 }
1656
1657 if ($this->hasReference($id, array($service->getArguments(), $service->getFactory(), $service->getProperties(), $service->getMethodCalls(), $service->getConfigurator()), $deep, $visited)) {
1658 return true;
1296 } 1659 }
1297 } 1660 }
1298 1661
1299 return false; 1662 return false;
1300 } 1663 }
1310 * @throws RuntimeException 1673 * @throws RuntimeException
1311 */ 1674 */
1312 private function dumpValue($value, $interpolate = true) 1675 private function dumpValue($value, $interpolate = true)
1313 { 1676 {
1314 if (is_array($value)) { 1677 if (is_array($value)) {
1678 if ($value && $interpolate && false !== $param = array_search($value, $this->container->getParameterBag()->all(), true)) {
1679 return $this->dumpValue("%$param%");
1680 }
1315 $code = array(); 1681 $code = array();
1316 foreach ($value as $k => $v) { 1682 foreach ($value as $k => $v) {
1317 $code[] = sprintf('%s => %s', $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate)); 1683 $code[] = sprintf('%s => %s', $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate));
1318 } 1684 }
1319 1685
1320 return sprintf('array(%s)', implode(', ', $code)); 1686 return sprintf('array(%s)', implode(', ', $code));
1687 } elseif ($value instanceof ArgumentInterface) {
1688 $scope = array($this->definitionVariables, $this->referenceVariables, $this->variableCount);
1689 $this->definitionVariables = $this->referenceVariables = null;
1690
1691 try {
1692 if ($value instanceof ServiceClosureArgument) {
1693 $value = $value->getValues()[0];
1694 $code = $this->dumpValue($value, $interpolate);
1695
1696 if ($value instanceof TypedReference) {
1697 $code = sprintf('$f = function (\\%s $v%s) { return $v; }; return $f(%s);', $value->getType(), ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $value->getInvalidBehavior() ? ' = null' : '', $code);
1698 } else {
1699 $code = sprintf('return %s;', $code);
1700 }
1701
1702 return sprintf("function () {\n %s\n }", $code);
1703 }
1704
1705 if ($value instanceof IteratorArgument) {
1706 $operands = array(0);
1707 $code = array();
1708 $code[] = 'new RewindableGenerator(function () {';
1709
1710 if (!$values = $value->getValues()) {
1711 $code[] = ' return new \EmptyIterator();';
1712 } else {
1713 $countCode = array();
1714 $countCode[] = 'function () {';
1715
1716 foreach ($values as $k => $v) {
1717 ($c = $this->getServiceConditionals($v)) ? $operands[] = "(int) ($c)" : ++$operands[0];
1718 $v = $this->wrapServiceConditionals($v, sprintf(" yield %s => %s;\n", $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate)));
1719 foreach (explode("\n", $v) as $v) {
1720 if ($v) {
1721 $code[] = ' '.$v;
1722 }
1723 }
1724 }
1725
1726 $countCode[] = sprintf(' return %s;', implode(' + ', $operands));
1727 $countCode[] = ' }';
1728 }
1729
1730 $code[] = sprintf(' }, %s)', count($operands) > 1 ? implode("\n", $countCode) : $operands[0]);
1731
1732 return implode("\n", $code);
1733 }
1734 } finally {
1735 list($this->definitionVariables, $this->referenceVariables, $this->variableCount) = $scope;
1736 }
1321 } elseif ($value instanceof Definition) { 1737 } elseif ($value instanceof Definition) {
1322 if (null !== $this->definitionVariables && $this->definitionVariables->contains($value)) { 1738 if (null !== $this->definitionVariables && $this->definitionVariables->contains($value)) {
1323 return $this->dumpValue($this->definitionVariables->offsetGet($value), $interpolate); 1739 return $this->dumpValue($this->definitionVariables[$value], $interpolate);
1324 } 1740 }
1325 if ($value->getMethodCalls()) { 1741 if ($value->getMethodCalls()) {
1326 throw new RuntimeException('Cannot dump definitions which have method calls.'); 1742 throw new RuntimeException('Cannot dump definitions which have method calls.');
1327 } 1743 }
1328 if ($value->getProperties()) { 1744 if ($value->getProperties()) {
1347 if (is_array($factory)) { 1763 if (is_array($factory)) {
1348 if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $factory[1])) { 1764 if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $factory[1])) {
1349 throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s)', $factory[1] ?: 'n/a')); 1765 throw new RuntimeException(sprintf('Cannot dump definition because of invalid factory method (%s)', $factory[1] ?: 'n/a'));
1350 } 1766 }
1351 1767
1768 $class = $this->dumpValue($factory[0]);
1352 if (is_string($factory[0])) { 1769 if (is_string($factory[0])) {
1353 return sprintf('%s::%s(%s)', $this->dumpLiteralClass($this->dumpValue($factory[0])), $factory[1], implode(', ', $arguments)); 1770 return sprintf('%s::%s(%s)', $this->dumpLiteralClass($class), $factory[1], implode(', ', $arguments));
1354 } 1771 }
1355 1772
1356 if ($factory[0] instanceof Definition) { 1773 if ($factory[0] instanceof Definition) {
1357 return sprintf("call_user_func(array(%s, '%s')%s)", $this->dumpValue($factory[0]), $factory[1], count($arguments) > 0 ? ', '.implode(', ', $arguments) : ''); 1774 if (0 === strpos($class, 'new ')) {
1775 return sprintf('(%s)->%s(%s)', $class, $factory[1], implode(', ', $arguments));
1776 }
1777
1778 return sprintf("\\call_user_func(array(%s, '%s')%s)", $class, $factory[1], count($arguments) > 0 ? ', '.implode(', ', $arguments) : '');
1358 } 1779 }
1359 1780
1360 if ($factory[0] instanceof Reference) { 1781 if ($factory[0] instanceof Reference) {
1361 return sprintf('%s->%s(%s)', $this->dumpValue($factory[0]), $factory[1], implode(', ', $arguments)); 1782 return sprintf('%s->%s(%s)', $class, $factory[1], implode(', ', $arguments));
1362 } 1783 }
1363 } 1784 }
1364 1785
1365 throw new RuntimeException('Cannot dump definition because of invalid factory'); 1786 throw new RuntimeException('Cannot dump definition because of invalid factory');
1366 } 1787 }
1372 1793
1373 return sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments)); 1794 return sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments));
1374 } elseif ($value instanceof Variable) { 1795 } elseif ($value instanceof Variable) {
1375 return '$'.$value; 1796 return '$'.$value;
1376 } elseif ($value instanceof Reference) { 1797 } elseif ($value instanceof Reference) {
1377 if (null !== $this->referenceVariables && isset($this->referenceVariables[$id = (string) $value])) { 1798 $id = $this->container->normalizeId($value);
1799 if (null !== $this->referenceVariables && isset($this->referenceVariables[$id])) {
1378 return $this->dumpValue($this->referenceVariables[$id], $interpolate); 1800 return $this->dumpValue($this->referenceVariables[$id], $interpolate);
1379 } 1801 }
1380 1802
1381 return $this->getServiceCall((string) $value, $value); 1803 return $this->getServiceCall($id, $value);
1382 } elseif ($value instanceof Expression) { 1804 } elseif ($value instanceof Expression) {
1383 return $this->getExpressionLanguage()->compile((string) $value, array('this' => 'container')); 1805 return $this->getExpressionLanguage()->compile((string) $value, array('this' => 'container'));
1384 } elseif ($value instanceof Parameter) { 1806 } elseif ($value instanceof Parameter) {
1385 return $this->dumpParameter($value); 1807 return $this->dumpParameter($value);
1386 } elseif (true === $interpolate && is_string($value)) { 1808 } elseif (true === $interpolate && is_string($value)) {
1387 if (preg_match('/^%([^%]+)%$/', $value, $match)) { 1809 if (preg_match('/^%([^%]+)%$/', $value, $match)) {
1388 // we do this to deal with non string values (Boolean, integer, ...) 1810 // we do this to deal with non string values (Boolean, integer, ...)
1389 // the preg_replace_callback converts them to strings 1811 // the preg_replace_callback converts them to strings
1390 return $this->dumpParameter(strtolower($match[1])); 1812 return $this->dumpParameter($match[1]);
1391 } else { 1813 } else {
1392 $replaceParameters = function ($match) { 1814 $replaceParameters = function ($match) {
1393 return "'.".$this->dumpParameter(strtolower($match[2])).".'"; 1815 return "'.".$this->dumpParameter($match[2]).".'";
1394 }; 1816 };
1395 1817
1396 $code = str_replace('%%', '%', preg_replace_callback('/(?<!%)(%)([^%]+)\1/', $replaceParameters, $this->export($value))); 1818 $code = str_replace('%%', '%', preg_replace_callback('/(?<!%)(%)([^%]+)\1/', $replaceParameters, $this->export($value)));
1397 1819
1398 return $code; 1820 return $code;
1434 * 1856 *
1435 * @return string 1857 * @return string
1436 */ 1858 */
1437 private function dumpParameter($name) 1859 private function dumpParameter($name)
1438 { 1860 {
1439 if ($this->container->isFrozen() && $this->container->hasParameter($name)) { 1861 if ($this->container->isCompiled() && $this->container->hasParameter($name)) {
1440 return $this->dumpValue($this->container->getParameter($name), false); 1862 $value = $this->container->getParameter($name);
1441 } 1863 $dumpedValue = $this->dumpValue($value, false);
1442 1864
1443 return sprintf("\$this->getParameter('%s')", strtolower($name)); 1865 if (!$value || !is_array($value)) {
1866 return $dumpedValue;
1867 }
1868
1869 if (!preg_match("/\\\$this->(?:getEnv\('(?:\w++:)*+\w++'\)|targetDirs\[\d++\])/", $dumpedValue)) {
1870 return sprintf("\$this->parameters['%s']", $name);
1871 }
1872 }
1873
1874 return sprintf("\$this->getParameter('%s')", $name);
1444 } 1875 }
1445 1876
1446 /** 1877 /**
1447 * Gets a service call. 1878 * Gets a service call.
1448 * 1879 *
1454 private function getServiceCall($id, Reference $reference = null) 1885 private function getServiceCall($id, Reference $reference = null)
1455 { 1886 {
1456 while ($this->container->hasAlias($id)) { 1887 while ($this->container->hasAlias($id)) {
1457 $id = (string) $this->container->getAlias($id); 1888 $id = (string) $this->container->getAlias($id);
1458 } 1889 }
1890 $id = $this->container->normalizeId($id);
1459 1891
1460 if ('service_container' === $id) { 1892 if ('service_container' === $id) {
1461 return '$this'; 1893 return '$this';
1462 } 1894 }
1463 1895
1464 if ($this->container->hasDefinition($id) && !$this->container->getDefinition($id)->isPublic()) { 1896 if ($this->container->hasDefinition($id) && ($definition = $this->container->getDefinition($id)) && !$definition->isSynthetic()) {
1465 // The following is PHP 5.5 syntax for what could be written as "(\$this->services['$id'] ?? \$this->{$this->generateMethodName($id)}())" on PHP>=7.0 1897 if (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) {
1466 1898 $code = 'null';
1467 return "\${(\$_ = isset(\$this->services['$id']) ? \$this->services['$id'] : \$this->{$this->generateMethodName($id)}()) && false ?: '_'}"; 1899 } elseif ($this->isTrivialInstance($definition)) {
1468 } 1900 $code = substr($this->addNewInstance($definition, '', '', $id), 8, -2);
1469 if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) { 1901 if ($definition->isShared()) {
1470 return sprintf('$this->get(\'%s\', ContainerInterface::NULL_ON_INVALID_REFERENCE)', $id); 1902 $code = sprintf('$this->services[\'%s\'] = %s', $id, $code);
1471 } 1903 }
1472 1904 } elseif ($this->asFiles && $definition->isShared() && !$this->isHotPath($definition)) {
1473 return sprintf('$this->get(\'%s\')', $id); 1905 $code = sprintf("\$this->load('%s.php')", $this->generateMethodName($id));
1906 } else {
1907 $code = sprintf('$this->%s()', $this->generateMethodName($id));
1908 }
1909 } elseif (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) {
1910 return 'null';
1911 } elseif (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE !== $reference->getInvalidBehavior()) {
1912 $code = sprintf('$this->get(\'%s\', /* ContainerInterface::NULL_ON_INVALID_REFERENCE */ %d)', $id, ContainerInterface::NULL_ON_INVALID_REFERENCE);
1913 } else {
1914 $code = sprintf('$this->get(\'%s\')', $id);
1915 }
1916
1917 // The following is PHP 5.5 syntax for what could be written as "(\$this->services['$id'] ?? $code)" on PHP>=7.0
1918
1919 return "\${(\$_ = isset(\$this->services['$id']) ? \$this->services['$id'] : $code) && false ?: '_'}";
1474 } 1920 }
1475 1921
1476 /** 1922 /**
1477 * Initializes the method names map to avoid conflicts with the Container methods. 1923 * Initializes the method names map to avoid conflicts with the Container methods.
1478 * 1924 *
1481 private function initializeMethodNamesMap($class) 1927 private function initializeMethodNamesMap($class)
1482 { 1928 {
1483 $this->serviceIdToMethodNameMap = array(); 1929 $this->serviceIdToMethodNameMap = array();
1484 $this->usedMethodNames = array(); 1930 $this->usedMethodNames = array();
1485 1931
1486 try { 1932 if ($reflectionClass = $this->container->getReflectionClass($class)) {
1487 $reflectionClass = new \ReflectionClass($class);
1488 foreach ($reflectionClass->getMethods() as $method) { 1933 foreach ($reflectionClass->getMethods() as $method) {
1489 $this->usedMethodNames[strtolower($method->getName())] = true; 1934 $this->usedMethodNames[strtolower($method->getName())] = true;
1490 } 1935 }
1491 } catch (\ReflectionException $e) {
1492 } 1936 }
1493 } 1937 }
1494 1938
1495 /** 1939 /**
1496 * Convert a service id to a valid PHP method name. 1940 * Convert a service id to a valid PHP method name.
1505 { 1949 {
1506 if (isset($this->serviceIdToMethodNameMap[$id])) { 1950 if (isset($this->serviceIdToMethodNameMap[$id])) {
1507 return $this->serviceIdToMethodNameMap[$id]; 1951 return $this->serviceIdToMethodNameMap[$id];
1508 } 1952 }
1509 1953
1510 $name = Container::camelize($id); 1954 $i = strrpos($id, '\\');
1955 $name = Container::camelize(false !== $i && isset($id[1 + $i]) ? substr($id, 1 + $i) : $id);
1511 $name = preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '', $name); 1956 $name = preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '', $name);
1512 $methodName = 'get'.$name.'Service'; 1957 $methodName = 'get'.$name.'Service';
1513 $suffix = 1; 1958 $suffix = 1;
1514 1959
1515 while (isset($this->usedMethodNames[strtolower($methodName)])) { 1960 while (isset($this->usedMethodNames[strtolower($methodName)])) {
1586 } 2031 }
1587 2032
1588 return $this->expressionLanguage; 2033 return $this->expressionLanguage;
1589 } 2034 }
1590 2035
1591 private function exportTargetDirs() 2036 private function isHotPath(Definition $definition)
1592 { 2037 {
1593 return null === $this->targetDirRegex ? '' : <<<EOF 2038 return $this->hotPathTag && $definition->hasTag($this->hotPathTag) && !$definition->isDeprecated();
1594
1595 \$dir = __DIR__;
1596 for (\$i = 1; \$i <= {$this->targetDirMaxMatches}; ++\$i) {
1597 \$this->targetDirs[\$i] = \$dir = dirname(\$dir);
1598 }
1599 EOF;
1600 } 2039 }
1601 2040
1602 private function export($value) 2041 private function export($value)
1603 { 2042 {
1604 if (null !== $this->targetDirRegex && is_string($value) && preg_match($this->targetDirRegex, $value, $matches, PREG_OFFSET_CAPTURE)) { 2043 if (null !== $this->targetDirRegex && is_string($value) && preg_match($this->targetDirRegex, $value, $matches, PREG_OFFSET_CAPTURE)) {
1605 $prefix = $matches[0][1] ? $this->doExport(substr($value, 0, $matches[0][1])).'.' : ''; 2044 $prefix = $matches[0][1] ? $this->doExport(substr($value, 0, $matches[0][1]), true).'.' : '';
1606 $suffix = $matches[0][1] + strlen($matches[0][0]); 2045 $suffix = $matches[0][1] + strlen($matches[0][0]);
1607 $suffix = isset($value[$suffix]) ? '.'.$this->doExport(substr($value, $suffix)) : ''; 2046 $suffix = isset($value[$suffix]) ? '.'.$this->doExport(substr($value, $suffix), true) : '';
1608 $dirname = '__DIR__'; 2047 $dirname = $this->asFiles ? '$this->containerDir' : '__DIR__';
1609 2048 $offset = 1 + $this->targetDirMaxMatches - count($matches);
1610 if (0 < $offset = 1 + $this->targetDirMaxMatches - count($matches)) { 2049
2050 if ($this->asFiles || 0 < $offset) {
1611 $dirname = sprintf('$this->targetDirs[%d]', $offset); 2051 $dirname = sprintf('$this->targetDirs[%d]', $offset);
1612 } 2052 }
1613 2053
1614 if ($prefix || $suffix) { 2054 if ($prefix || $suffix) {
1615 return sprintf('(%s%s%s)', $prefix, $dirname, $suffix); 2055 return sprintf('(%s%s%s)', $prefix, $dirname, $suffix);
1616 } 2056 }
1617 2057
1618 return $dirname; 2058 return $dirname;
1619 } 2059 }
1620 2060
1621 return $this->doExport($value); 2061 return $this->doExport($value, true);
1622 } 2062 }
1623 2063
1624 private function doExport($value) 2064 private function doExport($value, $resolveEnv = false)
1625 { 2065 {
1626 $export = var_export($value, true); 2066 if (is_string($value) && false !== strpos($value, "\n")) {
1627 2067 $cleanParts = explode("\n", $value);
1628 if ("'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$this->getEnv('%s').'")) { 2068 $cleanParts = array_map(function ($part) { return var_export($part, true); }, $cleanParts);
2069 $export = implode('."\n".', $cleanParts);
2070 } else {
2071 $export = var_export($value, true);
2072 }
2073
2074 if ($resolveEnv && "'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$this->getEnv('string:%s').'")) {
1629 $export = $resolvedExport; 2075 $export = $resolvedExport;
2076 if (".''" === substr($export, -3)) {
2077 $export = substr($export, 0, -3);
2078 if ("'" === $export[1]) {
2079 $export = substr_replace($export, '', 18, 7);
2080 }
2081 }
1630 if ("'" === $export[1]) { 2082 if ("'" === $export[1]) {
1631 $export = substr($export, 3); 2083 $export = substr($export, 3);
1632 } 2084 }
1633 if (".''" === substr($export, -3)) {
1634 $export = substr($export, 0, -3);
1635 }
1636 } 2085 }
1637 2086
1638 return $export; 2087 return $export;
1639 } 2088 }
1640 } 2089 }