|
1
|
|
|
<?php |
|
2
|
|
|
|
|
3
|
|
|
/* |
|
4
|
|
|
* This file is part of the Symfony package. |
|
5
|
|
|
* |
|
6
|
|
|
* (c) Fabien Potencier <[email protected]> |
|
7
|
|
|
* |
|
8
|
|
|
* For the full copyright and license information, please view the LICENSE |
|
9
|
|
|
* file that was distributed with this source code. |
|
10
|
|
|
*/ |
|
11
|
|
|
|
|
12
|
|
|
namespace Symfony\Component\DependencyInjection\Dumper; |
|
13
|
|
|
|
|
14
|
|
|
use Composer\Autoload\ClassLoader; |
|
15
|
|
|
use Symfony\Component\Config\Resource\FileResource; |
|
16
|
|
|
use Symfony\Component\DependencyInjection\Argument\AbstractArgument; |
|
17
|
|
|
use Symfony\Component\DependencyInjection\Argument\ArgumentInterface; |
|
18
|
|
|
use Symfony\Component\DependencyInjection\Argument\IteratorArgument; |
|
19
|
|
|
use Symfony\Component\DependencyInjection\Argument\LazyClosure; |
|
20
|
|
|
use Symfony\Component\DependencyInjection\Argument\ServiceClosureArgument; |
|
21
|
|
|
use Symfony\Component\DependencyInjection\Argument\ServiceLocator; |
|
22
|
|
|
use Symfony\Component\DependencyInjection\Argument\ServiceLocatorArgument; |
|
23
|
|
|
use Symfony\Component\DependencyInjection\Compiler\AnalyzeServiceReferencesPass; |
|
24
|
|
|
use Symfony\Component\DependencyInjection\Compiler\CheckCircularReferencesPass; |
|
25
|
|
|
use Symfony\Component\DependencyInjection\Compiler\ServiceReferenceGraphNode; |
|
26
|
|
|
use Symfony\Component\DependencyInjection\Container; |
|
27
|
|
|
use Symfony\Component\DependencyInjection\ContainerBuilder; |
|
28
|
|
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
|
29
|
|
|
use Symfony\Component\DependencyInjection\Definition; |
|
30
|
|
|
use Symfony\Component\DependencyInjection\Exception\EnvParameterException; |
|
31
|
|
|
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; |
|
32
|
|
|
use Symfony\Component\DependencyInjection\Exception\LogicException; |
|
33
|
|
|
use Symfony\Component\DependencyInjection\Exception\RuntimeException; |
|
34
|
|
|
use Symfony\Component\DependencyInjection\ExpressionLanguage; |
|
35
|
|
|
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\DumperInterface; |
|
|
|
|
|
|
36
|
|
|
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\LazyServiceDumper; |
|
37
|
|
|
use Symfony\Component\DependencyInjection\LazyProxy\PhpDumper\NullDumper; |
|
38
|
|
|
use Symfony\Component\DependencyInjection\Loader\FileLoader; |
|
39
|
|
|
use Symfony\Component\DependencyInjection\Parameter; |
|
40
|
|
|
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; |
|
41
|
|
|
use Symfony\Component\DependencyInjection\Reference; |
|
42
|
|
|
use Symfony\Component\DependencyInjection\ServiceLocator as BaseServiceLocator; |
|
43
|
|
|
use Symfony\Component\DependencyInjection\TypedReference; |
|
44
|
|
|
use Symfony\Component\DependencyInjection\Variable; |
|
45
|
|
|
use Symfony\Component\ErrorHandler\DebugClassLoader; |
|
46
|
|
|
use Symfony\Component\ExpressionLanguage\Expression; |
|
47
|
|
|
|
|
48
|
|
|
/** |
|
49
|
|
|
* PhpDumper dumps a service container as a PHP class. |
|
50
|
|
|
* |
|
51
|
|
|
* @author Fabien Potencier <[email protected]> |
|
52
|
|
|
* @author Johannes M. Schmitt <[email protected]> |
|
53
|
|
|
*/ |
|
54
|
|
|
class PhpDumper extends Dumper |
|
55
|
|
|
{ |
|
56
|
|
|
/** |
|
57
|
|
|
* Characters that might appear in the generated variable name as first character. |
|
58
|
|
|
*/ |
|
59
|
|
|
public const FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz'; |
|
60
|
|
|
|
|
61
|
|
|
/** |
|
62
|
|
|
* Characters that might appear in the generated variable name as any but the first character. |
|
63
|
|
|
*/ |
|
64
|
|
|
public const NON_FIRST_CHARS = 'abcdefghijklmnopqrstuvwxyz0123456789_'; |
|
65
|
|
|
|
|
66
|
|
|
/** @var \SplObjectStorage<Definition, Variable>|null */ |
|
67
|
|
|
private ?\SplObjectStorage $definitionVariables = null; |
|
68
|
|
|
private ?array $referenceVariables = null; |
|
69
|
|
|
private int $variableCount; |
|
70
|
|
|
private ?\SplObjectStorage $inlinedDefinitions = null; |
|
71
|
|
|
private ?array $serviceCalls = null; |
|
72
|
|
|
private array $reservedVariables = ['instance', 'class', 'this', 'container']; |
|
73
|
|
|
private ExpressionLanguage $expressionLanguage; |
|
74
|
|
|
private ?string $targetDirRegex = null; |
|
75
|
|
|
private int $targetDirMaxMatches; |
|
76
|
|
|
private string $docStar; |
|
77
|
|
|
private array $serviceIdToMethodNameMap; |
|
78
|
|
|
private array $usedMethodNames; |
|
79
|
|
|
private string $namespace; |
|
80
|
|
|
private bool $asFiles; |
|
81
|
|
|
private string $hotPathTag; |
|
82
|
|
|
private array $preloadTags; |
|
83
|
|
|
private bool $inlineFactories; |
|
84
|
|
|
private bool $inlineRequires; |
|
85
|
|
|
private array $inlinedRequires = []; |
|
86
|
|
|
private array $circularReferences = []; |
|
87
|
|
|
private array $singleUsePrivateIds = []; |
|
88
|
|
|
private array $preload = []; |
|
89
|
|
|
private bool $addGetService = false; |
|
90
|
|
|
private array $locatedIds = []; |
|
91
|
|
|
private string $serviceLocatorTag; |
|
92
|
|
|
private array $exportedVariables = []; |
|
93
|
|
|
private array $dynamicParameters = []; |
|
94
|
|
|
private string $baseClass; |
|
95
|
|
|
private string $class; |
|
96
|
|
|
private DumperInterface $proxyDumper; |
|
97
|
|
|
private bool $hasProxyDumper = true; |
|
98
|
|
|
|
|
99
|
|
|
public function __construct(ContainerBuilder $container) |
|
100
|
|
|
{ |
|
101
|
|
|
if (!$container->isCompiled()) { |
|
102
|
|
|
throw new LogicException('Cannot dump an uncompiled container.'); |
|
103
|
|
|
} |
|
104
|
|
|
|
|
105
|
|
|
parent::__construct($container); |
|
106
|
|
|
} |
|
107
|
|
|
|
|
108
|
|
|
/** |
|
109
|
|
|
* Sets the dumper to be used when dumping proxies in the generated container. |
|
110
|
|
|
*/ |
|
111
|
|
|
public function setProxyDumper(DumperInterface $proxyDumper): void |
|
112
|
|
|
{ |
|
113
|
|
|
$this->proxyDumper = $proxyDumper; |
|
114
|
|
|
$this->hasProxyDumper = !$proxyDumper instanceof NullDumper; |
|
115
|
|
|
} |
|
116
|
|
|
|
|
117
|
|
|
/** |
|
118
|
|
|
* Dumps the service container as a PHP class. |
|
119
|
|
|
* |
|
120
|
|
|
* Available options: |
|
121
|
|
|
* |
|
122
|
|
|
* * class: The class name |
|
123
|
|
|
* * base_class: The base class name |
|
124
|
|
|
* * namespace: The class namespace |
|
125
|
|
|
* * as_files: To split the container in several files |
|
126
|
|
|
* |
|
127
|
|
|
* @return string|array A PHP class representing the service container or an array of PHP files if the "as_files" option is set |
|
128
|
|
|
* |
|
129
|
|
|
* @throws EnvParameterException When an env var exists but has not been dumped |
|
130
|
|
|
*/ |
|
131
|
|
|
public function dump(array $options = []): string|array |
|
132
|
|
|
{ |
|
133
|
|
|
$this->locatedIds = []; |
|
134
|
|
|
$this->targetDirRegex = null; |
|
135
|
|
|
$this->inlinedRequires = []; |
|
136
|
|
|
$this->exportedVariables = []; |
|
137
|
|
|
$this->dynamicParameters = []; |
|
138
|
|
|
$options = array_merge([ |
|
139
|
|
|
'class' => 'ProjectServiceContainer', |
|
140
|
|
|
'base_class' => 'Container', |
|
141
|
|
|
'namespace' => '', |
|
142
|
|
|
'as_files' => false, |
|
143
|
|
|
'debug' => true, |
|
144
|
|
|
'hot_path_tag' => 'container.hot_path', |
|
145
|
|
|
'preload_tags' => ['container.preload', 'container.no_preload'], |
|
146
|
|
|
'inline_factories' => null, |
|
147
|
|
|
'inline_class_loader' => null, |
|
148
|
|
|
'preload_classes' => [], |
|
149
|
|
|
'service_locator_tag' => 'container.service_locator', |
|
150
|
|
|
'build_time' => time(), |
|
151
|
|
|
], $options); |
|
152
|
|
|
|
|
153
|
|
|
$this->addGetService = false; |
|
154
|
|
|
$this->namespace = $options['namespace']; |
|
155
|
|
|
$this->asFiles = $options['as_files']; |
|
156
|
|
|
$this->hotPathTag = $options['hot_path_tag']; |
|
157
|
|
|
$this->preloadTags = $options['preload_tags']; |
|
158
|
|
|
|
|
159
|
|
|
$this->inlineFactories = false; |
|
160
|
|
|
if (isset($options['inline_factories'])) { |
|
161
|
|
|
$this->inlineFactories = $this->asFiles && $options['inline_factories']; |
|
162
|
|
|
} |
|
163
|
|
|
|
|
164
|
|
|
$this->inlineRequires = $options['debug']; |
|
165
|
|
|
if (isset($options['inline_class_loader'])) { |
|
166
|
|
|
$this->inlineRequires = $options['inline_class_loader']; |
|
167
|
|
|
} |
|
168
|
|
|
|
|
169
|
|
|
$this->serviceLocatorTag = $options['service_locator_tag']; |
|
170
|
|
|
$this->class = $options['class']; |
|
171
|
|
|
|
|
172
|
|
|
if (!str_starts_with($baseClass = $options['base_class'], '\\') && 'Container' !== $baseClass) { |
|
173
|
|
|
$baseClass = \sprintf('%s\%s', $options['namespace'] ? '\\'.$options['namespace'] : '', $baseClass); |
|
174
|
|
|
$this->baseClass = $baseClass; |
|
175
|
|
|
} elseif ('Container' === $baseClass) { |
|
176
|
|
|
$this->baseClass = Container::class; |
|
177
|
|
|
} else { |
|
178
|
|
|
$this->baseClass = $baseClass; |
|
179
|
|
|
} |
|
180
|
|
|
|
|
181
|
|
|
$this->initializeMethodNamesMap('Container' === $baseClass ? Container::class : $baseClass); |
|
182
|
|
|
|
|
183
|
|
|
if (!$this->hasProxyDumper) { |
|
184
|
|
|
(new AnalyzeServiceReferencesPass(true, false))->process($this->container); |
|
185
|
|
|
(new CheckCircularReferencesPass())->process($this->container); |
|
186
|
|
|
} |
|
187
|
|
|
|
|
188
|
|
|
$this->analyzeReferences(); |
|
189
|
|
|
$this->docStar = $options['debug'] ? '*' : ''; |
|
190
|
|
|
|
|
191
|
|
|
if (!empty($options['file']) && is_dir($dir = \dirname($options['file']))) { |
|
192
|
|
|
// Build a regexp where the first root dirs are mandatory, |
|
193
|
|
|
// but every other sub-dir is optional up to the full path in $dir |
|
194
|
|
|
// Mandate at least 1 root dir and not more than 5 optional dirs. |
|
195
|
|
|
|
|
196
|
|
|
$dir = explode(\DIRECTORY_SEPARATOR, realpath($dir)); |
|
197
|
|
|
$i = \count($dir); |
|
198
|
|
|
|
|
199
|
|
|
if (2 + (int) ('\\' === \DIRECTORY_SEPARATOR) <= $i) { |
|
200
|
|
|
$regex = ''; |
|
201
|
|
|
$lastOptionalDir = $i > 8 ? $i - 5 : (2 + (int) ('\\' === \DIRECTORY_SEPARATOR)); |
|
202
|
|
|
$this->targetDirMaxMatches = $i - $lastOptionalDir; |
|
203
|
|
|
|
|
204
|
|
|
while (--$i >= $lastOptionalDir) { |
|
205
|
|
|
$regex = \sprintf('(%s%s)?', preg_quote(\DIRECTORY_SEPARATOR.$dir[$i], '#'), $regex); |
|
206
|
|
|
} |
|
207
|
|
|
|
|
208
|
|
|
do { |
|
209
|
|
|
$regex = preg_quote(\DIRECTORY_SEPARATOR.$dir[$i], '#').$regex; |
|
210
|
|
|
} while (0 < --$i); |
|
211
|
|
|
|
|
212
|
|
|
$this->targetDirRegex = '#(^|file://|[:;, \|\r\n])'.preg_quote($dir[0], '#').$regex.'#'; |
|
213
|
|
|
} |
|
214
|
|
|
} |
|
215
|
|
|
|
|
216
|
|
|
$proxyClasses = $this->inlineFactories ? $this->generateProxyClasses() : null; |
|
217
|
|
|
|
|
218
|
|
|
if ($options['preload_classes']) { |
|
219
|
|
|
$this->preload = array_combine($options['preload_classes'], $options['preload_classes']); |
|
220
|
|
|
} |
|
221
|
|
|
|
|
222
|
|
|
$code = $this->addDefaultParametersMethod(); |
|
223
|
|
|
$code = |
|
224
|
|
|
$this->startClass($options['class'], $baseClass, $this->inlineFactories && $proxyClasses). |
|
225
|
|
|
$this->addServices($services). |
|
226
|
|
|
$this->addDeprecatedAliases(). |
|
227
|
|
|
$code |
|
228
|
|
|
; |
|
229
|
|
|
|
|
230
|
|
|
$proxyClasses ??= $this->generateProxyClasses(); |
|
231
|
|
|
|
|
232
|
|
|
if ($this->addGetService) { |
|
233
|
|
|
$code = preg_replace( |
|
234
|
|
|
"/\r?\n\r?\n public function __construct.+?\\{\r?\n/s", |
|
235
|
|
|
"\n protected \Closure \$getService;$0", |
|
236
|
|
|
$code, |
|
237
|
|
|
1 |
|
238
|
|
|
); |
|
239
|
|
|
} |
|
240
|
|
|
|
|
241
|
|
|
if ($this->asFiles) { |
|
242
|
|
|
$fileTemplate = <<<EOF |
|
243
|
|
|
<?php |
|
244
|
|
|
|
|
245
|
|
|
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; |
|
246
|
|
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
|
247
|
|
|
use Symfony\Component\DependencyInjection\Exception\RuntimeException; |
|
248
|
|
|
|
|
249
|
|
|
/*{$this->docStar} |
|
250
|
|
|
* @internal This class has been auto-generated by the Symfony Dependency Injection Component. |
|
251
|
|
|
*/ |
|
252
|
|
|
class %s extends {$options['class']} |
|
253
|
|
|
{%s} |
|
254
|
|
|
|
|
255
|
|
|
EOF; |
|
256
|
|
|
$files = []; |
|
257
|
|
|
$preloadedFiles = []; |
|
258
|
|
|
$ids = $this->container->getRemovedIds(); |
|
259
|
|
|
foreach ($this->container->getDefinitions() as $id => $definition) { |
|
260
|
|
|
if (!$definition->isPublic() && '.' !== ($id[0] ?? '-')) { |
|
261
|
|
|
$ids[$id] = true; |
|
262
|
|
|
} |
|
263
|
|
|
} |
|
264
|
|
|
if ($ids = array_keys($ids)) { |
|
265
|
|
|
sort($ids); |
|
266
|
|
|
$c = "<?php\n\nreturn [\n"; |
|
267
|
|
|
foreach ($ids as $id) { |
|
268
|
|
|
$c .= ' '.$this->doExport($id)." => true,\n"; |
|
269
|
|
|
} |
|
270
|
|
|
$files['removed-ids.php'] = $c."];\n"; |
|
271
|
|
|
} |
|
272
|
|
|
|
|
273
|
|
|
if (!$this->inlineFactories) { |
|
274
|
|
|
foreach ($this->generateServiceFiles($services) as $file => [$c, $preload]) { |
|
275
|
|
|
$files[$file] = \sprintf($fileTemplate, substr($file, 0, -4), $c); |
|
276
|
|
|
|
|
277
|
|
|
if ($preload) { |
|
278
|
|
|
$preloadedFiles[$file] = $file; |
|
279
|
|
|
} |
|
280
|
|
|
} |
|
281
|
|
|
foreach ($proxyClasses as $file => $c) { |
|
282
|
|
|
$files[$file] = "<?php\n".$c; |
|
283
|
|
|
$preloadedFiles[$file] = $file; |
|
284
|
|
|
} |
|
285
|
|
|
} |
|
286
|
|
|
|
|
287
|
|
|
$code .= $this->endClass(); |
|
288
|
|
|
|
|
289
|
|
|
if ($this->inlineFactories && $proxyClasses) { |
|
290
|
|
|
$files['proxy-classes.php'] = "<?php\n\n"; |
|
291
|
|
|
|
|
292
|
|
|
foreach ($proxyClasses as $c) { |
|
293
|
|
|
$files['proxy-classes.php'] .= $c; |
|
294
|
|
|
} |
|
295
|
|
|
} |
|
296
|
|
|
|
|
297
|
|
|
$files[$options['class'].'.php'] = $code; |
|
298
|
|
|
$hash = ucfirst(strtr(ContainerBuilder::hash($files), '._', 'xx')); |
|
299
|
|
|
$code = []; |
|
300
|
|
|
|
|
301
|
|
|
foreach ($files as $file => $c) { |
|
302
|
|
|
$code["Container{$hash}/{$file}"] = substr_replace($c, "<?php\n\nnamespace Container{$hash};\n", 0, 6); |
|
303
|
|
|
|
|
304
|
|
|
if (isset($preloadedFiles[$file])) { |
|
305
|
|
|
$preloadedFiles[$file] = "Container{$hash}/{$file}"; |
|
306
|
|
|
} |
|
307
|
|
|
} |
|
308
|
|
|
$namespaceLine = $this->namespace ? "\nnamespace {$this->namespace};\n" : ''; |
|
309
|
|
|
$time = $options['build_time']; |
|
310
|
|
|
$id = hash('crc32', $hash.$time); |
|
311
|
|
|
$this->asFiles = false; |
|
312
|
|
|
|
|
313
|
|
|
if ($this->preload && null !== $autoloadFile = $this->getAutoloadFile()) { |
|
|
|
|
|
|
314
|
|
|
$autoloadFile = trim($this->export($autoloadFile), '()\\'); |
|
315
|
|
|
|
|
316
|
|
|
$preloadedFiles = array_reverse($preloadedFiles); |
|
317
|
|
|
if ('' !== $preloadedFiles = implode("';\nrequire __DIR__.'/", $preloadedFiles)) { |
|
318
|
|
|
$preloadedFiles = "require __DIR__.'/$preloadedFiles';\n"; |
|
319
|
|
|
} |
|
320
|
|
|
|
|
321
|
|
|
$code[$options['class'].'.preload.php'] = <<<EOF |
|
322
|
|
|
<?php |
|
323
|
|
|
|
|
324
|
|
|
// This file has been auto-generated by the Symfony Dependency Injection Component |
|
325
|
|
|
// You can reference it in the "opcache.preload" php.ini setting on PHP >= 7.4 when preloading is desired |
|
326
|
|
|
|
|
327
|
|
|
use Symfony\Component\DependencyInjection\Dumper\Preloader; |
|
328
|
|
|
|
|
329
|
|
|
if (in_array(PHP_SAPI, ['cli', 'phpdbg', 'embed'], true)) { |
|
330
|
|
|
return; |
|
331
|
|
|
} |
|
332
|
|
|
|
|
333
|
|
|
require $autoloadFile; |
|
334
|
|
|
(require __DIR__.'/{$options['class']}.php')->set(\\Container{$hash}\\{$options['class']}::class, null); |
|
335
|
|
|
$preloadedFiles |
|
336
|
|
|
\$classes = []; |
|
337
|
|
|
|
|
338
|
|
|
EOF; |
|
339
|
|
|
|
|
340
|
|
|
foreach ($this->preload as $class) { |
|
341
|
|
|
if (!$class || str_contains($class, '$') || \in_array($class, ['int', 'float', 'string', 'bool', 'resource', 'object', 'array', 'null', 'callable', 'iterable', 'mixed', 'void', 'never'], true)) { |
|
342
|
|
|
continue; |
|
343
|
|
|
} |
|
344
|
|
|
if (!(class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) || (new \ReflectionClass($class))->isUserDefined()) { |
|
345
|
|
|
$code[$options['class'].'.preload.php'] .= \sprintf("\$classes[] = '%s';\n", $class); |
|
346
|
|
|
} |
|
347
|
|
|
} |
|
348
|
|
|
|
|
349
|
|
|
$code[$options['class'].'.preload.php'] .= <<<'EOF' |
|
350
|
|
|
|
|
351
|
|
|
$preloaded = Preloader::preload($classes); |
|
352
|
|
|
|
|
353
|
|
|
EOF; |
|
354
|
|
|
} |
|
355
|
|
|
|
|
356
|
|
|
$code[$options['class'].'.php'] = <<<EOF |
|
357
|
|
|
<?php |
|
358
|
|
|
{$namespaceLine} |
|
359
|
|
|
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use. |
|
360
|
|
|
|
|
361
|
|
|
if (\\class_exists(\\Container{$hash}\\{$options['class']}::class, false)) { |
|
362
|
|
|
// no-op |
|
363
|
|
|
} elseif (!include __DIR__.'/Container{$hash}/{$options['class']}.php') { |
|
364
|
|
|
touch(__DIR__.'/Container{$hash}.legacy'); |
|
365
|
|
|
|
|
366
|
|
|
return; |
|
367
|
|
|
} |
|
368
|
|
|
|
|
369
|
|
|
if (!\\class_exists({$options['class']}::class, false)) { |
|
370
|
|
|
\\class_alias(\\Container{$hash}\\{$options['class']}::class, {$options['class']}::class, false); |
|
371
|
|
|
} |
|
372
|
|
|
|
|
373
|
|
|
return new \\Container{$hash}\\{$options['class']}([ |
|
374
|
|
|
'container.build_hash' => '$hash', |
|
375
|
|
|
'container.build_id' => '$id', |
|
376
|
|
|
'container.build_time' => $time, |
|
377
|
|
|
'container.runtime_mode' => \\in_array(\\PHP_SAPI, ['cli', 'phpdbg', 'embed'], true) ? 'web=0' : 'web=1', |
|
378
|
|
|
], __DIR__.\\DIRECTORY_SEPARATOR.'Container{$hash}'); |
|
379
|
|
|
|
|
380
|
|
|
EOF; |
|
381
|
|
|
} else { |
|
382
|
|
|
$code .= $this->endClass(); |
|
383
|
|
|
foreach ($proxyClasses as $c) { |
|
384
|
|
|
$code .= $c; |
|
385
|
|
|
} |
|
386
|
|
|
} |
|
387
|
|
|
|
|
388
|
|
|
$this->targetDirRegex = null; |
|
389
|
|
|
$this->inlinedRequires = []; |
|
390
|
|
|
$this->circularReferences = []; |
|
391
|
|
|
$this->locatedIds = []; |
|
392
|
|
|
$this->exportedVariables = []; |
|
393
|
|
|
$this->dynamicParameters = []; |
|
394
|
|
|
$this->preload = []; |
|
395
|
|
|
|
|
396
|
|
|
$unusedEnvs = []; |
|
397
|
|
|
foreach ($this->container->getEnvCounters() as $env => $use) { |
|
398
|
|
|
if (!$use) { |
|
399
|
|
|
$unusedEnvs[] = $env; |
|
400
|
|
|
} |
|
401
|
|
|
} |
|
402
|
|
|
if ($unusedEnvs) { |
|
403
|
|
|
throw new EnvParameterException($unusedEnvs, null, 'Environment variables "%s" are never used. Please, check your container\'s configuration.'); |
|
404
|
|
|
} |
|
405
|
|
|
|
|
406
|
|
|
return $code; |
|
407
|
|
|
} |
|
408
|
|
|
|
|
409
|
|
|
/** |
|
410
|
|
|
* Retrieves the currently set proxy dumper or instantiates one. |
|
411
|
|
|
*/ |
|
412
|
|
|
private function getProxyDumper(): DumperInterface |
|
413
|
|
|
{ |
|
414
|
|
|
return $this->proxyDumper ??= new LazyServiceDumper($this->class); |
|
415
|
|
|
} |
|
416
|
|
|
|
|
417
|
|
|
private function analyzeReferences(): void |
|
418
|
|
|
{ |
|
419
|
|
|
(new AnalyzeServiceReferencesPass(false, $this->hasProxyDumper))->process($this->container); |
|
420
|
|
|
$checkedNodes = []; |
|
421
|
|
|
$this->circularReferences = []; |
|
422
|
|
|
$this->singleUsePrivateIds = []; |
|
423
|
|
|
foreach ($this->container->getCompiler()->getServiceReferenceGraph()->getNodes() as $id => $node) { |
|
424
|
|
|
if (!$node->getValue() instanceof Definition) { |
|
425
|
|
|
continue; |
|
426
|
|
|
} |
|
427
|
|
|
|
|
428
|
|
|
if ($this->isSingleUsePrivateNode($node)) { |
|
429
|
|
|
$this->singleUsePrivateIds[$id] = $id; |
|
430
|
|
|
} |
|
431
|
|
|
|
|
432
|
|
|
$this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes); |
|
433
|
|
|
} |
|
434
|
|
|
|
|
435
|
|
|
$this->container->getCompiler()->getServiceReferenceGraph()->clear(); |
|
436
|
|
|
$this->singleUsePrivateIds = array_diff_key($this->singleUsePrivateIds, $this->circularReferences); |
|
437
|
|
|
} |
|
438
|
|
|
|
|
439
|
|
|
private function collectCircularReferences(string $sourceId, array $edges, array &$checkedNodes, array &$loops = [], array $path = [], bool $byConstructor = true): void |
|
440
|
|
|
{ |
|
441
|
|
|
$path[$sourceId] = $byConstructor; |
|
442
|
|
|
$checkedNodes[$sourceId] = true; |
|
443
|
|
|
foreach ($edges as $edge) { |
|
444
|
|
|
$node = $edge->getDestNode(); |
|
445
|
|
|
$id = $node->getId(); |
|
446
|
|
|
if ($sourceId === $id || !$node->getValue() instanceof Definition || $edge->isWeak()) { |
|
447
|
|
|
continue; |
|
448
|
|
|
} |
|
449
|
|
|
|
|
450
|
|
|
if (isset($path[$id])) { |
|
451
|
|
|
$loop = null; |
|
452
|
|
|
$loopByConstructor = $edge->isReferencedByConstructor() && !$edge->isLazy(); |
|
453
|
|
|
$pathInLoop = [$id, []]; |
|
454
|
|
|
foreach ($path as $k => $pathByConstructor) { |
|
455
|
|
|
if (null !== $loop) { |
|
456
|
|
|
$loop[] = $k; |
|
457
|
|
|
$pathInLoop[1][$k] = $pathByConstructor; |
|
458
|
|
|
$loops[$k][] = &$pathInLoop; |
|
459
|
|
|
$loopByConstructor = $loopByConstructor && $pathByConstructor; |
|
460
|
|
|
} elseif ($k === $id) { |
|
461
|
|
|
$loop = []; |
|
462
|
|
|
} |
|
463
|
|
|
} |
|
464
|
|
|
$this->addCircularReferences($id, $loop, $loopByConstructor); |
|
|
|
|
|
|
465
|
|
|
} elseif (!isset($checkedNodes[$id])) { |
|
466
|
|
|
$this->collectCircularReferences($id, $node->getOutEdges(), $checkedNodes, $loops, $path, $edge->isReferencedByConstructor() && !$edge->isLazy()); |
|
467
|
|
|
} elseif (isset($loops[$id])) { |
|
468
|
|
|
// we already had detected loops for this edge |
|
469
|
|
|
// let's check if we have a common ancestor in one of the detected loops |
|
470
|
|
|
foreach ($loops[$id] as [$first, $loopPath]) { |
|
471
|
|
|
if (!isset($path[$first])) { |
|
472
|
|
|
continue; |
|
473
|
|
|
} |
|
474
|
|
|
// We have a common ancestor, let's fill the current path |
|
475
|
|
|
$fillPath = null; |
|
476
|
|
|
foreach ($loopPath as $k => $pathByConstructor) { |
|
477
|
|
|
if (null !== $fillPath) { |
|
478
|
|
|
$fillPath[$k] = $pathByConstructor; |
|
479
|
|
|
} elseif ($k === $id) { |
|
480
|
|
|
$fillPath = $path; |
|
481
|
|
|
$fillPath[$k] = $pathByConstructor; |
|
482
|
|
|
} |
|
483
|
|
|
} |
|
484
|
|
|
|
|
485
|
|
|
// we can now build the loop |
|
486
|
|
|
$loop = null; |
|
487
|
|
|
$loopByConstructor = $edge->isReferencedByConstructor() && !$edge->isLazy(); |
|
488
|
|
|
foreach ($fillPath as $k => $pathByConstructor) { |
|
489
|
|
|
if (null !== $loop) { |
|
490
|
|
|
$loop[] = $k; |
|
491
|
|
|
$loopByConstructor = $loopByConstructor && $pathByConstructor; |
|
492
|
|
|
} elseif ($k === $first) { |
|
493
|
|
|
$loop = []; |
|
494
|
|
|
} |
|
495
|
|
|
} |
|
496
|
|
|
$this->addCircularReferences($first, $loop, $loopByConstructor); |
|
497
|
|
|
break; |
|
498
|
|
|
} |
|
499
|
|
|
} |
|
500
|
|
|
} |
|
501
|
|
|
unset($path[$sourceId]); |
|
502
|
|
|
} |
|
503
|
|
|
|
|
504
|
|
|
private function addCircularReferences(string $sourceId, array $currentPath, bool $byConstructor): void |
|
505
|
|
|
{ |
|
506
|
|
|
$currentId = $sourceId; |
|
507
|
|
|
$currentPath = array_reverse($currentPath); |
|
508
|
|
|
$currentPath[] = $currentId; |
|
509
|
|
|
foreach ($currentPath as $parentId) { |
|
510
|
|
|
if (empty($this->circularReferences[$parentId][$currentId])) { |
|
511
|
|
|
$this->circularReferences[$parentId][$currentId] = $byConstructor; |
|
512
|
|
|
} |
|
513
|
|
|
|
|
514
|
|
|
$currentId = $parentId; |
|
515
|
|
|
} |
|
516
|
|
|
} |
|
517
|
|
|
|
|
518
|
|
|
private function collectLineage(string $class, array &$lineage): void |
|
519
|
|
|
{ |
|
520
|
|
|
if (isset($lineage[$class])) { |
|
521
|
|
|
return; |
|
522
|
|
|
} |
|
523
|
|
|
if (!$r = $this->container->getReflectionClass($class, false)) { |
|
524
|
|
|
return; |
|
525
|
|
|
} |
|
526
|
|
|
if (is_a($class, $this->baseClass, true)) { |
|
527
|
|
|
return; |
|
528
|
|
|
} |
|
529
|
|
|
$file = $r->getFileName(); |
|
530
|
|
|
if (str_ends_with($file, ') : eval()\'d code')) { |
|
531
|
|
|
$file = substr($file, 0, strrpos($file, '(', -17)); |
|
532
|
|
|
} |
|
533
|
|
|
if (!$file || $this->doExport($file) === $exportedFile = $this->export($file)) { |
|
534
|
|
|
return; |
|
535
|
|
|
} |
|
536
|
|
|
|
|
537
|
|
|
$lineage[$class] = substr($exportedFile, 1, -1); |
|
538
|
|
|
|
|
539
|
|
|
if ($parent = $r->getParentClass()) { |
|
540
|
|
|
$this->collectLineage($parent->name, $lineage); |
|
541
|
|
|
} |
|
542
|
|
|
|
|
543
|
|
|
foreach ($r->getInterfaces() as $parent) { |
|
544
|
|
|
$this->collectLineage($parent->name, $lineage); |
|
545
|
|
|
} |
|
546
|
|
|
|
|
547
|
|
|
foreach ($r->getTraits() as $parent) { |
|
548
|
|
|
$this->collectLineage($parent->name, $lineage); |
|
549
|
|
|
} |
|
550
|
|
|
|
|
551
|
|
|
unset($lineage[$class]); |
|
552
|
|
|
$lineage[$class] = substr($exportedFile, 1, -1); |
|
553
|
|
|
} |
|
554
|
|
|
|
|
555
|
|
|
private function generateProxyClasses(): array |
|
556
|
|
|
{ |
|
557
|
|
|
$proxyClasses = []; |
|
558
|
|
|
$alreadyGenerated = []; |
|
559
|
|
|
$definitions = $this->container->getDefinitions(); |
|
560
|
|
|
$strip = '' === $this->docStar; |
|
561
|
|
|
$proxyDumper = $this->getProxyDumper(); |
|
562
|
|
|
ksort($definitions); |
|
563
|
|
|
foreach ($definitions as $id => $definition) { |
|
564
|
|
|
if (!$definition = $this->isProxyCandidate($definition, $asGhostObject, $id)) { |
|
565
|
|
|
continue; |
|
566
|
|
|
} |
|
567
|
|
|
if (isset($alreadyGenerated[$asGhostObject][$class = $definition->getClass()])) { |
|
568
|
|
|
continue; |
|
569
|
|
|
} |
|
570
|
|
|
$alreadyGenerated[$asGhostObject][$class] = true; |
|
571
|
|
|
|
|
572
|
|
|
foreach (array_column($definition->getTag('proxy'), 'interface') ?: [$class] as $r) { |
|
573
|
|
|
if (!$r = $this->container->getReflectionClass($r)) { |
|
574
|
|
|
continue; |
|
575
|
|
|
} |
|
576
|
|
|
do { |
|
577
|
|
|
$file = $r->getFileName(); |
|
578
|
|
|
if (str_ends_with($file, ') : eval()\'d code')) { |
|
579
|
|
|
$file = substr($file, 0, strrpos($file, '(', -17)); |
|
580
|
|
|
} |
|
581
|
|
|
if (is_file($file)) { |
|
582
|
|
|
$this->container->addResource(new FileResource($file)); |
|
583
|
|
|
} |
|
584
|
|
|
$r = $r->getParentClass() ?: null; |
|
585
|
|
|
} while ($r?->isUserDefined()); |
|
586
|
|
|
} |
|
587
|
|
|
|
|
588
|
|
|
if ("\n" === $proxyCode = "\n".$proxyDumper->getProxyCode($definition, $id)) { |
|
589
|
|
|
continue; |
|
590
|
|
|
} |
|
591
|
|
|
|
|
592
|
|
|
if ($this->inlineRequires) { |
|
593
|
|
|
$lineage = []; |
|
594
|
|
|
$this->collectLineage($class, $lineage); |
|
|
|
|
|
|
595
|
|
|
|
|
596
|
|
|
$code = ''; |
|
597
|
|
|
foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) { |
|
598
|
|
|
if ($this->inlineFactories) { |
|
599
|
|
|
$this->inlinedRequires[$file] = true; |
|
600
|
|
|
} |
|
601
|
|
|
$code .= \sprintf("include_once %s;\n", $file); |
|
602
|
|
|
} |
|
603
|
|
|
|
|
604
|
|
|
$proxyCode = $code.$proxyCode; |
|
605
|
|
|
} |
|
606
|
|
|
|
|
607
|
|
|
if ($strip) { |
|
608
|
|
|
$proxyCode = "<?php\n".$proxyCode; |
|
609
|
|
|
$proxyCode = substr(self::stripComments($proxyCode), 5); |
|
610
|
|
|
} |
|
611
|
|
|
|
|
612
|
|
|
$proxyClass = $this->inlineRequires ? substr($proxyCode, \strlen($code)) : $proxyCode; |
|
|
|
|
|
|
613
|
|
|
$i = strpos($proxyClass, 'class'); |
|
614
|
|
|
$proxyClass = substr($proxyClass, 6 + $i, strpos($proxyClass, ' ', 7 + $i) - $i - 6); |
|
615
|
|
|
|
|
616
|
|
|
if ($this->asFiles || $this->namespace) { |
|
617
|
|
|
$proxyCode .= "\nif (!\\class_exists('$proxyClass', false)) {\n \\class_alias(__NAMESPACE__.'\\\\$proxyClass', '$proxyClass', false);\n}\n"; |
|
618
|
|
|
} |
|
619
|
|
|
|
|
620
|
|
|
$proxyClasses[$proxyClass.'.php'] = $proxyCode; |
|
621
|
|
|
} |
|
622
|
|
|
|
|
623
|
|
|
return $proxyClasses; |
|
624
|
|
|
} |
|
625
|
|
|
|
|
626
|
|
|
private function addServiceInclude(string $cId, Definition $definition, bool $isProxyCandidate): string |
|
627
|
|
|
{ |
|
628
|
|
|
$code = ''; |
|
629
|
|
|
|
|
630
|
|
|
if ($this->inlineRequires && (!$this->isHotPath($definition) || $isProxyCandidate)) { |
|
631
|
|
|
$lineage = []; |
|
632
|
|
|
foreach ($this->inlinedDefinitions as $def) { |
|
633
|
|
|
if (!$def->isDeprecated()) { |
|
634
|
|
|
foreach ($this->getClasses($def, $cId) as $class) { |
|
635
|
|
|
$this->collectLineage($class, $lineage); |
|
636
|
|
|
} |
|
637
|
|
|
} |
|
638
|
|
|
} |
|
639
|
|
|
|
|
640
|
|
|
foreach ($this->serviceCalls as $id => [$callCount, $behavior]) { |
|
641
|
|
|
if ('service_container' !== $id && $id !== $cId |
|
642
|
|
|
&& ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $behavior |
|
643
|
|
|
&& $this->container->has($id) |
|
644
|
|
|
&& $this->isTrivialInstance($def = $this->container->findDefinition($id)) |
|
645
|
|
|
) { |
|
646
|
|
|
foreach ($this->getClasses($def, $cId) as $class) { |
|
647
|
|
|
$this->collectLineage($class, $lineage); |
|
648
|
|
|
} |
|
649
|
|
|
} |
|
650
|
|
|
} |
|
651
|
|
|
|
|
652
|
|
|
foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) { |
|
653
|
|
|
$code .= \sprintf(" include_once %s;\n", $file); |
|
654
|
|
|
} |
|
655
|
|
|
} |
|
656
|
|
|
|
|
657
|
|
|
foreach ($this->inlinedDefinitions as $def) { |
|
658
|
|
|
if ($file = $def->getFile()) { |
|
659
|
|
|
$file = $this->dumpValue($file); |
|
660
|
|
|
$file = '(' === $file[0] ? substr($file, 1, -1) : $file; |
|
661
|
|
|
$code .= \sprintf(" include_once %s;\n", $file); |
|
662
|
|
|
} |
|
663
|
|
|
} |
|
664
|
|
|
|
|
665
|
|
|
if ('' !== $code) { |
|
666
|
|
|
$code .= "\n"; |
|
667
|
|
|
} |
|
668
|
|
|
|
|
669
|
|
|
return $code; |
|
670
|
|
|
} |
|
671
|
|
|
|
|
672
|
|
|
/** |
|
673
|
|
|
* @throws InvalidArgumentException |
|
674
|
|
|
* @throws RuntimeException |
|
675
|
|
|
*/ |
|
676
|
|
|
private function addServiceInstance(string $id, Definition $definition, bool $isSimpleInstance): string |
|
677
|
|
|
{ |
|
678
|
|
|
$class = $this->dumpValue($definition->getClass()); |
|
679
|
|
|
|
|
680
|
|
|
if (str_starts_with($class, "'") && !str_contains($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)) { |
|
681
|
|
|
throw new InvalidArgumentException(\sprintf('"%s" is not a valid class name for the "%s" service.', $class, $id)); |
|
682
|
|
|
} |
|
683
|
|
|
|
|
684
|
|
|
$asGhostObject = false; |
|
685
|
|
|
$isProxyCandidate = $this->isProxyCandidate($definition, $asGhostObject, $id); |
|
686
|
|
|
$instantiation = ''; |
|
687
|
|
|
|
|
688
|
|
|
$lastWitherIndex = null; |
|
689
|
|
|
foreach ($definition->getMethodCalls() as $k => $call) { |
|
690
|
|
|
if ($call[2] ?? false) { |
|
691
|
|
|
$lastWitherIndex = $k; |
|
692
|
|
|
} |
|
693
|
|
|
} |
|
694
|
|
|
|
|
695
|
|
|
if (!$isProxyCandidate && $definition->isShared() && !isset($this->singleUsePrivateIds[$id]) && null === $lastWitherIndex) { |
|
696
|
|
|
$instantiation = \sprintf('$container->%s[%s] = %s', $this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', $this->doExport($id), $isSimpleInstance ? '' : '$instance'); |
|
697
|
|
|
} elseif (!$isSimpleInstance) { |
|
698
|
|
|
$instantiation = '$instance'; |
|
699
|
|
|
} |
|
700
|
|
|
|
|
701
|
|
|
$return = ''; |
|
702
|
|
|
if ($isSimpleInstance) { |
|
703
|
|
|
$return = 'return '; |
|
704
|
|
|
} else { |
|
705
|
|
|
$instantiation .= ' = '; |
|
706
|
|
|
} |
|
707
|
|
|
|
|
708
|
|
|
return $this->addNewInstance($definition, ' '.$return.$instantiation, $id, $asGhostObject); |
|
709
|
|
|
} |
|
710
|
|
|
|
|
711
|
|
|
private function isTrivialInstance(Definition $definition): bool |
|
712
|
|
|
{ |
|
713
|
|
|
if ($definition->hasErrors()) { |
|
714
|
|
|
return true; |
|
715
|
|
|
} |
|
716
|
|
|
if ($definition->isSynthetic() || $definition->getFile() || $definition->getMethodCalls() || $definition->getProperties() || $definition->getConfigurator()) { |
|
717
|
|
|
return false; |
|
718
|
|
|
} |
|
719
|
|
|
if ($definition->isDeprecated() || $definition->isLazy() || $definition->getFactory() || 3 < \count($definition->getArguments())) { |
|
720
|
|
|
return false; |
|
721
|
|
|
} |
|
722
|
|
|
|
|
723
|
|
|
foreach ($definition->getArguments() as $arg) { |
|
724
|
|
|
if (!$arg || $arg instanceof Parameter) { |
|
725
|
|
|
continue; |
|
726
|
|
|
} |
|
727
|
|
|
if (\is_array($arg) && 3 >= \count($arg)) { |
|
728
|
|
|
foreach ($arg as $k => $v) { |
|
729
|
|
|
if ($this->dumpValue($k) !== $this->dumpValue($k, false)) { |
|
730
|
|
|
return false; |
|
731
|
|
|
} |
|
732
|
|
|
if (!$v || $v instanceof Parameter) { |
|
733
|
|
|
continue; |
|
734
|
|
|
} |
|
735
|
|
|
if ($v instanceof Reference && $this->container->has($id = (string) $v) && $this->container->findDefinition($id)->isSynthetic()) { |
|
736
|
|
|
continue; |
|
737
|
|
|
} |
|
738
|
|
|
if (!\is_scalar($v) || $this->dumpValue($v) !== $this->dumpValue($v, false)) { |
|
739
|
|
|
return false; |
|
740
|
|
|
} |
|
741
|
|
|
} |
|
742
|
|
|
} elseif ($arg instanceof Reference && $this->container->has($id = (string) $arg) && $this->container->findDefinition($id)->isSynthetic()) { |
|
743
|
|
|
continue; |
|
744
|
|
|
} elseif (!\is_scalar($arg) || $this->dumpValue($arg) !== $this->dumpValue($arg, false)) { |
|
745
|
|
|
return false; |
|
746
|
|
|
} |
|
747
|
|
|
} |
|
748
|
|
|
|
|
749
|
|
|
return true; |
|
750
|
|
|
} |
|
751
|
|
|
|
|
752
|
|
|
private function addServiceMethodCalls(Definition $definition, string $variableName, ?string $sharedNonLazyId): string |
|
753
|
|
|
{ |
|
754
|
|
|
$lastWitherIndex = null; |
|
755
|
|
|
foreach ($definition->getMethodCalls() as $k => $call) { |
|
756
|
|
|
if ($call[2] ?? false) { |
|
757
|
|
|
$lastWitherIndex = $k; |
|
758
|
|
|
} |
|
759
|
|
|
} |
|
760
|
|
|
|
|
761
|
|
|
$calls = ''; |
|
762
|
|
|
foreach ($definition->getMethodCalls() as $k => $call) { |
|
763
|
|
|
$arguments = []; |
|
764
|
|
|
foreach ($call[1] as $i => $value) { |
|
765
|
|
|
$arguments[] = (\is_string($i) ? $i.': ' : '').$this->dumpValue($value); |
|
766
|
|
|
} |
|
767
|
|
|
|
|
768
|
|
|
$witherAssignation = ''; |
|
769
|
|
|
|
|
770
|
|
|
if ($call[2] ?? false) { |
|
771
|
|
|
if (null !== $sharedNonLazyId && $lastWitherIndex === $k && 'instance' === $variableName) { |
|
772
|
|
|
$witherAssignation = \sprintf('$container->%s[\'%s\'] = ', $definition->isPublic() ? 'services' : 'privates', $sharedNonLazyId); |
|
773
|
|
|
} |
|
774
|
|
|
$witherAssignation .= \sprintf('$%s = ', $variableName); |
|
775
|
|
|
} |
|
776
|
|
|
|
|
777
|
|
|
$calls .= $this->wrapServiceConditionals($call[1], \sprintf(" %s\$%s->%s(%s);\n", $witherAssignation, $variableName, $call[0], implode(', ', $arguments))); |
|
778
|
|
|
} |
|
779
|
|
|
|
|
780
|
|
|
return $calls; |
|
781
|
|
|
} |
|
782
|
|
|
|
|
783
|
|
|
private function addServiceProperties(Definition $definition, string $variableName = 'instance'): string |
|
784
|
|
|
{ |
|
785
|
|
|
$code = ''; |
|
786
|
|
|
foreach ($definition->getProperties() as $name => $value) { |
|
787
|
|
|
$code .= \sprintf(" \$%s->%s = %s;\n", $variableName, $name, $this->dumpValue($value)); |
|
788
|
|
|
} |
|
789
|
|
|
|
|
790
|
|
|
return $code; |
|
791
|
|
|
} |
|
792
|
|
|
|
|
793
|
|
|
private function addServiceConfigurator(Definition $definition, string $variableName = 'instance'): string |
|
794
|
|
|
{ |
|
795
|
|
|
if (!$callable = $definition->getConfigurator()) { |
|
796
|
|
|
return ''; |
|
797
|
|
|
} |
|
798
|
|
|
|
|
799
|
|
|
if (\is_array($callable)) { |
|
800
|
|
|
if ($callable[0] instanceof Reference |
|
801
|
|
|
|| ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0])) |
|
|
|
|
|
|
802
|
|
|
) { |
|
803
|
|
|
return \sprintf(" %s->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); |
|
804
|
|
|
} |
|
805
|
|
|
|
|
806
|
|
|
$class = $this->dumpValue($callable[0]); |
|
807
|
|
|
// If the class is a string we can optimize away |
|
808
|
|
|
if (str_starts_with($class, "'") && !str_contains($class, '$')) { |
|
809
|
|
|
return \sprintf(" %s::%s(\$%s);\n", $this->dumpLiteralClass($class), $callable[1], $variableName); |
|
810
|
|
|
} |
|
811
|
|
|
|
|
812
|
|
|
if (str_starts_with($class, 'new ')) { |
|
813
|
|
|
return \sprintf(" (%s)->%s(\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); |
|
814
|
|
|
} |
|
815
|
|
|
|
|
816
|
|
|
return \sprintf(" [%s, '%s'](\$%s);\n", $this->dumpValue($callable[0]), $callable[1], $variableName); |
|
817
|
|
|
} |
|
818
|
|
|
|
|
819
|
|
|
return \sprintf(" %s(\$%s);\n", $callable, $variableName); |
|
820
|
|
|
} |
|
821
|
|
|
|
|
822
|
|
|
private function addService(string $id, Definition $definition): array |
|
823
|
|
|
{ |
|
824
|
|
|
$this->definitionVariables = new \SplObjectStorage(); |
|
825
|
|
|
$this->referenceVariables = []; |
|
826
|
|
|
$this->variableCount = 0; |
|
827
|
|
|
$this->referenceVariables[$id] = new Variable('instance'); |
|
828
|
|
|
|
|
829
|
|
|
$return = []; |
|
830
|
|
|
|
|
831
|
|
|
if ($class = $definition->getClass()) { |
|
832
|
|
|
$class = $class instanceof Parameter ? '%'.$class.'%' : $this->container->resolveEnvPlaceholders($class); |
|
|
|
|
|
|
833
|
|
|
$return[] = \sprintf(str_starts_with($class, '%') ? '@return object A %1$s instance' : '@return \%s', ltrim($class, '\\')); |
|
834
|
|
|
} elseif ($factory = $definition->getFactory()) { |
|
835
|
|
|
if (\is_string($factory) && !str_starts_with($factory, '@=')) { |
|
836
|
|
|
$return[] = \sprintf('@return object An instance returned by %s()', $factory); |
|
837
|
|
|
} elseif (\is_array($factory) && (\is_string($factory[0]) || $factory[0] instanceof Definition || $factory[0] instanceof Reference)) { |
|
838
|
|
|
$class = $factory[0] instanceof Definition ? $factory[0]->getClass() : (string) $factory[0]; |
|
839
|
|
|
$class = $class instanceof Parameter ? '%'.$class.'%' : $this->container->resolveEnvPlaceholders($class); |
|
840
|
|
|
$return[] = \sprintf('@return object An instance returned by %s::%s()', $class, $factory[1]); |
|
841
|
|
|
} |
|
842
|
|
|
} |
|
843
|
|
|
|
|
844
|
|
|
if ($definition->isDeprecated()) { |
|
845
|
|
|
if ($return && str_starts_with($return[\count($return) - 1], '@return')) { |
|
846
|
|
|
$return[] = ''; |
|
847
|
|
|
} |
|
848
|
|
|
|
|
849
|
|
|
$deprecation = $definition->getDeprecation($id); |
|
850
|
|
|
$return[] = \sprintf('@deprecated %s', ($deprecation['package'] || $deprecation['version'] ? "Since {$deprecation['package']} {$deprecation['version']}: " : '').$deprecation['message']); |
|
851
|
|
|
} |
|
852
|
|
|
|
|
853
|
|
|
$return = str_replace("\n * \n", "\n *\n", implode("\n * ", $return)); |
|
854
|
|
|
$return = $this->container->resolveEnvPlaceholders($return); |
|
855
|
|
|
|
|
856
|
|
|
$shared = $definition->isShared() ? ' shared' : ''; |
|
857
|
|
|
$public = $definition->isPublic() ? 'public' : 'private'; |
|
858
|
|
|
$autowired = $definition->isAutowired() ? ' autowired' : ''; |
|
859
|
|
|
$asFile = $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition); |
|
860
|
|
|
$methodName = $this->generateMethodName($id); |
|
861
|
|
|
|
|
862
|
|
|
if ($asFile || $definition->isLazy()) { |
|
863
|
|
|
$lazyInitialization = ', $lazyLoad = true'; |
|
864
|
|
|
} else { |
|
865
|
|
|
$lazyInitialization = ''; |
|
866
|
|
|
} |
|
867
|
|
|
|
|
868
|
|
|
$code = <<<EOF |
|
869
|
|
|
|
|
870
|
|
|
/*{$this->docStar} |
|
871
|
|
|
* Gets the $public '$id'$shared$autowired service. |
|
872
|
|
|
* |
|
873
|
|
|
* $return |
|
874
|
|
|
EOF; |
|
875
|
|
|
$code = str_replace('*/', ' ', $code).<<<EOF |
|
876
|
|
|
|
|
877
|
|
|
*/ |
|
878
|
|
|
protected static function {$methodName}(\$container$lazyInitialization) |
|
879
|
|
|
{ |
|
880
|
|
|
|
|
881
|
|
|
EOF; |
|
882
|
|
|
|
|
883
|
|
|
if ($asFile) { |
|
884
|
|
|
$file = $methodName.'.php'; |
|
885
|
|
|
$code = str_replace("protected static function {$methodName}(", 'public static function do(', $code); |
|
886
|
|
|
} else { |
|
887
|
|
|
$file = null; |
|
888
|
|
|
} |
|
889
|
|
|
|
|
890
|
|
|
if ($definition->hasErrors() && $e = $definition->getErrors()) { |
|
891
|
|
|
$code .= \sprintf(" throw new RuntimeException(%s);\n", $this->export(reset($e))); |
|
892
|
|
|
} else { |
|
893
|
|
|
$this->serviceCalls = []; |
|
894
|
|
|
$this->inlinedDefinitions = $this->getDefinitionsFromArguments([$definition], null, $this->serviceCalls); |
|
|
|
|
|
|
895
|
|
|
|
|
896
|
|
|
if ($definition->isDeprecated()) { |
|
897
|
|
|
$deprecation = $definition->getDeprecation($id); |
|
898
|
|
|
$code .= \sprintf(" trigger_deprecation(%s, %s, %s);\n\n", $this->export($deprecation['package']), $this->export($deprecation['version']), $this->export($deprecation['message'])); |
|
899
|
|
|
} elseif ($definition->hasTag($this->hotPathTag) || !$definition->hasTag($this->preloadTags[1])) { |
|
900
|
|
|
foreach ($this->inlinedDefinitions as $def) { |
|
901
|
|
|
foreach ($this->getClasses($def, $id) as $class) { |
|
902
|
|
|
$this->preload[$class] = $class; |
|
903
|
|
|
} |
|
904
|
|
|
} |
|
905
|
|
|
} |
|
906
|
|
|
|
|
907
|
|
|
if (!$definition->isShared()) { |
|
908
|
|
|
$factory = \sprintf('$container->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id)); |
|
909
|
|
|
} |
|
910
|
|
|
|
|
911
|
|
|
$asGhostObject = false; |
|
912
|
|
|
if ($isProxyCandidate = $this->isProxyCandidate($definition, $asGhostObject, $id)) { |
|
913
|
|
|
$definition = $isProxyCandidate; |
|
914
|
|
|
|
|
915
|
|
|
if (!$definition->isShared()) { |
|
916
|
|
|
$code .= \sprintf(' %s ??= ', $factory); |
|
|
|
|
|
|
917
|
|
|
|
|
918
|
|
|
if ($definition->isPublic()) { |
|
919
|
|
|
$code .= \sprintf("fn () => self::%s(\$container);\n\n", $asFile ? 'do' : $methodName); |
|
920
|
|
|
} else { |
|
921
|
|
|
$code .= \sprintf("self::%s(...);\n\n", $asFile ? 'do' : $methodName); |
|
922
|
|
|
} |
|
923
|
|
|
} |
|
924
|
|
|
$lazyLoad = $asGhostObject ? '$proxy' : 'false'; |
|
925
|
|
|
|
|
926
|
|
|
$factoryCode = $asFile ? \sprintf('self::do($container, %s)', $lazyLoad) : \sprintf('self::%s($container, %s)', $methodName, $lazyLoad); |
|
927
|
|
|
$code .= $this->getProxyDumper()->getProxyFactoryCode($definition, $id, $factoryCode); |
|
928
|
|
|
} |
|
929
|
|
|
|
|
930
|
|
|
$c = $this->addServiceInclude($id, $definition, null !== $isProxyCandidate); |
|
931
|
|
|
|
|
932
|
|
|
if ('' !== $c && $isProxyCandidate && !$definition->isShared()) { |
|
933
|
|
|
$c = implode("\n", array_map(fn ($line) => $line ? ' '.$line : $line, explode("\n", $c))); |
|
934
|
|
|
$code .= " static \$include = true;\n\n"; |
|
935
|
|
|
$code .= " if (\$include) {\n"; |
|
936
|
|
|
$code .= $c; |
|
937
|
|
|
$code .= " \$include = false;\n"; |
|
938
|
|
|
$code .= " }\n\n"; |
|
939
|
|
|
} else { |
|
940
|
|
|
$code .= $c; |
|
941
|
|
|
} |
|
942
|
|
|
|
|
943
|
|
|
$c = $this->addInlineService($id, $definition); |
|
944
|
|
|
|
|
945
|
|
|
if (!$isProxyCandidate && !$definition->isShared()) { |
|
946
|
|
|
$c = implode("\n", array_map(fn ($line) => $line ? ' '.$line : $line, explode("\n", $c))); |
|
947
|
|
|
$lazyloadInitialization = $definition->isLazy() ? ', $lazyLoad = true' : ''; |
|
948
|
|
|
|
|
949
|
|
|
$c = \sprintf(" %s = function (\$container%s) {\n%s };\n\n return %1\$s(\$container);\n", $factory, $lazyloadInitialization, $c); |
|
950
|
|
|
} |
|
951
|
|
|
|
|
952
|
|
|
$code .= $c; |
|
953
|
|
|
} |
|
954
|
|
|
|
|
955
|
|
|
$code .= " }\n"; |
|
956
|
|
|
|
|
957
|
|
|
$this->definitionVariables = $this->inlinedDefinitions = null; |
|
958
|
|
|
$this->referenceVariables = $this->serviceCalls = null; |
|
959
|
|
|
|
|
960
|
|
|
return [$file, $code]; |
|
961
|
|
|
} |
|
962
|
|
|
|
|
963
|
|
|
private function addInlineVariables(string $id, Definition $definition, array $arguments, bool $forConstructor): string |
|
964
|
|
|
{ |
|
965
|
|
|
$code = ''; |
|
966
|
|
|
|
|
967
|
|
|
foreach ($arguments as $argument) { |
|
968
|
|
|
if (\is_array($argument)) { |
|
969
|
|
|
$code .= $this->addInlineVariables($id, $definition, $argument, $forConstructor); |
|
970
|
|
|
} elseif ($argument instanceof Reference) { |
|
971
|
|
|
$code .= $this->addInlineReference($id, $definition, $argument, $forConstructor); |
|
972
|
|
|
} elseif ($argument instanceof Definition) { |
|
973
|
|
|
$code .= $this->addInlineService($id, $definition, $argument, $forConstructor); |
|
974
|
|
|
} |
|
975
|
|
|
} |
|
976
|
|
|
|
|
977
|
|
|
return $code; |
|
978
|
|
|
} |
|
979
|
|
|
|
|
980
|
|
|
private function addInlineReference(string $id, Definition $definition, string $targetId, bool $forConstructor): string |
|
981
|
|
|
{ |
|
982
|
|
|
while ($this->container->hasAlias($targetId)) { |
|
983
|
|
|
$targetId = (string) $this->container->getAlias($targetId); |
|
984
|
|
|
} |
|
985
|
|
|
|
|
986
|
|
|
[$callCount, $behavior] = $this->serviceCalls[$targetId]; |
|
987
|
|
|
|
|
988
|
|
|
if ($id === $targetId) { |
|
989
|
|
|
return $this->addInlineService($id, $definition, $definition); |
|
990
|
|
|
} |
|
991
|
|
|
|
|
992
|
|
|
if ('service_container' === $targetId || isset($this->referenceVariables[$targetId])) { |
|
993
|
|
|
return ''; |
|
994
|
|
|
} |
|
995
|
|
|
|
|
996
|
|
|
if ($this->container->hasDefinition($targetId) && ($def = $this->container->getDefinition($targetId)) && !$def->isShared()) { |
|
997
|
|
|
return ''; |
|
998
|
|
|
} |
|
999
|
|
|
|
|
1000
|
|
|
$hasSelfRef = isset($this->circularReferences[$id][$targetId]) && !isset($this->definitionVariables[$definition]) && !($this->hasProxyDumper && $definition->isLazy()); |
|
1001
|
|
|
|
|
1002
|
|
|
if ($hasSelfRef && !$forConstructor && !$forConstructor = !$this->circularReferences[$id][$targetId]) { |
|
1003
|
|
|
$code = $this->addInlineService($id, $definition, $definition); |
|
1004
|
|
|
} else { |
|
1005
|
|
|
$code = ''; |
|
1006
|
|
|
} |
|
1007
|
|
|
|
|
1008
|
|
|
if (isset($this->referenceVariables[$targetId]) || (2 > $callCount && (!$hasSelfRef || !$forConstructor))) { |
|
1009
|
|
|
return $code; |
|
1010
|
|
|
} |
|
1011
|
|
|
|
|
1012
|
|
|
$name = $this->getNextVariableName(); |
|
1013
|
|
|
$this->referenceVariables[$targetId] = new Variable($name); |
|
1014
|
|
|
|
|
1015
|
|
|
$reference = ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $behavior ? new Reference($targetId, $behavior) : null; |
|
1016
|
|
|
$code .= \sprintf(" \$%s = %s;\n", $name, $this->getServiceCall($targetId, $reference)); |
|
1017
|
|
|
|
|
1018
|
|
|
if (!$hasSelfRef || !$forConstructor) { |
|
1019
|
|
|
return $code; |
|
1020
|
|
|
} |
|
1021
|
|
|
|
|
1022
|
|
|
return $code.\sprintf(<<<'EOTXT' |
|
1023
|
|
|
|
|
1024
|
|
|
if (isset($container->%s[%s])) { |
|
1025
|
|
|
return $container->%1$s[%2$s]; |
|
1026
|
|
|
} |
|
1027
|
|
|
|
|
1028
|
|
|
EOTXT |
|
1029
|
|
|
, |
|
1030
|
|
|
$this->container->getDefinition($id)->isPublic() ? 'services' : 'privates', |
|
1031
|
|
|
$this->doExport($id) |
|
1032
|
|
|
); |
|
1033
|
|
|
} |
|
1034
|
|
|
|
|
1035
|
|
|
private function addInlineService(string $id, Definition $definition, ?Definition $inlineDef = null, bool $forConstructor = true): string |
|
1036
|
|
|
{ |
|
1037
|
|
|
$code = ''; |
|
1038
|
|
|
|
|
1039
|
|
|
if ($isSimpleInstance = $isRootInstance = null === $inlineDef) { |
|
1040
|
|
|
foreach ($this->serviceCalls as $targetId => [$callCount, $behavior, $byConstructor]) { |
|
1041
|
|
|
if ($byConstructor && isset($this->circularReferences[$id][$targetId]) && !$this->circularReferences[$id][$targetId] && !($this->hasProxyDumper && $definition->isLazy())) { |
|
1042
|
|
|
$code .= $this->addInlineReference($id, $definition, $targetId, $forConstructor); |
|
1043
|
|
|
} |
|
1044
|
|
|
} |
|
1045
|
|
|
} |
|
1046
|
|
|
|
|
1047
|
|
|
if (isset($this->definitionVariables[$inlineDef ??= $definition])) { |
|
1048
|
|
|
return $code; |
|
1049
|
|
|
} |
|
1050
|
|
|
|
|
1051
|
|
|
$arguments = [$inlineDef->getArguments(), $inlineDef->getFactory()]; |
|
1052
|
|
|
|
|
1053
|
|
|
$code .= $this->addInlineVariables($id, $definition, $arguments, $forConstructor); |
|
1054
|
|
|
|
|
1055
|
|
|
if ($arguments = array_filter([$inlineDef->getProperties(), $inlineDef->getMethodCalls(), $inlineDef->getConfigurator()])) { |
|
1056
|
|
|
$isSimpleInstance = false; |
|
1057
|
|
|
} elseif ($definition !== $inlineDef && 2 > $this->inlinedDefinitions[$inlineDef]) { |
|
1058
|
|
|
return $code; |
|
1059
|
|
|
} |
|
1060
|
|
|
|
|
1061
|
|
|
$asGhostObject = false; |
|
1062
|
|
|
$isProxyCandidate = $this->isProxyCandidate($inlineDef, $asGhostObject, $id); |
|
1063
|
|
|
|
|
1064
|
|
|
if (isset($this->definitionVariables[$inlineDef])) { |
|
1065
|
|
|
$isSimpleInstance = false; |
|
1066
|
|
|
} else { |
|
1067
|
|
|
$name = $definition === $inlineDef ? 'instance' : $this->getNextVariableName(); |
|
1068
|
|
|
$this->definitionVariables[$inlineDef] = new Variable($name); |
|
1069
|
|
|
$code .= '' !== $code ? "\n" : ''; |
|
1070
|
|
|
|
|
1071
|
|
|
if ('instance' === $name) { |
|
1072
|
|
|
$code .= $this->addServiceInstance($id, $definition, $isSimpleInstance); |
|
1073
|
|
|
} else { |
|
1074
|
|
|
$code .= $this->addNewInstance($inlineDef, ' $'.$name.' = ', $id); |
|
1075
|
|
|
} |
|
1076
|
|
|
|
|
1077
|
|
|
if ('' !== $inline = $this->addInlineVariables($id, $definition, $arguments, false)) { |
|
1078
|
|
|
$code .= "\n".$inline."\n"; |
|
1079
|
|
|
} elseif ($arguments && 'instance' === $name) { |
|
|
|
|
|
|
1080
|
|
|
$code .= "\n"; |
|
1081
|
|
|
} |
|
1082
|
|
|
|
|
1083
|
|
|
$code .= $this->addServiceProperties($inlineDef, $name); |
|
1084
|
|
|
$code .= $this->addServiceMethodCalls($inlineDef, $name, !$isProxyCandidate && $inlineDef->isShared() && !isset($this->singleUsePrivateIds[$id]) ? $id : null); |
|
1085
|
|
|
$code .= $this->addServiceConfigurator($inlineDef, $name); |
|
1086
|
|
|
} |
|
1087
|
|
|
|
|
1088
|
|
|
if (!$isRootInstance || $isSimpleInstance) { |
|
1089
|
|
|
return $code; |
|
1090
|
|
|
} |
|
1091
|
|
|
|
|
1092
|
|
|
return $code."\n return \$instance;\n"; |
|
1093
|
|
|
} |
|
1094
|
|
|
|
|
1095
|
|
|
private function addServices(?array &$services = null): string |
|
1096
|
|
|
{ |
|
1097
|
|
|
$publicServices = $privateServices = ''; |
|
1098
|
|
|
$definitions = $this->container->getDefinitions(); |
|
1099
|
|
|
ksort($definitions); |
|
1100
|
|
|
foreach ($definitions as $id => $definition) { |
|
1101
|
|
|
if (!$definition->isSynthetic()) { |
|
1102
|
|
|
$services[$id] = $this->addService($id, $definition); |
|
1103
|
|
|
} elseif ($definition->hasTag($this->hotPathTag) || !$definition->hasTag($this->preloadTags[1])) { |
|
1104
|
|
|
$services[$id] = null; |
|
1105
|
|
|
|
|
1106
|
|
|
foreach ($this->getClasses($definition, $id) as $class) { |
|
1107
|
|
|
$this->preload[$class] = $class; |
|
1108
|
|
|
} |
|
1109
|
|
|
} |
|
1110
|
|
|
} |
|
1111
|
|
|
|
|
1112
|
|
|
foreach ($definitions as $id => $definition) { |
|
1113
|
|
|
if (!([$file, $code] = $services[$id]) || null !== $file) { |
|
1114
|
|
|
continue; |
|
1115
|
|
|
} |
|
1116
|
|
|
if ($definition->isPublic()) { |
|
1117
|
|
|
$publicServices .= $code; |
|
1118
|
|
|
} elseif (!$this->isTrivialInstance($definition) || isset($this->locatedIds[$id])) { |
|
1119
|
|
|
$privateServices .= $code; |
|
1120
|
|
|
} |
|
1121
|
|
|
} |
|
1122
|
|
|
|
|
1123
|
|
|
return $publicServices.$privateServices; |
|
1124
|
|
|
} |
|
1125
|
|
|
|
|
1126
|
|
|
private function generateServiceFiles(array $services): iterable |
|
1127
|
|
|
{ |
|
1128
|
|
|
$definitions = $this->container->getDefinitions(); |
|
1129
|
|
|
ksort($definitions); |
|
1130
|
|
|
foreach ($definitions as $id => $definition) { |
|
1131
|
|
|
if (([$file, $code] = $services[$id]) && null !== $file && ($definition->isPublic() || !$this->isTrivialInstance($definition) || isset($this->locatedIds[$id]))) { |
|
1132
|
|
|
yield $file => [$code, $definition->hasTag($this->hotPathTag) || !$definition->hasTag($this->preloadTags[1]) && !$definition->isDeprecated() && !$definition->hasErrors()]; |
|
1133
|
|
|
} |
|
1134
|
|
|
} |
|
1135
|
|
|
} |
|
1136
|
|
|
|
|
1137
|
|
|
private function addNewInstance(Definition $definition, string $return = '', ?string $id = null, bool $asGhostObject = false): string |
|
1138
|
|
|
{ |
|
1139
|
|
|
$tail = $return ? str_repeat(')', substr_count($return, '(') - substr_count($return, ')')).";\n" : ''; |
|
1140
|
|
|
|
|
1141
|
|
|
$arguments = []; |
|
1142
|
|
|
if (BaseServiceLocator::class === $definition->getClass() && $definition->hasTag($this->serviceLocatorTag)) { |
|
1143
|
|
|
foreach ($definition->getArgument(0) as $k => $argument) { |
|
1144
|
|
|
$arguments[$k] = $argument->getValues()[0]; |
|
1145
|
|
|
} |
|
1146
|
|
|
|
|
1147
|
|
|
return $return.$this->dumpValue(new ServiceLocatorArgument($arguments)).$tail; |
|
1148
|
|
|
} |
|
1149
|
|
|
|
|
1150
|
|
|
foreach ($definition->getArguments() as $i => $value) { |
|
1151
|
|
|
$arguments[] = (\is_string($i) ? $i.': ' : '').$this->dumpValue($value); |
|
1152
|
|
|
} |
|
1153
|
|
|
|
|
1154
|
|
|
if ($callable = $definition->getFactory()) { |
|
1155
|
|
|
if ('current' === $callable && [0] === array_keys($definition->getArguments()) && \is_array($value) && [0] === array_keys($value)) { |
|
|
|
|
|
|
1156
|
|
|
return $return.$this->dumpValue($value[0]).$tail; |
|
1157
|
|
|
} |
|
1158
|
|
|
|
|
1159
|
|
|
if (['Closure', 'fromCallable'] === $callable) { |
|
1160
|
|
|
$callable = $definition->getArgument(0); |
|
1161
|
|
|
if ($callable instanceof ServiceClosureArgument) { |
|
1162
|
|
|
return $return.$this->dumpValue($callable).$tail; |
|
1163
|
|
|
} |
|
1164
|
|
|
|
|
1165
|
|
|
$arguments = ['...']; |
|
1166
|
|
|
|
|
1167
|
|
|
if ($callable instanceof Reference || $callable instanceof Definition) { |
|
1168
|
|
|
$callable = [$callable, '__invoke']; |
|
1169
|
|
|
} |
|
1170
|
|
|
} |
|
1171
|
|
|
|
|
1172
|
|
|
if (\is_string($callable) && str_starts_with($callable, '@=')) { |
|
1173
|
|
|
return $return.\sprintf('(($args = %s) ? (%s) : null)', |
|
1174
|
|
|
$this->dumpValue(new ServiceLocatorArgument($definition->getArguments())), |
|
1175
|
|
|
$this->getExpressionLanguage()->compile(substr($callable, 2), ['container' => 'container', 'args' => 'args']) |
|
1176
|
|
|
).$tail; |
|
1177
|
|
|
} |
|
1178
|
|
|
|
|
1179
|
|
|
if (!\is_array($callable)) { |
|
1180
|
|
|
return $return.\sprintf('%s(%s)', $this->dumpLiteralClass($this->dumpValue($callable)), $arguments ? implode(', ', $arguments) : '').$tail; |
|
1181
|
|
|
} |
|
1182
|
|
|
|
|
1183
|
|
|
if (!preg_match('/^[a-zA-Z_\x7f-\xff][a-zA-Z0-9_\x7f-\xff]*$/', $callable[1])) { |
|
1184
|
|
|
throw new RuntimeException(\sprintf('Cannot dump definition because of invalid factory method (%s).', $callable[1] ?: 'n/a')); |
|
1185
|
|
|
} |
|
1186
|
|
|
|
|
1187
|
|
|
if (['...'] === $arguments && ($definition->isLazy() || 'Closure' !== ($definition->getClass() ?? 'Closure')) && ( |
|
1188
|
|
|
$callable[0] instanceof Reference |
|
1189
|
|
|
|| ($callable[0] instanceof Definition && !$this->definitionVariables->contains($callable[0])) |
|
1190
|
|
|
)) { |
|
1191
|
|
|
$initializer = 'fn () => '.$this->dumpValue($callable[0]); |
|
1192
|
|
|
|
|
1193
|
|
|
return $return.LazyClosure::getCode($initializer, $callable, $definition, $this->container, $id).$tail; |
|
1194
|
|
|
} |
|
1195
|
|
|
|
|
1196
|
|
|
if ($callable[0] instanceof Reference |
|
1197
|
|
|
|| ($callable[0] instanceof Definition && $this->definitionVariables->contains($callable[0])) |
|
1198
|
|
|
) { |
|
1199
|
|
|
return $return.\sprintf('%s->%s(%s)', $this->dumpValue($callable[0]), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; |
|
1200
|
|
|
} |
|
1201
|
|
|
|
|
1202
|
|
|
$class = $this->dumpValue($callable[0]); |
|
1203
|
|
|
// If the class is a string we can optimize away |
|
1204
|
|
|
if (str_starts_with($class, "'") && !str_contains($class, '$')) { |
|
1205
|
|
|
if ("''" === $class) { |
|
1206
|
|
|
throw new RuntimeException(\sprintf('Cannot dump definition: "%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 ? 'The "'.$id.'"' : 'inline')); |
|
1207
|
|
|
} |
|
1208
|
|
|
|
|
1209
|
|
|
return $return.\sprintf('%s::%s(%s)', $this->dumpLiteralClass($class), $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; |
|
1210
|
|
|
} |
|
1211
|
|
|
|
|
1212
|
|
|
if (str_starts_with($class, 'new ')) { |
|
1213
|
|
|
return $return.\sprintf('(%s)->%s(%s)', $class, $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; |
|
1214
|
|
|
} |
|
1215
|
|
|
|
|
1216
|
|
|
return $return.\sprintf("[%s, '%s'](%s)", $class, $callable[1], $arguments ? implode(', ', $arguments) : '').$tail; |
|
1217
|
|
|
} |
|
1218
|
|
|
|
|
1219
|
|
|
if (null === $class = $definition->getClass()) { |
|
1220
|
|
|
throw new RuntimeException('Cannot dump definitions which have no class nor factory.'); |
|
1221
|
|
|
} |
|
1222
|
|
|
|
|
1223
|
|
|
if (!$asGhostObject) { |
|
1224
|
|
|
return $return.\sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments)).$tail; |
|
1225
|
|
|
} |
|
1226
|
|
|
|
|
1227
|
|
|
if (!method_exists($this->container->getParameterBag()->resolveValue($class), '__construct')) { |
|
1228
|
|
|
return $return.'$lazyLoad'.$tail; |
|
1229
|
|
|
} |
|
1230
|
|
|
|
|
1231
|
|
|
return $return.\sprintf('($lazyLoad->__construct(%s) && false ?: $lazyLoad)', implode(', ', $arguments)).$tail; |
|
1232
|
|
|
} |
|
1233
|
|
|
|
|
1234
|
|
|
private function startClass(string $class, string $baseClass, bool $hasProxyClasses): string |
|
1235
|
|
|
{ |
|
1236
|
|
|
$namespaceLine = !$this->asFiles && $this->namespace ? "\nnamespace {$this->namespace};\n" : ''; |
|
1237
|
|
|
|
|
1238
|
|
|
$code = <<<EOF |
|
1239
|
|
|
<?php |
|
1240
|
|
|
$namespaceLine |
|
1241
|
|
|
use Symfony\Component\DependencyInjection\Argument\RewindableGenerator; |
|
1242
|
|
|
use Symfony\Component\DependencyInjection\ContainerInterface; |
|
1243
|
|
|
use Symfony\Component\DependencyInjection\Container; |
|
1244
|
|
|
use Symfony\Component\DependencyInjection\Exception\LogicException; |
|
1245
|
|
|
use Symfony\Component\DependencyInjection\Exception\ParameterNotFoundException; |
|
1246
|
|
|
use Symfony\Component\DependencyInjection\Exception\RuntimeException; |
|
1247
|
|
|
use Symfony\Component\DependencyInjection\ParameterBag\FrozenParameterBag; |
|
1248
|
|
|
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBagInterface; |
|
1249
|
|
|
|
|
1250
|
|
|
/*{$this->docStar} |
|
1251
|
|
|
* @internal This class has been auto-generated by the Symfony Dependency Injection Component. |
|
1252
|
|
|
*/ |
|
1253
|
|
|
class $class extends $baseClass |
|
1254
|
|
|
{ |
|
1255
|
|
|
private const DEPRECATED_PARAMETERS = []; |
|
1256
|
|
|
|
|
1257
|
|
|
private const NONEMPTY_PARAMETERS = []; |
|
1258
|
|
|
|
|
1259
|
|
|
protected \$parameters = []; |
|
1260
|
|
|
|
|
1261
|
|
|
public function __construct() |
|
1262
|
|
|
{ |
|
1263
|
|
|
|
|
1264
|
|
|
EOF; |
|
1265
|
|
|
$code = str_replace(" private const DEPRECATED_PARAMETERS = [];\n\n", $this->addDeprecatedParameters(), $code); |
|
1266
|
|
|
$code = str_replace(" private const NONEMPTY_PARAMETERS = [];\n\n", $this->addNonEmptyParameters(), $code); |
|
1267
|
|
|
|
|
1268
|
|
|
if ($this->asFiles) { |
|
1269
|
|
|
$code = str_replace('__construct()', '__construct(private array $buildParameters = [], protected string $containerDir = __DIR__)', $code); |
|
1270
|
|
|
|
|
1271
|
|
|
if (null !== $this->targetDirRegex) { |
|
1272
|
|
|
$code = str_replace('$parameters = []', "\$targetDir;\n protected \$parameters = []", $code); |
|
1273
|
|
|
$code .= ' $this->targetDir = \\dirname($containerDir);'."\n"; |
|
1274
|
|
|
} |
|
1275
|
|
|
} |
|
1276
|
|
|
|
|
1277
|
|
|
if (Container::class !== $this->baseClass) { |
|
1278
|
|
|
$r = $this->container->getReflectionClass($this->baseClass, false); |
|
1279
|
|
|
if (null !== $r |
|
1280
|
|
|
&& (null !== $constructor = $r->getConstructor()) |
|
1281
|
|
|
&& 0 === $constructor->getNumberOfRequiredParameters() |
|
1282
|
|
|
&& Container::class !== $constructor->getDeclaringClass()->name |
|
1283
|
|
|
) { |
|
1284
|
|
|
$code .= " parent::__construct();\n"; |
|
1285
|
|
|
$code .= " \$this->parameterBag = null;\n\n"; |
|
1286
|
|
|
} |
|
1287
|
|
|
} |
|
1288
|
|
|
|
|
1289
|
|
|
if ($this->container->getParameterBag()->all()) { |
|
1290
|
|
|
$code .= " \$this->parameters = \$this->getDefaultParameters();\n\n"; |
|
1291
|
|
|
} |
|
1292
|
|
|
$code .= " \$this->services = \$this->privates = [];\n"; |
|
1293
|
|
|
|
|
1294
|
|
|
$code .= $this->addSyntheticIds(); |
|
1295
|
|
|
$code .= $this->addMethodMap(); |
|
1296
|
|
|
$code .= $this->asFiles && !$this->inlineFactories ? $this->addFileMap() : ''; |
|
1297
|
|
|
$code .= $this->addAliases(); |
|
1298
|
|
|
$code .= $this->addInlineRequires($hasProxyClasses); |
|
1299
|
|
|
$code .= <<<EOF |
|
1300
|
|
|
} |
|
1301
|
|
|
|
|
1302
|
|
|
public function compile(): void |
|
1303
|
|
|
{ |
|
1304
|
|
|
throw new LogicException('You cannot compile a dumped container that was already compiled.'); |
|
1305
|
|
|
} |
|
1306
|
|
|
|
|
1307
|
|
|
public function isCompiled(): bool |
|
1308
|
|
|
{ |
|
1309
|
|
|
return true; |
|
1310
|
|
|
} |
|
1311
|
|
|
|
|
1312
|
|
|
EOF; |
|
1313
|
|
|
$code .= $this->addRemovedIds(); |
|
1314
|
|
|
|
|
1315
|
|
|
if ($this->asFiles && !$this->inlineFactories) { |
|
1316
|
|
|
$code .= <<<'EOF' |
|
1317
|
|
|
|
|
1318
|
|
|
protected function load($file, $lazyLoad = true): mixed |
|
1319
|
|
|
{ |
|
1320
|
|
|
if (class_exists($class = __NAMESPACE__.'\\'.$file, false)) { |
|
1321
|
|
|
return $class::do($this, $lazyLoad); |
|
1322
|
|
|
} |
|
1323
|
|
|
|
|
1324
|
|
|
if ('.' === $file[-4]) { |
|
1325
|
|
|
$class = substr($class, 0, -4); |
|
1326
|
|
|
} else { |
|
1327
|
|
|
$file .= '.php'; |
|
1328
|
|
|
} |
|
1329
|
|
|
|
|
1330
|
|
|
$service = require $this->containerDir.\DIRECTORY_SEPARATOR.$file; |
|
1331
|
|
|
|
|
1332
|
|
|
return class_exists($class, false) ? $class::do($this, $lazyLoad) : $service; |
|
1333
|
|
|
} |
|
1334
|
|
|
|
|
1335
|
|
|
EOF; |
|
1336
|
|
|
} |
|
1337
|
|
|
|
|
1338
|
|
|
foreach ($this->container->getDefinitions() as $definition) { |
|
1339
|
|
|
if (!$definition->isLazy() || !$this->hasProxyDumper) { |
|
1340
|
|
|
continue; |
|
1341
|
|
|
} |
|
1342
|
|
|
|
|
1343
|
|
|
if ($this->asFiles && !$this->inlineFactories) { |
|
1344
|
|
|
$proxyLoader = "class_exists(\$class, false) || require __DIR__.'/'.\$class.'.php';\n\n "; |
|
1345
|
|
|
} else { |
|
1346
|
|
|
$proxyLoader = ''; |
|
1347
|
|
|
} |
|
1348
|
|
|
|
|
1349
|
|
|
$code .= <<<EOF |
|
1350
|
|
|
|
|
1351
|
|
|
protected function createProxy(\$class, \Closure \$factory) |
|
1352
|
|
|
{ |
|
1353
|
|
|
{$proxyLoader}return \$factory(); |
|
1354
|
|
|
} |
|
1355
|
|
|
|
|
1356
|
|
|
EOF; |
|
1357
|
|
|
break; |
|
1358
|
|
|
} |
|
1359
|
|
|
|
|
1360
|
|
|
return $code; |
|
1361
|
|
|
} |
|
1362
|
|
|
|
|
1363
|
|
|
private function addSyntheticIds(): string |
|
1364
|
|
|
{ |
|
1365
|
|
|
$code = ''; |
|
1366
|
|
|
$definitions = $this->container->getDefinitions(); |
|
1367
|
|
|
ksort($definitions); |
|
1368
|
|
|
foreach ($definitions as $id => $definition) { |
|
1369
|
|
|
if ($definition->isSynthetic() && 'service_container' !== $id) { |
|
1370
|
|
|
$code .= ' '.$this->doExport($id)." => true,\n"; |
|
1371
|
|
|
} |
|
1372
|
|
|
} |
|
1373
|
|
|
|
|
1374
|
|
|
return $code ? " \$this->syntheticIds = [\n{$code} ];\n" : ''; |
|
1375
|
|
|
} |
|
1376
|
|
|
|
|
1377
|
|
|
private function addRemovedIds(): string |
|
1378
|
|
|
{ |
|
1379
|
|
|
$ids = $this->container->getRemovedIds(); |
|
1380
|
|
|
foreach ($this->container->getDefinitions() as $id => $definition) { |
|
1381
|
|
|
if (!$definition->isPublic() && '.' !== ($id[0] ?? '-')) { |
|
1382
|
|
|
$ids[$id] = true; |
|
1383
|
|
|
} |
|
1384
|
|
|
} |
|
1385
|
|
|
if (!$ids) { |
|
1386
|
|
|
return ''; |
|
1387
|
|
|
} |
|
1388
|
|
|
if ($this->asFiles) { |
|
1389
|
|
|
$code = "require \$this->containerDir.\\DIRECTORY_SEPARATOR.'removed-ids.php'"; |
|
1390
|
|
|
} else { |
|
1391
|
|
|
$code = ''; |
|
1392
|
|
|
$ids = array_keys($ids); |
|
1393
|
|
|
sort($ids); |
|
1394
|
|
|
foreach ($ids as $id) { |
|
1395
|
|
|
if (preg_match(FileLoader::ANONYMOUS_ID_REGEXP, $id)) { |
|
1396
|
|
|
continue; |
|
1397
|
|
|
} |
|
1398
|
|
|
$code .= ' '.$this->doExport($id)." => true,\n"; |
|
1399
|
|
|
} |
|
1400
|
|
|
|
|
1401
|
|
|
$code = "[\n{$code} ]"; |
|
1402
|
|
|
} |
|
1403
|
|
|
|
|
1404
|
|
|
return <<<EOF |
|
1405
|
|
|
|
|
1406
|
|
|
public function getRemovedIds(): array |
|
1407
|
|
|
{ |
|
1408
|
|
|
return {$code}; |
|
1409
|
|
|
} |
|
1410
|
|
|
|
|
1411
|
|
|
EOF; |
|
1412
|
|
|
} |
|
1413
|
|
|
|
|
1414
|
|
|
private function addDeprecatedParameters(): string |
|
1415
|
|
|
{ |
|
1416
|
|
|
if (!($bag = $this->container->getParameterBag()) instanceof ParameterBag) { |
|
1417
|
|
|
return ''; |
|
1418
|
|
|
} |
|
1419
|
|
|
|
|
1420
|
|
|
if (!$deprecated = $bag->allDeprecated()) { |
|
1421
|
|
|
return ''; |
|
1422
|
|
|
} |
|
1423
|
|
|
$code = ''; |
|
1424
|
|
|
ksort($deprecated); |
|
1425
|
|
|
foreach ($deprecated as $param => $deprecation) { |
|
1426
|
|
|
$code .= ' '.$this->doExport($param).' => ['.implode(', ', array_map($this->doExport(...), $deprecation))."],\n"; |
|
1427
|
|
|
} |
|
1428
|
|
|
|
|
1429
|
|
|
return " private const DEPRECATED_PARAMETERS = [\n{$code} ];\n\n"; |
|
1430
|
|
|
} |
|
1431
|
|
|
|
|
1432
|
|
|
private function addNonEmptyParameters(): string |
|
1433
|
|
|
{ |
|
1434
|
|
|
if (!($bag = $this->container->getParameterBag()) instanceof ParameterBag) { |
|
1435
|
|
|
return ''; |
|
1436
|
|
|
} |
|
1437
|
|
|
|
|
1438
|
|
|
if (!$nonEmpty = $bag->allNonEmpty()) { |
|
1439
|
|
|
return ''; |
|
1440
|
|
|
} |
|
1441
|
|
|
$code = ''; |
|
1442
|
|
|
ksort($nonEmpty); |
|
1443
|
|
|
foreach ($nonEmpty as $param => $message) { |
|
1444
|
|
|
$code .= ' '.$this->doExport($param).' => '.$this->doExport($message).",\n"; |
|
1445
|
|
|
} |
|
1446
|
|
|
|
|
1447
|
|
|
return " private const NONEMPTY_PARAMETERS = [\n{$code} ];\n\n"; |
|
1448
|
|
|
} |
|
1449
|
|
|
|
|
1450
|
|
|
private function addMethodMap(): string |
|
1451
|
|
|
{ |
|
1452
|
|
|
$code = ''; |
|
1453
|
|
|
$definitions = $this->container->getDefinitions(); |
|
1454
|
|
|
ksort($definitions); |
|
1455
|
|
|
foreach ($definitions as $id => $definition) { |
|
1456
|
|
|
if (!$definition->isSynthetic() && $definition->isPublic() && (!$this->asFiles || $this->inlineFactories || $this->isHotPath($definition))) { |
|
1457
|
|
|
$code .= ' '.$this->doExport($id).' => '.$this->doExport($this->generateMethodName($id)).",\n"; |
|
1458
|
|
|
} |
|
1459
|
|
|
} |
|
1460
|
|
|
|
|
1461
|
|
|
$aliases = $this->container->getAliases(); |
|
1462
|
|
|
foreach ($aliases as $alias => $id) { |
|
1463
|
|
|
if (!$id->isDeprecated()) { |
|
1464
|
|
|
continue; |
|
1465
|
|
|
} |
|
1466
|
|
|
$code .= ' '.$this->doExport($alias).' => '.$this->doExport($this->generateMethodName($alias)).",\n"; |
|
1467
|
|
|
} |
|
1468
|
|
|
|
|
1469
|
|
|
return $code ? " \$this->methodMap = [\n{$code} ];\n" : ''; |
|
1470
|
|
|
} |
|
1471
|
|
|
|
|
1472
|
|
|
private function addFileMap(): string |
|
1473
|
|
|
{ |
|
1474
|
|
|
$code = ''; |
|
1475
|
|
|
$definitions = $this->container->getDefinitions(); |
|
1476
|
|
|
ksort($definitions); |
|
1477
|
|
|
foreach ($definitions as $id => $definition) { |
|
1478
|
|
|
if (!$definition->isSynthetic() && $definition->isPublic() && !$this->isHotPath($definition)) { |
|
1479
|
|
|
$code .= \sprintf(" %s => '%s',\n", $this->doExport($id), $this->generateMethodName($id)); |
|
1480
|
|
|
} |
|
1481
|
|
|
} |
|
1482
|
|
|
|
|
1483
|
|
|
return $code ? " \$this->fileMap = [\n{$code} ];\n" : ''; |
|
1484
|
|
|
} |
|
1485
|
|
|
|
|
1486
|
|
|
private function addAliases(): string |
|
1487
|
|
|
{ |
|
1488
|
|
|
if (!$aliases = $this->container->getAliases()) { |
|
1489
|
|
|
return "\n \$this->aliases = [];\n"; |
|
1490
|
|
|
} |
|
1491
|
|
|
|
|
1492
|
|
|
$code = " \$this->aliases = [\n"; |
|
1493
|
|
|
ksort($aliases); |
|
1494
|
|
|
foreach ($aliases as $alias => $id) { |
|
1495
|
|
|
if ($id->isDeprecated()) { |
|
1496
|
|
|
continue; |
|
1497
|
|
|
} |
|
1498
|
|
|
|
|
1499
|
|
|
$id = (string) $id; |
|
1500
|
|
|
while (isset($aliases[$id])) { |
|
1501
|
|
|
$id = (string) $aliases[$id]; |
|
1502
|
|
|
} |
|
1503
|
|
|
$code .= ' '.$this->doExport($alias).' => '.$this->doExport($id).",\n"; |
|
1504
|
|
|
} |
|
1505
|
|
|
|
|
1506
|
|
|
return $code." ];\n"; |
|
1507
|
|
|
} |
|
1508
|
|
|
|
|
1509
|
|
|
private function addDeprecatedAliases(): string |
|
1510
|
|
|
{ |
|
1511
|
|
|
$code = ''; |
|
1512
|
|
|
$aliases = $this->container->getAliases(); |
|
1513
|
|
|
foreach ($aliases as $alias => $definition) { |
|
1514
|
|
|
if (!$definition->isDeprecated()) { |
|
1515
|
|
|
continue; |
|
1516
|
|
|
} |
|
1517
|
|
|
$public = $definition->isPublic() ? 'public' : 'private'; |
|
1518
|
|
|
$id = (string) $definition; |
|
1519
|
|
|
$methodNameAlias = $this->generateMethodName($alias); |
|
1520
|
|
|
$idExported = $this->export($id); |
|
1521
|
|
|
$deprecation = $definition->getDeprecation($alias); |
|
1522
|
|
|
$packageExported = $this->export($deprecation['package']); |
|
1523
|
|
|
$versionExported = $this->export($deprecation['version']); |
|
1524
|
|
|
$messageExported = $this->export($deprecation['message']); |
|
1525
|
|
|
$code .= <<<EOF |
|
1526
|
|
|
|
|
1527
|
|
|
/*{$this->docStar} |
|
1528
|
|
|
* Gets the $public '$alias' alias. |
|
1529
|
|
|
* |
|
1530
|
|
|
* @return object The "$id" service. |
|
1531
|
|
|
*/ |
|
1532
|
|
|
protected static function {$methodNameAlias}(\$container) |
|
1533
|
|
|
{ |
|
1534
|
|
|
trigger_deprecation($packageExported, $versionExported, $messageExported); |
|
1535
|
|
|
|
|
1536
|
|
|
return \$container->get($idExported); |
|
1537
|
|
|
} |
|
1538
|
|
|
|
|
1539
|
|
|
EOF; |
|
1540
|
|
|
} |
|
1541
|
|
|
|
|
1542
|
|
|
return $code; |
|
1543
|
|
|
} |
|
1544
|
|
|
|
|
1545
|
|
|
private function addInlineRequires(bool $hasProxyClasses): string |
|
1546
|
|
|
{ |
|
1547
|
|
|
$lineage = []; |
|
1548
|
|
|
$hotPathServices = $this->hotPathTag && $this->inlineRequires ? $this->container->findTaggedServiceIds($this->hotPathTag) : []; |
|
1549
|
|
|
|
|
1550
|
|
|
foreach ($hotPathServices as $id => $tags) { |
|
1551
|
|
|
$definition = $this->container->getDefinition($id); |
|
1552
|
|
|
|
|
1553
|
|
|
if ($definition->isLazy() && $this->hasProxyDumper) { |
|
1554
|
|
|
continue; |
|
1555
|
|
|
} |
|
1556
|
|
|
|
|
1557
|
|
|
$inlinedDefinitions = $this->getDefinitionsFromArguments([$definition]); |
|
1558
|
|
|
|
|
1559
|
|
|
foreach ($inlinedDefinitions as $def) { |
|
1560
|
|
|
foreach ($this->getClasses($def, $id) as $class) { |
|
1561
|
|
|
$this->collectLineage($class, $lineage); |
|
1562
|
|
|
} |
|
1563
|
|
|
} |
|
1564
|
|
|
} |
|
1565
|
|
|
|
|
1566
|
|
|
$code = ''; |
|
1567
|
|
|
|
|
1568
|
|
|
foreach ($lineage as $file) { |
|
1569
|
|
|
if (!isset($this->inlinedRequires[$file])) { |
|
1570
|
|
|
$this->inlinedRequires[$file] = true; |
|
1571
|
|
|
$code .= \sprintf("\n include_once %s;", $file); |
|
1572
|
|
|
} |
|
1573
|
|
|
} |
|
1574
|
|
|
|
|
1575
|
|
|
if ($hasProxyClasses) { |
|
1576
|
|
|
$code .= "\n include_once __DIR__.'/proxy-classes.php';"; |
|
1577
|
|
|
} |
|
1578
|
|
|
|
|
1579
|
|
|
return $code ? \sprintf("\n \$this->privates['service_container'] = static function (\$container) {%s\n };\n", $code) : ''; |
|
1580
|
|
|
} |
|
1581
|
|
|
|
|
1582
|
|
|
private function addDefaultParametersMethod(): string |
|
1583
|
|
|
{ |
|
1584
|
|
|
$bag = $this->container->getParameterBag(); |
|
1585
|
|
|
|
|
1586
|
|
|
if (!$bag->all() && (!$bag instanceof ParameterBag || !$bag->allNonEmpty())) { |
|
1587
|
|
|
return ''; |
|
1588
|
|
|
} |
|
1589
|
|
|
|
|
1590
|
|
|
$php = []; |
|
1591
|
|
|
$dynamicPhp = []; |
|
1592
|
|
|
|
|
1593
|
|
|
foreach ($bag->all() as $key => $value) { |
|
1594
|
|
|
if ($key !== $resolvedKey = $this->container->resolveEnvPlaceholders($key)) { |
|
1595
|
|
|
throw new InvalidArgumentException(\sprintf('Parameter name cannot use env parameters: "%s".', $resolvedKey)); |
|
1596
|
|
|
} |
|
1597
|
|
|
$hasEnum = false; |
|
1598
|
|
|
$export = $this->exportParameters([$value], '', 12, $hasEnum); |
|
1599
|
|
|
$export = explode('0 => ', substr(rtrim($export, " ]\n"), 2, -1), 2); |
|
1600
|
|
|
|
|
1601
|
|
|
if ($hasEnum || preg_match("/\\\$container->(?:getEnv\('(?:[-.\w\\\\]*+:)*+\w*+'\)|targetDir\.'')/", $export[1])) { |
|
1602
|
|
|
$dynamicPhp[$key] = \sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]); |
|
1603
|
|
|
$this->dynamicParameters[$key] = true; |
|
1604
|
|
|
} else { |
|
1605
|
|
|
$php[] = \sprintf('%s%s => %s,', $export[0], $this->export($key), $export[1]); |
|
1606
|
|
|
} |
|
1607
|
|
|
} |
|
1608
|
|
|
$parameters = \sprintf("[\n%s\n%s]", implode("\n", $php), str_repeat(' ', 8)); |
|
1609
|
|
|
|
|
1610
|
|
|
$code = <<<'EOF' |
|
1611
|
|
|
|
|
1612
|
|
|
public function getParameter(string $name): array|bool|string|int|float|\UnitEnum|null |
|
1613
|
|
|
{ |
|
1614
|
|
|
if (isset(self::DEPRECATED_PARAMETERS[$name])) { |
|
1615
|
|
|
trigger_deprecation(...self::DEPRECATED_PARAMETERS[$name]); |
|
1616
|
|
|
} |
|
1617
|
|
|
|
|
1618
|
|
|
if (isset($this->buildParameters[$name])) { |
|
1619
|
|
|
return $this->buildParameters[$name]; |
|
1620
|
|
|
} |
|
1621
|
|
|
|
|
1622
|
|
|
if (!(isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters))) { |
|
1623
|
|
|
throw new ParameterNotFoundException($name, extraMessage: self::NONEMPTY_PARAMETERS[$name] ?? null); |
|
1624
|
|
|
} |
|
1625
|
|
|
|
|
1626
|
|
|
if (isset($this->loadedDynamicParameters[$name])) { |
|
1627
|
|
|
$value = $this->loadedDynamicParameters[$name] ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); |
|
1628
|
|
|
} else { |
|
1629
|
|
|
$value = $this->parameters[$name]; |
|
1630
|
|
|
} |
|
1631
|
|
|
|
|
1632
|
|
|
if (isset(self::NONEMPTY_PARAMETERS[$name]) && (null === $value || '' === $value || [] === $value)) { |
|
1633
|
|
|
throw new \Symfony\Component\DependencyInjection\Exception\EmptyParameterValueException(self::NONEMPTY_PARAMETERS[$name]); |
|
1634
|
|
|
} |
|
1635
|
|
|
|
|
1636
|
|
|
return $value; |
|
1637
|
|
|
} |
|
1638
|
|
|
|
|
1639
|
|
|
public function hasParameter(string $name): bool |
|
1640
|
|
|
{ |
|
1641
|
|
|
if (isset($this->buildParameters[$name])) { |
|
1642
|
|
|
return true; |
|
1643
|
|
|
} |
|
1644
|
|
|
|
|
1645
|
|
|
return isset($this->parameters[$name]) || isset($this->loadedDynamicParameters[$name]) || \array_key_exists($name, $this->parameters); |
|
1646
|
|
|
} |
|
1647
|
|
|
|
|
1648
|
|
|
public function setParameter(string $name, $value): void |
|
1649
|
|
|
{ |
|
1650
|
|
|
throw new LogicException('Impossible to call set() on a frozen ParameterBag.'); |
|
1651
|
|
|
} |
|
1652
|
|
|
|
|
1653
|
|
|
public function getParameterBag(): ParameterBagInterface |
|
1654
|
|
|
{ |
|
1655
|
|
|
if (!isset($this->parameterBag)) { |
|
1656
|
|
|
$parameters = $this->parameters; |
|
1657
|
|
|
foreach ($this->loadedDynamicParameters as $name => $loaded) { |
|
1658
|
|
|
$parameters[$name] = $loaded ? $this->dynamicParameters[$name] : $this->getDynamicParameter($name); |
|
1659
|
|
|
} |
|
1660
|
|
|
foreach ($this->buildParameters as $name => $value) { |
|
1661
|
|
|
$parameters[$name] = $value; |
|
1662
|
|
|
} |
|
1663
|
|
|
$this->parameterBag = new FrozenParameterBag($parameters, self::DEPRECATED_PARAMETERS, self::NONEMPTY_PARAMETERS); |
|
1664
|
|
|
} |
|
1665
|
|
|
|
|
1666
|
|
|
return $this->parameterBag; |
|
1667
|
|
|
} |
|
1668
|
|
|
|
|
1669
|
|
|
EOF; |
|
1670
|
|
|
|
|
1671
|
|
|
if (!$this->asFiles) { |
|
1672
|
|
|
$code = preg_replace('/^.*buildParameters.*\n.*\n.*\n\n?/m', '', $code); |
|
1673
|
|
|
} |
|
1674
|
|
|
|
|
1675
|
|
|
if (!$bag instanceof ParameterBag || !$bag->allDeprecated()) { |
|
1676
|
|
|
$code = preg_replace("/\n.*DEPRECATED_PARAMETERS.*\n.*\n.*\n/m", '', $code, 1); |
|
1677
|
|
|
$code = str_replace(', self::DEPRECATED_PARAMETERS', ', []', $code); |
|
1678
|
|
|
} |
|
1679
|
|
|
|
|
1680
|
|
|
if (!$bag instanceof ParameterBag || !$bag->allNonEmpty()) { |
|
1681
|
|
|
$code = str_replace(', extraMessage: self::NONEMPTY_PARAMETERS[$name] ?? null', '', $code); |
|
1682
|
|
|
$code = str_replace(', self::NONEMPTY_PARAMETERS', '', $code); |
|
1683
|
|
|
$code = preg_replace("/\n.*NONEMPTY_PARAMETERS.*\n.*\n.*\n/m", '', $code, 1); |
|
1684
|
|
|
} |
|
1685
|
|
|
|
|
1686
|
|
|
if ($dynamicPhp) { |
|
1687
|
|
|
$loadedDynamicParameters = $this->exportParameters(array_combine(array_keys($dynamicPhp), array_fill(0, \count($dynamicPhp), false)), '', 8); |
|
1688
|
|
|
$getDynamicParameter = <<<'EOF' |
|
1689
|
|
|
$container = $this; |
|
1690
|
|
|
$value = match ($name) { |
|
1691
|
|
|
%s |
|
1692
|
|
|
default => throw new ParameterNotFoundException($name), |
|
1693
|
|
|
}; |
|
1694
|
|
|
$this->loadedDynamicParameters[$name] = true; |
|
1695
|
|
|
|
|
1696
|
|
|
return $this->dynamicParameters[$name] = $value; |
|
1697
|
|
|
EOF; |
|
1698
|
|
|
$getDynamicParameter = \sprintf($getDynamicParameter, implode("\n", $dynamicPhp)); |
|
1699
|
|
|
} else { |
|
1700
|
|
|
$loadedDynamicParameters = '[]'; |
|
1701
|
|
|
$getDynamicParameter = str_repeat(' ', 8).'throw new ParameterNotFoundException($name);'; |
|
1702
|
|
|
} |
|
1703
|
|
|
|
|
1704
|
|
|
return $code.<<<EOF |
|
1705
|
|
|
|
|
1706
|
|
|
private \$loadedDynamicParameters = {$loadedDynamicParameters}; |
|
1707
|
|
|
private \$dynamicParameters = []; |
|
1708
|
|
|
|
|
1709
|
|
|
private function getDynamicParameter(string \$name) |
|
1710
|
|
|
{ |
|
1711
|
|
|
{$getDynamicParameter} |
|
1712
|
|
|
} |
|
1713
|
|
|
|
|
1714
|
|
|
protected function getDefaultParameters(): array |
|
1715
|
|
|
{ |
|
1716
|
|
|
return $parameters; |
|
1717
|
|
|
} |
|
1718
|
|
|
|
|
1719
|
|
|
EOF; |
|
1720
|
|
|
} |
|
1721
|
|
|
|
|
1722
|
|
|
/** |
|
1723
|
|
|
* @throws InvalidArgumentException |
|
1724
|
|
|
*/ |
|
1725
|
|
|
private function exportParameters(array $parameters, string $path = '', int $indent = 12, bool &$hasEnum = false): string |
|
1726
|
|
|
{ |
|
1727
|
|
|
$php = []; |
|
1728
|
|
|
foreach ($parameters as $key => $value) { |
|
1729
|
|
|
if (\is_array($value)) { |
|
1730
|
|
|
$value = $this->exportParameters($value, $path.'/'.$key, $indent + 4, $hasEnum); |
|
1731
|
|
|
} elseif ($value instanceof ArgumentInterface) { |
|
1732
|
|
|
throw new InvalidArgumentException(\sprintf('You cannot dump a container with parameters that contain special arguments. "%s" found in "%s".', get_debug_type($value), $path.'/'.$key)); |
|
1733
|
|
|
} elseif ($value instanceof Variable) { |
|
1734
|
|
|
throw new InvalidArgumentException(\sprintf('You cannot dump a container with parameters that contain variable references. Variable "%s" found in "%s".', $value, $path.'/'.$key)); |
|
1735
|
|
|
} elseif ($value instanceof Definition) { |
|
1736
|
|
|
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)); |
|
1737
|
|
|
} elseif ($value instanceof Reference) { |
|
1738
|
|
|
throw new InvalidArgumentException(\sprintf('You cannot dump a container with parameters that contain references to other services (reference to service "%s" found in "%s").', $value, $path.'/'.$key)); |
|
1739
|
|
|
} elseif ($value instanceof Expression) { |
|
1740
|
|
|
throw new InvalidArgumentException(\sprintf('You cannot dump a container with parameters that contain expressions. Expression "%s" found in "%s".', $value, $path.'/'.$key)); |
|
1741
|
|
|
} elseif ($value instanceof \UnitEnum) { |
|
|
|
|
|
|
1742
|
|
|
$hasEnum = true; |
|
1743
|
|
|
$value = \sprintf('\%s::%s', $value::class, $value->name); |
|
1744
|
|
|
} else { |
|
1745
|
|
|
$value = $this->export($value); |
|
1746
|
|
|
} |
|
1747
|
|
|
|
|
1748
|
|
|
$php[] = \sprintf('%s%s => %s,', str_repeat(' ', $indent), $this->export($key), $value); |
|
1749
|
|
|
} |
|
1750
|
|
|
|
|
1751
|
|
|
return \sprintf("[\n%s\n%s]", implode("\n", $php), str_repeat(' ', $indent - 4)); |
|
1752
|
|
|
} |
|
1753
|
|
|
|
|
1754
|
|
|
private function endClass(): string |
|
1755
|
|
|
{ |
|
1756
|
|
|
return <<<'EOF' |
|
1757
|
|
|
} |
|
1758
|
|
|
|
|
1759
|
|
|
EOF; |
|
1760
|
|
|
} |
|
1761
|
|
|
|
|
1762
|
|
|
private function wrapServiceConditionals(mixed $value, string $code): string |
|
1763
|
|
|
{ |
|
1764
|
|
|
if (!$condition = $this->getServiceConditionals($value)) { |
|
1765
|
|
|
return $code; |
|
1766
|
|
|
} |
|
1767
|
|
|
|
|
1768
|
|
|
// re-indent the wrapped code |
|
1769
|
|
|
$code = implode("\n", array_map(fn ($line) => $line ? ' '.$line : $line, explode("\n", $code))); |
|
1770
|
|
|
|
|
1771
|
|
|
return \sprintf(" if (%s) {\n%s }\n", $condition, $code); |
|
1772
|
|
|
} |
|
1773
|
|
|
|
|
1774
|
|
|
private function getServiceConditionals(mixed $value): string |
|
1775
|
|
|
{ |
|
1776
|
|
|
$conditions = []; |
|
1777
|
|
|
foreach (ContainerBuilder::getInitializedConditionals($value) as $service) { |
|
1778
|
|
|
if (!$this->container->hasDefinition($service)) { |
|
1779
|
|
|
return 'false'; |
|
1780
|
|
|
} |
|
1781
|
|
|
$conditions[] = \sprintf('isset($container->%s[%s])', $this->container->getDefinition($service)->isPublic() ? 'services' : 'privates', $this->doExport($service)); |
|
1782
|
|
|
} |
|
1783
|
|
|
foreach (ContainerBuilder::getServiceConditionals($value) as $service) { |
|
1784
|
|
|
if ($this->container->hasDefinition($service) && !$this->container->getDefinition($service)->isPublic()) { |
|
1785
|
|
|
continue; |
|
1786
|
|
|
} |
|
1787
|
|
|
|
|
1788
|
|
|
$conditions[] = \sprintf('$container->has(%s)', $this->doExport($service)); |
|
1789
|
|
|
} |
|
1790
|
|
|
|
|
1791
|
|
|
if (!$conditions) { |
|
1792
|
|
|
return ''; |
|
1793
|
|
|
} |
|
1794
|
|
|
|
|
1795
|
|
|
return implode(' && ', $conditions); |
|
1796
|
|
|
} |
|
1797
|
|
|
|
|
1798
|
|
|
private function getDefinitionsFromArguments(array $arguments, ?\SplObjectStorage $definitions = null, array &$calls = [], ?bool $byConstructor = null): \SplObjectStorage |
|
1799
|
|
|
{ |
|
1800
|
|
|
$definitions ??= new \SplObjectStorage(); |
|
1801
|
|
|
|
|
1802
|
|
|
foreach ($arguments as $argument) { |
|
1803
|
|
|
if (\is_array($argument)) { |
|
1804
|
|
|
$this->getDefinitionsFromArguments($argument, $definitions, $calls, $byConstructor); |
|
1805
|
|
|
} elseif ($argument instanceof Reference) { |
|
1806
|
|
|
$id = (string) $argument; |
|
1807
|
|
|
|
|
1808
|
|
|
while ($this->container->hasAlias($id)) { |
|
1809
|
|
|
$id = (string) $this->container->getAlias($id); |
|
1810
|
|
|
} |
|
1811
|
|
|
|
|
1812
|
|
|
if (!isset($calls[$id])) { |
|
1813
|
|
|
$calls[$id] = [0, $argument->getInvalidBehavior(), $byConstructor]; |
|
1814
|
|
|
} else { |
|
1815
|
|
|
$calls[$id][1] = min($calls[$id][1], $argument->getInvalidBehavior()); |
|
1816
|
|
|
} |
|
1817
|
|
|
|
|
1818
|
|
|
++$calls[$id][0]; |
|
1819
|
|
|
} elseif (!$argument instanceof Definition) { |
|
1820
|
|
|
// no-op |
|
1821
|
|
|
} elseif (isset($definitions[$argument])) { |
|
1822
|
|
|
$definitions[$argument] = 1 + $definitions[$argument]; |
|
1823
|
|
|
} else { |
|
1824
|
|
|
$definitions[$argument] = 1; |
|
1825
|
|
|
$arguments = [$argument->getArguments(), $argument->getFactory()]; |
|
1826
|
|
|
$this->getDefinitionsFromArguments($arguments, $definitions, $calls, null === $byConstructor || $byConstructor); |
|
1827
|
|
|
$arguments = [$argument->getProperties(), $argument->getMethodCalls(), $argument->getConfigurator()]; |
|
1828
|
|
|
$this->getDefinitionsFromArguments($arguments, $definitions, $calls, null !== $byConstructor && $byConstructor); |
|
1829
|
|
|
} |
|
1830
|
|
|
} |
|
1831
|
|
|
|
|
1832
|
|
|
return $definitions; |
|
1833
|
|
|
} |
|
1834
|
|
|
|
|
1835
|
|
|
/** |
|
1836
|
|
|
* @throws RuntimeException |
|
1837
|
|
|
*/ |
|
1838
|
|
|
private function dumpValue(mixed $value, bool $interpolate = true): string |
|
1839
|
|
|
{ |
|
1840
|
|
|
if (\is_array($value)) { |
|
1841
|
|
|
if ($value && $interpolate && false !== $param = array_search($value, $this->container->getParameterBag()->all(), true)) { |
|
|
|
|
|
|
1842
|
|
|
return $this->dumpValue("%$param%"); |
|
1843
|
|
|
} |
|
1844
|
|
|
$isList = array_is_list($value); |
|
|
|
|
|
|
1845
|
|
|
$code = []; |
|
1846
|
|
|
foreach ($value as $k => $v) { |
|
1847
|
|
|
$code[] = $isList ? $this->dumpValue($v, $interpolate) : \sprintf('%s => %s', $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate)); |
|
1848
|
|
|
} |
|
1849
|
|
|
|
|
1850
|
|
|
return \sprintf('[%s]', implode(', ', $code)); |
|
1851
|
|
|
} elseif ($value instanceof ArgumentInterface) { |
|
1852
|
|
|
$scope = [$this->definitionVariables, $this->referenceVariables]; |
|
1853
|
|
|
$this->definitionVariables = $this->referenceVariables = null; |
|
1854
|
|
|
|
|
1855
|
|
|
try { |
|
1856
|
|
|
if ($value instanceof ServiceClosureArgument) { |
|
1857
|
|
|
$value = $value->getValues()[0]; |
|
1858
|
|
|
$code = $this->dumpValue($value, $interpolate); |
|
1859
|
|
|
|
|
1860
|
|
|
$returnedType = ''; |
|
1861
|
|
|
if ($value instanceof TypedReference) { |
|
1862
|
|
|
$returnedType = \sprintf(': %s\%s', ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE >= $value->getInvalidBehavior() ? '' : '?', str_replace(['|', '&'], ['|\\', '&\\'], $value->getType())); |
|
1863
|
|
|
} |
|
1864
|
|
|
|
|
1865
|
|
|
$attribute = ''; |
|
1866
|
|
|
if ($value instanceof Reference) { |
|
1867
|
|
|
$attribute = 'name: '.$this->dumpValue((string) $value, $interpolate); |
|
1868
|
|
|
|
|
1869
|
|
|
if ($this->container->hasDefinition($value) && ($class = $this->container->findDefinition($value)->getClass()) && $class !== (string) $value) { |
|
1870
|
|
|
$attribute .= ', class: '.$this->dumpValue($class, $interpolate); |
|
1871
|
|
|
} |
|
1872
|
|
|
|
|
1873
|
|
|
$attribute = \sprintf('#[\Closure(%s)] ', $attribute); |
|
1874
|
|
|
} |
|
1875
|
|
|
|
|
1876
|
|
|
return \sprintf('%sfn ()%s => %s', $attribute, $returnedType, $code); |
|
1877
|
|
|
} |
|
1878
|
|
|
|
|
1879
|
|
|
if ($value instanceof IteratorArgument) { |
|
1880
|
|
|
if (!$values = $value->getValues()) { |
|
1881
|
|
|
return 'new RewindableGenerator(fn () => new \EmptyIterator(), 0)'; |
|
1882
|
|
|
} |
|
1883
|
|
|
|
|
1884
|
|
|
$code = []; |
|
1885
|
|
|
$code[] = 'new RewindableGenerator(function () use ($container) {'; |
|
1886
|
|
|
|
|
1887
|
|
|
$operands = [0]; |
|
1888
|
|
|
foreach ($values as $k => $v) { |
|
1889
|
|
|
($c = $this->getServiceConditionals($v)) ? $operands[] = "(int) ($c)" : ++$operands[0]; |
|
1890
|
|
|
$v = $this->wrapServiceConditionals($v, \sprintf(" yield %s => %s;\n", $this->dumpValue($k, $interpolate), $this->dumpValue($v, $interpolate))); |
|
1891
|
|
|
foreach (explode("\n", $v) as $v) { |
|
|
|
|
|
|
1892
|
|
|
if ($v) { |
|
1893
|
|
|
$code[] = ' '.$v; |
|
1894
|
|
|
} |
|
1895
|
|
|
} |
|
1896
|
|
|
} |
|
1897
|
|
|
|
|
1898
|
|
|
$code[] = \sprintf(' }, %s)', \count($operands) > 1 ? 'fn () => '.implode(' + ', $operands) : $operands[0]); |
|
1899
|
|
|
|
|
1900
|
|
|
return implode("\n", $code); |
|
1901
|
|
|
} |
|
1902
|
|
|
|
|
1903
|
|
|
if ($value instanceof ServiceLocatorArgument) { |
|
1904
|
|
|
$serviceMap = ''; |
|
1905
|
|
|
$serviceTypes = ''; |
|
1906
|
|
|
foreach ($value->getValues() as $k => $v) { |
|
1907
|
|
|
if (!$v instanceof Reference) { |
|
1908
|
|
|
$serviceMap .= \sprintf("\n %s => [%s],", $this->export($k), $this->dumpValue($v)); |
|
1909
|
|
|
$serviceTypes .= \sprintf("\n %s => '?',", $this->export($k)); |
|
1910
|
|
|
continue; |
|
1911
|
|
|
} |
|
1912
|
|
|
$id = (string) $v; |
|
1913
|
|
|
while ($this->container->hasAlias($id)) { |
|
1914
|
|
|
$id = (string) $this->container->getAlias($id); |
|
1915
|
|
|
} |
|
1916
|
|
|
$definition = $this->container->getDefinition($id); |
|
1917
|
|
|
$load = !($definition->hasErrors() && $e = $definition->getErrors()) ? $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) : reset($e); |
|
1918
|
|
|
$serviceMap .= \sprintf("\n %s => [%s, %s, %s, %s],", |
|
1919
|
|
|
$this->export($k), |
|
1920
|
|
|
$this->export($definition->isShared() ? ($definition->isPublic() ? 'services' : 'privates') : false), |
|
1921
|
|
|
$this->doExport($id), |
|
1922
|
|
|
$this->export(ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE !== $v->getInvalidBehavior() && !\is_string($load) ? $this->generateMethodName($id) : null), |
|
1923
|
|
|
$this->export($load) |
|
1924
|
|
|
); |
|
1925
|
|
|
$serviceTypes .= \sprintf("\n %s => %s,", $this->export($k), $this->export($v instanceof TypedReference ? $v->getType() : '?')); |
|
1926
|
|
|
$this->locatedIds[$id] = true; |
|
1927
|
|
|
} |
|
1928
|
|
|
$this->addGetService = true; |
|
1929
|
|
|
|
|
1930
|
|
|
return \sprintf('new \%s($container->getService ??= $container->getService(...), [%s%s], [%s%s])', ServiceLocator::class, $serviceMap, $serviceMap ? "\n " : '', $serviceTypes, $serviceTypes ? "\n " : ''); |
|
1931
|
|
|
} |
|
1932
|
|
|
} finally { |
|
1933
|
|
|
[$this->definitionVariables, $this->referenceVariables] = $scope; |
|
1934
|
|
|
} |
|
1935
|
|
|
} elseif ($value instanceof Definition) { |
|
1936
|
|
|
if ($value->hasErrors() && $e = $value->getErrors()) { |
|
1937
|
|
|
return \sprintf('throw new RuntimeException(%s)', $this->export(reset($e))); |
|
1938
|
|
|
} |
|
1939
|
|
|
if ($this->definitionVariables?->contains($value)) { |
|
1940
|
|
|
return $this->dumpValue($this->definitionVariables[$value], $interpolate); |
|
1941
|
|
|
} |
|
1942
|
|
|
if ($value->getMethodCalls()) { |
|
1943
|
|
|
throw new RuntimeException('Cannot dump definitions which have method calls.'); |
|
1944
|
|
|
} |
|
1945
|
|
|
if ($value->getProperties()) { |
|
1946
|
|
|
throw new RuntimeException('Cannot dump definitions which have properties.'); |
|
1947
|
|
|
} |
|
1948
|
|
|
if (null !== $value->getConfigurator()) { |
|
1949
|
|
|
throw new RuntimeException('Cannot dump definitions which have a configurator.'); |
|
1950
|
|
|
} |
|
1951
|
|
|
|
|
1952
|
|
|
return $this->addNewInstance($value); |
|
1953
|
|
|
} elseif ($value instanceof Variable) { |
|
1954
|
|
|
return '$'.$value; |
|
1955
|
|
|
} elseif ($value instanceof Reference) { |
|
1956
|
|
|
$id = (string) $value; |
|
1957
|
|
|
|
|
1958
|
|
|
while ($this->container->hasAlias($id)) { |
|
1959
|
|
|
$id = (string) $this->container->getAlias($id); |
|
1960
|
|
|
} |
|
1961
|
|
|
|
|
1962
|
|
|
if (null !== $this->referenceVariables && isset($this->referenceVariables[$id])) { |
|
1963
|
|
|
return $this->dumpValue($this->referenceVariables[$id], $interpolate); |
|
1964
|
|
|
} |
|
1965
|
|
|
|
|
1966
|
|
|
return $this->getServiceCall($id, $value); |
|
1967
|
|
|
} elseif ($value instanceof Expression) { |
|
1968
|
|
|
return $this->getExpressionLanguage()->compile((string) $value, ['container' => 'container']); |
|
1969
|
|
|
} elseif ($value instanceof Parameter) { |
|
1970
|
|
|
return $this->dumpParameter($value); |
|
1971
|
|
|
} elseif (true === $interpolate && \is_string($value)) { |
|
1972
|
|
|
if (preg_match('/^%([^%]+)%$/', $value, $match)) { |
|
1973
|
|
|
// we do this to deal with non string values (Boolean, integer, ...) |
|
1974
|
|
|
// the preg_replace_callback converts them to strings |
|
1975
|
|
|
return $this->dumpParameter($match[1]); |
|
1976
|
|
|
} |
|
1977
|
|
|
|
|
1978
|
|
|
$replaceParameters = fn ($match) => "'.".$this->dumpParameter($match[2]).".'"; |
|
1979
|
|
|
|
|
1980
|
|
|
return str_replace('%%', '%', preg_replace_callback('/(?<!%)(%)([^%]+)\1/', $replaceParameters, $this->export($value))); |
|
1981
|
|
|
} elseif ($value instanceof \UnitEnum) { |
|
1982
|
|
|
return \sprintf('\%s::%s', $value::class, $value->name); |
|
1983
|
|
|
} elseif ($value instanceof AbstractArgument) { |
|
1984
|
|
|
throw new RuntimeException($value->getTextWithContext()); |
|
1985
|
|
|
} elseif (\is_object($value) || \is_resource($value)) { |
|
1986
|
|
|
throw new RuntimeException(\sprintf('Unable to dump a service container if a parameter is an object or a resource, got "%s".', get_debug_type($value))); |
|
1987
|
|
|
} |
|
1988
|
|
|
|
|
1989
|
|
|
return $this->export($value); |
|
1990
|
|
|
} |
|
1991
|
|
|
|
|
1992
|
|
|
/** |
|
1993
|
|
|
* Dumps a string to a literal (aka PHP Code) class value. |
|
1994
|
|
|
* |
|
1995
|
|
|
* @throws RuntimeException |
|
1996
|
|
|
*/ |
|
1997
|
|
|
private function dumpLiteralClass(string $class): string |
|
1998
|
|
|
{ |
|
1999
|
|
|
if (str_contains($class, '$')) { |
|
2000
|
|
|
return \sprintf('${($_ = %s) && false ?: "_"}', $class); |
|
2001
|
|
|
} |
|
2002
|
|
|
if (!str_starts_with($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)) { |
|
2003
|
|
|
throw new RuntimeException(\sprintf('Cannot dump definition because of invalid class name (%s).', $class ?: 'n/a')); |
|
2004
|
|
|
} |
|
2005
|
|
|
|
|
2006
|
|
|
$class = substr(str_replace('\\\\', '\\', $class), 1, -1); |
|
2007
|
|
|
|
|
2008
|
|
|
return str_starts_with($class, '\\') ? $class : '\\'.$class; |
|
2009
|
|
|
} |
|
2010
|
|
|
|
|
2011
|
|
|
private function dumpParameter(string $name): string |
|
2012
|
|
|
{ |
|
2013
|
|
|
if (!$this->container->hasParameter($name) || ($this->dynamicParameters[$name] ?? false)) { |
|
2014
|
|
|
return \sprintf('$container->getParameter(%s)', $this->doExport($name)); |
|
2015
|
|
|
} |
|
2016
|
|
|
|
|
2017
|
|
|
$value = $this->container->getParameter($name); |
|
2018
|
|
|
$dumpedValue = $this->dumpValue($value, false); |
|
2019
|
|
|
|
|
2020
|
|
|
if (!$value || !\is_array($value)) { |
|
2021
|
|
|
return $dumpedValue; |
|
2022
|
|
|
} |
|
2023
|
|
|
|
|
2024
|
|
|
return \sprintf('$container->parameters[%s]', $this->doExport($name)); |
|
2025
|
|
|
} |
|
2026
|
|
|
|
|
2027
|
|
|
private function getServiceCall(string $id, ?Reference $reference = null): string |
|
2028
|
|
|
{ |
|
2029
|
|
|
while ($this->container->hasAlias($id)) { |
|
2030
|
|
|
$id = (string) $this->container->getAlias($id); |
|
2031
|
|
|
} |
|
2032
|
|
|
|
|
2033
|
|
|
if ('service_container' === $id) { |
|
2034
|
|
|
return '$container'; |
|
2035
|
|
|
} |
|
2036
|
|
|
|
|
2037
|
|
|
if ($this->container->hasDefinition($id) && $definition = $this->container->getDefinition($id)) { |
|
2038
|
|
|
if ($definition->isSynthetic()) { |
|
2039
|
|
|
$code = \sprintf('$container->get(%s%s)', $this->doExport($id), null !== $reference ? ', '.$reference->getInvalidBehavior() : ''); |
|
2040
|
|
|
} elseif (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) { |
|
2041
|
|
|
$code = 'null'; |
|
2042
|
|
|
if (!$definition->isShared()) { |
|
2043
|
|
|
return $code; |
|
2044
|
|
|
} |
|
2045
|
|
|
} elseif ($this->isTrivialInstance($definition)) { |
|
2046
|
|
|
if ($definition->hasErrors() && $e = $definition->getErrors()) { |
|
2047
|
|
|
return \sprintf('throw new RuntimeException(%s)', $this->export(reset($e))); |
|
2048
|
|
|
} |
|
2049
|
|
|
$code = $this->addNewInstance($definition, '', $id); |
|
2050
|
|
|
if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) { |
|
2051
|
|
|
return \sprintf('($container->%s[%s] ??= %s)', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code); |
|
2052
|
|
|
} |
|
2053
|
|
|
$code = "($code)"; |
|
2054
|
|
|
} else { |
|
2055
|
|
|
$code = $this->asFiles && !$this->inlineFactories && !$this->isHotPath($definition) ? "\$container->load('%s')" : 'self::%s($container)'; |
|
2056
|
|
|
$code = \sprintf($code, $this->generateMethodName($id)); |
|
2057
|
|
|
|
|
2058
|
|
|
if (!$definition->isShared()) { |
|
2059
|
|
|
$factory = \sprintf('$container->factories%s[%s]', $definition->isPublic() ? '' : "['service_container']", $this->doExport($id)); |
|
2060
|
|
|
$code = \sprintf('(isset(%s) ? %1$s($container) : %s)', $factory, $code); |
|
2061
|
|
|
} |
|
2062
|
|
|
} |
|
2063
|
|
|
if ($definition->isShared() && !isset($this->singleUsePrivateIds[$id])) { |
|
2064
|
|
|
$code = \sprintf('($container->%s[%s] ?? %s)', $definition->isPublic() ? 'services' : 'privates', $this->doExport($id), $code); |
|
2065
|
|
|
} |
|
2066
|
|
|
|
|
2067
|
|
|
return $code; |
|
2068
|
|
|
} |
|
2069
|
|
|
if (null !== $reference && ContainerInterface::IGNORE_ON_UNINITIALIZED_REFERENCE === $reference->getInvalidBehavior()) { |
|
2070
|
|
|
return 'null'; |
|
2071
|
|
|
} |
|
2072
|
|
|
if (null !== $reference && ContainerInterface::EXCEPTION_ON_INVALID_REFERENCE < $reference->getInvalidBehavior()) { |
|
2073
|
|
|
$code = \sprintf('$container->get(%s, ContainerInterface::NULL_ON_INVALID_REFERENCE)', $this->doExport($id)); |
|
2074
|
|
|
} else { |
|
2075
|
|
|
$code = \sprintf('$container->get(%s)', $this->doExport($id)); |
|
2076
|
|
|
} |
|
2077
|
|
|
|
|
2078
|
|
|
return \sprintf('($container->services[%s] ?? %s)', $this->doExport($id), $code); |
|
2079
|
|
|
} |
|
2080
|
|
|
|
|
2081
|
|
|
/** |
|
2082
|
|
|
* Initializes the method names map to avoid conflicts with the Container methods. |
|
2083
|
|
|
*/ |
|
2084
|
|
|
private function initializeMethodNamesMap(string $class): void |
|
2085
|
|
|
{ |
|
2086
|
|
|
$this->serviceIdToMethodNameMap = []; |
|
2087
|
|
|
$this->usedMethodNames = []; |
|
2088
|
|
|
|
|
2089
|
|
|
if ($reflectionClass = $this->container->getReflectionClass($class)) { |
|
2090
|
|
|
foreach ($reflectionClass->getMethods() as $method) { |
|
2091
|
|
|
$this->usedMethodNames[strtolower($method->getName())] = true; |
|
2092
|
|
|
} |
|
2093
|
|
|
} |
|
2094
|
|
|
} |
|
2095
|
|
|
|
|
2096
|
|
|
/** |
|
2097
|
|
|
* @throws InvalidArgumentException |
|
2098
|
|
|
*/ |
|
2099
|
|
|
private function generateMethodName(string $id): string |
|
2100
|
|
|
{ |
|
2101
|
|
|
if (isset($this->serviceIdToMethodNameMap[$id])) { |
|
2102
|
|
|
return $this->serviceIdToMethodNameMap[$id]; |
|
2103
|
|
|
} |
|
2104
|
|
|
|
|
2105
|
|
|
$i = strrpos($id, '\\'); |
|
2106
|
|
|
$name = Container::camelize(false !== $i && isset($id[1 + $i]) ? substr($id, 1 + $i) : $id); |
|
2107
|
|
|
$name = preg_replace('/[^a-zA-Z0-9_\x7f-\xff]/', '', $name); |
|
2108
|
|
|
$methodName = 'get'.$name.'Service'; |
|
2109
|
|
|
$suffix = 1; |
|
2110
|
|
|
|
|
2111
|
|
|
while (isset($this->usedMethodNames[strtolower($methodName)])) { |
|
2112
|
|
|
++$suffix; |
|
2113
|
|
|
$methodName = 'get'.$name.$suffix.'Service'; |
|
2114
|
|
|
} |
|
2115
|
|
|
|
|
2116
|
|
|
$this->serviceIdToMethodNameMap[$id] = $methodName; |
|
2117
|
|
|
$this->usedMethodNames[strtolower($methodName)] = true; |
|
2118
|
|
|
|
|
2119
|
|
|
return $methodName; |
|
2120
|
|
|
} |
|
2121
|
|
|
|
|
2122
|
|
|
private function getNextVariableName(): string |
|
2123
|
|
|
{ |
|
2124
|
|
|
$firstChars = self::FIRST_CHARS; |
|
2125
|
|
|
$firstCharsLength = \strlen($firstChars); |
|
2126
|
|
|
$nonFirstChars = self::NON_FIRST_CHARS; |
|
2127
|
|
|
$nonFirstCharsLength = \strlen($nonFirstChars); |
|
2128
|
|
|
|
|
2129
|
|
|
while (true) { |
|
2130
|
|
|
$name = ''; |
|
2131
|
|
|
$i = $this->variableCount; |
|
2132
|
|
|
$name .= $firstChars[$i % $firstCharsLength]; |
|
2133
|
|
|
$i = (int) ($i / $firstCharsLength); |
|
2134
|
|
|
|
|
2135
|
|
|
while ($i > 0) { |
|
2136
|
|
|
--$i; |
|
2137
|
|
|
$name .= $nonFirstChars[$i % $nonFirstCharsLength]; |
|
2138
|
|
|
$i = (int) ($i / $nonFirstCharsLength); |
|
2139
|
|
|
} |
|
2140
|
|
|
|
|
2141
|
|
|
++$this->variableCount; |
|
2142
|
|
|
|
|
2143
|
|
|
// check that the name is not reserved |
|
2144
|
|
|
if (\in_array($name, $this->reservedVariables, true)) { |
|
2145
|
|
|
continue; |
|
2146
|
|
|
} |
|
2147
|
|
|
|
|
2148
|
|
|
return $name; |
|
2149
|
|
|
} |
|
|
|
|
|
|
2150
|
|
|
} |
|
2151
|
|
|
|
|
2152
|
|
|
private function getExpressionLanguage(): ExpressionLanguage |
|
2153
|
|
|
{ |
|
2154
|
|
|
if (!isset($this->expressionLanguage)) { |
|
2155
|
|
|
if (!class_exists(\Symfony\Component\ExpressionLanguage\ExpressionLanguage::class)) { |
|
2156
|
|
|
throw new LogicException('Unable to use expressions as the Symfony ExpressionLanguage component is not installed. Try running "composer require symfony/expression-language".'); |
|
2157
|
|
|
} |
|
2158
|
|
|
$providers = $this->container->getExpressionLanguageProviders(); |
|
2159
|
|
|
$this->expressionLanguage = new ExpressionLanguage(null, $providers, function ($arg) { |
|
2160
|
|
|
$id = '""' === substr_replace($arg, '', 1, -1) ? stripcslashes(substr($arg, 1, -1)) : null; |
|
2161
|
|
|
|
|
2162
|
|
|
if (null !== $id && ($this->container->hasAlias($id) || $this->container->hasDefinition($id))) { |
|
2163
|
|
|
return $this->getServiceCall($id); |
|
2164
|
|
|
} |
|
2165
|
|
|
|
|
2166
|
|
|
return \sprintf('$container->get(%s)', $arg); |
|
2167
|
|
|
}); |
|
2168
|
|
|
|
|
2169
|
|
|
if ($this->container->isTrackingResources()) { |
|
2170
|
|
|
foreach ($providers as $provider) { |
|
2171
|
|
|
$this->container->addObjectResource($provider); |
|
2172
|
|
|
} |
|
2173
|
|
|
} |
|
2174
|
|
|
} |
|
2175
|
|
|
|
|
2176
|
|
|
return $this->expressionLanguage; |
|
2177
|
|
|
} |
|
2178
|
|
|
|
|
2179
|
|
|
private function isHotPath(Definition $definition): bool |
|
2180
|
|
|
{ |
|
2181
|
|
|
return $this->hotPathTag && $definition->hasTag($this->hotPathTag) && !$definition->isDeprecated(); |
|
2182
|
|
|
} |
|
2183
|
|
|
|
|
2184
|
|
|
private function isSingleUsePrivateNode(ServiceReferenceGraphNode $node): bool |
|
2185
|
|
|
{ |
|
2186
|
|
|
if ($node->getValue()->isPublic()) { |
|
2187
|
|
|
return false; |
|
2188
|
|
|
} |
|
2189
|
|
|
$ids = []; |
|
2190
|
|
|
foreach ($node->getInEdges() as $edge) { |
|
2191
|
|
|
if (!$value = $edge->getSourceNode()->getValue()) { |
|
2192
|
|
|
continue; |
|
2193
|
|
|
} |
|
2194
|
|
|
if ($edge->isLazy() || !$value instanceof Definition || !$value->isShared()) { |
|
2195
|
|
|
return false; |
|
2196
|
|
|
} |
|
2197
|
|
|
|
|
2198
|
|
|
// When the source node is a proxy or ghost, it will construct its references only when the node itself is initialized. |
|
2199
|
|
|
// Since the node can be cloned before being fully initialized, we do not know how often its references are used. |
|
2200
|
|
|
if ($this->getProxyDumper()->isProxyCandidate($value)) { |
|
2201
|
|
|
return false; |
|
2202
|
|
|
} |
|
2203
|
|
|
$ids[$edge->getSourceNode()->getId()] = true; |
|
2204
|
|
|
} |
|
2205
|
|
|
|
|
2206
|
|
|
return 1 === \count($ids); |
|
2207
|
|
|
} |
|
2208
|
|
|
|
|
2209
|
|
|
private function export(mixed $value): mixed |
|
2210
|
|
|
{ |
|
2211
|
|
|
if (null !== $this->targetDirRegex && \is_string($value) && preg_match($this->targetDirRegex, $value, $matches, \PREG_OFFSET_CAPTURE)) { |
|
2212
|
|
|
$suffix = $matches[0][1] + \strlen($matches[0][0]); |
|
2213
|
|
|
$matches[0][1] += \strlen($matches[1][0]); |
|
2214
|
|
|
$prefix = $matches[0][1] ? $this->doExport(substr($value, 0, $matches[0][1]), true).'.' : ''; |
|
2215
|
|
|
|
|
2216
|
|
|
if ('\\' === \DIRECTORY_SEPARATOR && isset($value[$suffix])) { |
|
2217
|
|
|
$cookie = '\\'.random_int(100000, \PHP_INT_MAX); |
|
2218
|
|
|
$suffix = '.'.$this->doExport(str_replace('\\', $cookie, substr($value, $suffix)), true); |
|
2219
|
|
|
$suffix = str_replace('\\'.$cookie, "'.\\DIRECTORY_SEPARATOR.'", $suffix); |
|
2220
|
|
|
} else { |
|
2221
|
|
|
$suffix = isset($value[$suffix]) ? '.'.$this->doExport(substr($value, $suffix), true) : ''; |
|
2222
|
|
|
} |
|
2223
|
|
|
|
|
2224
|
|
|
$dirname = $this->asFiles ? '$container->containerDir' : '__DIR__'; |
|
2225
|
|
|
$offset = 2 + $this->targetDirMaxMatches - \count($matches); |
|
2226
|
|
|
|
|
2227
|
|
|
if (0 < $offset) { |
|
2228
|
|
|
$dirname = \sprintf('\dirname(__DIR__, %d)', $offset + (int) $this->asFiles); |
|
2229
|
|
|
} elseif ($this->asFiles) { |
|
2230
|
|
|
$dirname = "\$container->targetDir.''"; // empty string concatenation on purpose |
|
2231
|
|
|
} |
|
2232
|
|
|
|
|
2233
|
|
|
if ($prefix || $suffix) { |
|
2234
|
|
|
return \sprintf('(%s%s%s)', $prefix, $dirname, $suffix); |
|
2235
|
|
|
} |
|
2236
|
|
|
|
|
2237
|
|
|
return $dirname; |
|
2238
|
|
|
} |
|
2239
|
|
|
|
|
2240
|
|
|
return $this->doExport($value, true); |
|
2241
|
|
|
} |
|
2242
|
|
|
|
|
2243
|
|
|
private function doExport(mixed $value, bool $resolveEnv = false): mixed |
|
2244
|
|
|
{ |
|
2245
|
|
|
$shouldCacheValue = $resolveEnv && \is_string($value); |
|
2246
|
|
|
if ($shouldCacheValue && isset($this->exportedVariables[$value])) { |
|
2247
|
|
|
return $this->exportedVariables[$value]; |
|
2248
|
|
|
} |
|
2249
|
|
|
if (\is_string($value) && str_contains($value, "\n")) { |
|
2250
|
|
|
$cleanParts = explode("\n", $value); |
|
2251
|
|
|
$cleanParts = array_map(fn ($part) => var_export($part, true), $cleanParts); |
|
2252
|
|
|
$export = implode('."\n".', $cleanParts); |
|
2253
|
|
|
} else { |
|
2254
|
|
|
$export = var_export($value, true); |
|
2255
|
|
|
} |
|
2256
|
|
|
|
|
2257
|
|
|
if ($resolveEnv && "'" === $export[0] && $export !== $resolvedExport = $this->container->resolveEnvPlaceholders($export, "'.\$container->getEnv('string:%s').'")) { |
|
2258
|
|
|
$export = $resolvedExport; |
|
2259
|
|
|
if (str_ends_with($export, ".''")) { |
|
2260
|
|
|
$export = substr($export, 0, -3); |
|
2261
|
|
|
if ("'" === $export[1]) { |
|
2262
|
|
|
$export = substr_replace($export, '', 23, 7); |
|
2263
|
|
|
} |
|
2264
|
|
|
} |
|
2265
|
|
|
if ("'" === $export[1]) { |
|
2266
|
|
|
$export = substr($export, 3); |
|
|
|
|
|
|
2267
|
|
|
} |
|
2268
|
|
|
} |
|
2269
|
|
|
|
|
2270
|
|
|
if ($shouldCacheValue) { |
|
2271
|
|
|
$this->exportedVariables[$value] = $export; |
|
2272
|
|
|
} |
|
2273
|
|
|
|
|
2274
|
|
|
return $export; |
|
2275
|
|
|
} |
|
2276
|
|
|
|
|
2277
|
|
|
private function getAutoloadFile(): ?string |
|
2278
|
|
|
{ |
|
2279
|
|
|
$file = null; |
|
2280
|
|
|
|
|
2281
|
|
|
foreach (spl_autoload_functions() as $autoloader) { |
|
2282
|
|
|
if (!\is_array($autoloader)) { |
|
2283
|
|
|
continue; |
|
2284
|
|
|
} |
|
2285
|
|
|
|
|
2286
|
|
|
if ($autoloader[0] instanceof DebugClassLoader) { |
|
2287
|
|
|
$autoloader = $autoloader[0]->getClassLoader(); |
|
2288
|
|
|
} |
|
2289
|
|
|
|
|
2290
|
|
|
if (!\is_array($autoloader) || !$autoloader[0] instanceof ClassLoader || !$autoloader[0]->findFile(__CLASS__)) { |
|
2291
|
|
|
continue; |
|
2292
|
|
|
} |
|
2293
|
|
|
|
|
2294
|
|
|
foreach (get_declared_classes() as $class) { |
|
2295
|
|
|
if (str_starts_with($class, 'ComposerAutoloaderInit') && $class::getLoader() === $autoloader[0]) { |
|
2296
|
|
|
$file = \dirname((new \ReflectionClass($class))->getFileName(), 2).'/autoload.php'; |
|
2297
|
|
|
|
|
2298
|
|
|
if (null !== $this->targetDirRegex && preg_match($this->targetDirRegex.'A', $file)) { |
|
2299
|
|
|
return $file; |
|
2300
|
|
|
} |
|
2301
|
|
|
} |
|
2302
|
|
|
} |
|
2303
|
|
|
} |
|
2304
|
|
|
|
|
2305
|
|
|
return $file; |
|
2306
|
|
|
} |
|
2307
|
|
|
|
|
2308
|
|
|
private function getClasses(Definition $definition, string $id): array |
|
2309
|
|
|
{ |
|
2310
|
|
|
$classes = []; |
|
2311
|
|
|
|
|
2312
|
|
|
while ($definition instanceof Definition) { |
|
2313
|
|
|
foreach ($definition->getTag($this->preloadTags[0]) as $tag) { |
|
2314
|
|
|
if (!isset($tag['class'])) { |
|
2315
|
|
|
throw new InvalidArgumentException(\sprintf('Missing attribute "class" on tag "%s" for service "%s".', $this->preloadTags[0], $id)); |
|
2316
|
|
|
} |
|
2317
|
|
|
|
|
2318
|
|
|
$classes[] = trim($tag['class'], '\\'); |
|
2319
|
|
|
} |
|
2320
|
|
|
|
|
2321
|
|
|
if ($class = $definition->getClass()) { |
|
2322
|
|
|
$classes[] = trim($class, '\\'); |
|
2323
|
|
|
} |
|
2324
|
|
|
$factory = $definition->getFactory(); |
|
2325
|
|
|
|
|
2326
|
|
|
if (\is_string($factory) && !str_starts_with($factory, '@=') && str_contains($factory, '::')) { |
|
2327
|
|
|
$factory = explode('::', $factory); |
|
2328
|
|
|
} |
|
2329
|
|
|
|
|
2330
|
|
|
if (!\is_array($factory)) { |
|
2331
|
|
|
$definition = $factory; |
|
2332
|
|
|
continue; |
|
2333
|
|
|
} |
|
2334
|
|
|
|
|
2335
|
|
|
$definition = $factory[0] ?? null; |
|
2336
|
|
|
|
|
2337
|
|
|
if (\is_string($definition)) { |
|
2338
|
|
|
$classes[] = trim($factory[0], '\\'); |
|
2339
|
|
|
} |
|
2340
|
|
|
} |
|
2341
|
|
|
|
|
2342
|
|
|
return $classes; |
|
2343
|
|
|
} |
|
2344
|
|
|
|
|
2345
|
|
|
private function isProxyCandidate(Definition $definition, ?bool &$asGhostObject, string $id): ?Definition |
|
2346
|
|
|
{ |
|
2347
|
|
|
$asGhostObject = false; |
|
2348
|
|
|
|
|
2349
|
|
|
if (['Closure', 'fromCallable'] === $definition->getFactory()) { |
|
2350
|
|
|
return null; |
|
2351
|
|
|
} |
|
2352
|
|
|
|
|
2353
|
|
|
if (!$definition->isLazy() || !$this->hasProxyDumper) { |
|
2354
|
|
|
return null; |
|
2355
|
|
|
} |
|
2356
|
|
|
|
|
2357
|
|
|
return $this->getProxyDumper()->isProxyCandidate($definition, $asGhostObject, $id) ? $definition : null; |
|
2358
|
|
|
} |
|
2359
|
|
|
|
|
2360
|
|
|
/** |
|
2361
|
|
|
* Removes comments from a PHP source string. |
|
2362
|
|
|
* |
|
2363
|
|
|
* We don't use the PHP php_strip_whitespace() function |
|
2364
|
|
|
* as we want the content to be readable and well-formatted. |
|
2365
|
|
|
*/ |
|
2366
|
|
|
private static function stripComments(string $source): string |
|
2367
|
|
|
{ |
|
2368
|
|
|
if (!\function_exists('token_get_all')) { |
|
2369
|
|
|
return $source; |
|
2370
|
|
|
} |
|
2371
|
|
|
|
|
2372
|
|
|
$rawChunk = ''; |
|
2373
|
|
|
$output = ''; |
|
2374
|
|
|
$tokens = token_get_all($source); |
|
2375
|
|
|
$ignoreSpace = false; |
|
2376
|
|
|
for ($i = 0; isset($tokens[$i]); ++$i) { |
|
2377
|
|
|
$token = $tokens[$i]; |
|
2378
|
|
|
if (!isset($token[1]) || 'b"' === $token) { |
|
2379
|
|
|
$rawChunk .= $token; |
|
2380
|
|
|
} elseif (\T_START_HEREDOC === $token[0]) { |
|
2381
|
|
|
$output .= $rawChunk.$token[1]; |
|
2382
|
|
|
do { |
|
2383
|
|
|
$token = $tokens[++$i]; |
|
2384
|
|
|
$output .= isset($token[1]) && 'b"' !== $token ? $token[1] : $token; |
|
2385
|
|
|
} while (\T_END_HEREDOC !== $token[0]); |
|
2386
|
|
|
$rawChunk = ''; |
|
2387
|
|
|
} elseif (\T_WHITESPACE === $token[0]) { |
|
2388
|
|
|
if ($ignoreSpace) { |
|
2389
|
|
|
$ignoreSpace = false; |
|
2390
|
|
|
|
|
2391
|
|
|
continue; |
|
2392
|
|
|
} |
|
2393
|
|
|
|
|
2394
|
|
|
// replace multiple new lines with a single newline |
|
2395
|
|
|
$rawChunk .= preg_replace(['/\n{2,}/S'], "\n", $token[1]); |
|
2396
|
|
|
} elseif (\in_array($token[0], [\T_COMMENT, \T_DOC_COMMENT])) { |
|
2397
|
|
|
if (!\in_array($rawChunk[\strlen($rawChunk) - 1], [' ', "\n", "\r", "\t"], true)) { |
|
2398
|
|
|
$rawChunk .= ' '; |
|
2399
|
|
|
} |
|
2400
|
|
|
$ignoreSpace = true; |
|
2401
|
|
|
} else { |
|
2402
|
|
|
$rawChunk .= $token[1]; |
|
2403
|
|
|
|
|
2404
|
|
|
// The PHP-open tag already has a new-line |
|
2405
|
|
|
if (\T_OPEN_TAG === $token[0]) { |
|
2406
|
|
|
$ignoreSpace = true; |
|
2407
|
|
|
} else { |
|
2408
|
|
|
$ignoreSpace = false; |
|
2409
|
|
|
} |
|
2410
|
|
|
} |
|
2411
|
|
|
} |
|
2412
|
|
|
|
|
2413
|
|
|
$output .= $rawChunk; |
|
2414
|
|
|
|
|
2415
|
|
|
unset($tokens, $rawChunk); |
|
2416
|
|
|
gc_mem_caches(); |
|
2417
|
|
|
|
|
2418
|
|
|
return $output; |
|
2419
|
|
|
} |
|
2420
|
|
|
} |
|
2421
|
|
|
|
Let?s assume that you have a directory layout like this:
. |-- OtherDir | |-- Bar.php | `-- Foo.php `-- SomeDir `-- Foo.phpand let?s assume the following content of
Bar.php:If both files
OtherDir/Foo.phpandSomeDir/Foo.phpare loaded in the same runtime, you will see a PHP error such as the following:PHP Fatal error: Cannot use SomeDir\Foo as Foo because the name is already in use in OtherDir/Foo.phpHowever, as
OtherDir/Foo.phpdoes not necessarily have to be loaded and the error is only triggered if it is loaded beforeOtherDir/Bar.php, this problem might go unnoticed for a while. In order to prevent this error from surfacing, you must import the namespace with a different alias: