8000 Remove non-autoloadable classes from preload · symfony/symfony@263bace · GitHub
[go: up one dir, main page]

Skip to content

Commit 263bace

Browse files
committed
Remove non-autoloadable classes from preload
1 parent 8d13f4b commit 263bace

File tree

6 files changed

+135
-7
lines changed

6 files changed

+135
-7
lines changed

src/Symfony/Component/DependencyInjection/Dumper/PhpDumper.php

Lines changed: 115 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,6 +90,7 @@ class PhpDumper extends Dumper
9090
private string $serviceLocatorTag;
9191
private array $exportedVariables = [];
9292
private string $baseClass;
93+
private array $preloadableCache = [];
9394
private ProxyDumper $proxyDumper;
9495

9596
/**
@@ -324,24 +325,31 @@ class %s extends {$options['class']}
324325
}
325326
326327
require $autoloadFile;
328+
if (\PHP_VERSION_ID >= 80100) {
327329
(require __DIR__.'/{$options['class']}.php')->set(\\Container{$hash}\\{$options['class']}::class, null);
328330
$preloadedFiles
331+
}
329332
\$classes = [];
333+
\$preloaded = [];
330334
331335
EOF;
332336

333337
foreach ($this->preload as $class) {
334338
if (!$class || str_contains($class, '$') || \in_array($class, ['int', 'float', 'string', 'bool', 'resource', 'object', 'array', 'null', 'callable', 'iterable', 'mixed', 'void'], true)) {
335339
continue;
336340
}
337-
if (!(class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) || (new \ReflectionClass($class))->isUserDefined()) {
341+
if ($this->isPreloadable($class)) {
338342
$code[$options['class'].'.preload.php'] .= sprintf("\$classes[] = '%s';\n", $class);
339343
}
340344
}
341345

346+
foreach ($this->collectNonPreloadable($this->preload) as $class) {
347+
$code[$options['class'].'.preload.php'] .= sprintf("\$preloaded['%s'] = true;\n", $class);
348+
}
349+
342350
$code[$options['class'].'.preload.php'] .= <<<'EOF'
343351
344-
$preloaded = Preloader::preload($classes);
352+
$preloaded = Preloader::preload($classes, $preloaded);
345353

346354
EOF;
347355
}
@@ -397,6 +405,111 @@ class %s extends {$options['class']}
397405
return $code;
398406
}
399407

408+
private function isPreloadable(string $class): ?bool
409+
{
410+
if (array_key_exists($class, $this->preloadableCache)) {
411+
return $this->preloadableCache[$class];
412+
}
413+
414+
$this->preloadableCache[$class] = true; // prevent recursion
415+
if (in_array($class, ['self', 'static', 'parent'], true)) {
416+
return $this->preloadableCache[$class] = null;
417+
}
418+
419+
try {
420+
if (!class_exists($class) && !interface_exists($class) && !trait_exists($class)) {
421+
return $this->preloadableCache[$class] = false;
422+
}
423+
} catch (\Error $e) {
424+
return $this->preloadableCache[$class] = false;
425+
}
426+
427+
$r = new \ReflectionClass($class);
428+
if (!$r->isUserDefined()) {
429+
return $this->preloadableCache[$class] = null;
430+
}
431+
432+
// Prior to PHP 8.1, typehinted properties have to be auto-loadable.
433+
if (\PHP_VERSION_ID >= 80100) {
434+
return $this->preloadableCache[$class] = true;
435+
}
436+
437+
// Before PHP 7.4, user can not define typehinted properties
438+
if (\PHP_VERSION_ID >= 70400) {
439+
foreach ($r->getProperties() as $p) {
440+
if (!$this->isPreloadableType($p->getType())) {
441+
// do not return to warm the preloadableCache
442+
$this->preloadableCache[$class] = false;
443+
}
444+
}
445+
}
446+
447+
// code bellow does not prevent the class to be preloaded, but is here to warm the preloadableCache
448+
foreach ($r->getMethods(\ReflectionMethod::IS_PUBLIC) as $m) {
449+
foreach ($m->getParameters() as $p) {
450+
if ($p->isDefaultValueAvailable() && $p->isDefaultValueConstant()) {
451+
$c = $p->getDefaultValueConstantName();
452+
453+
if ($i = strpos($c, '::')) {
454+
$c = substr($c, 0, $i);
455+
if (in_array($c, ['self', 'static', 'parent'], true)) {
456+
continue;
457+
}
458+
// warm the preloadableCache
459+
$this->isPreloadable($c);
460+
}
461+
}
462+
$this->isPreloadableType($p->getType());
463+
}
464+
$this->isPreloadableType($m->getReturnType());
465+
}
466+
467+
return $this->preloadableCache[$class];
468+
}
469+
470+
private function isPreloadableType(?\ReflectionType $t): bool
471+
{
472+
if (!$t) {
473+
return true;
474+
}
475+
476+
$result = true;
477+
foreach (($t instanceof \ReflectionUnionType || $t instanceof \ReflectionIntersectionType) ? $t->getTypes() : [$t] as $t) {
478+
if (!$t instanceof \ReflectionNamedType || $t->isBuiltin()) {
479+
continue;
480+
}
481+
if (false === $this->isPreloadable($t->getName())) {
482+
$result = false;
483+
}
484+
}
485+
486+
return $result;
487+
}
488+
489+
private function collectNonPreloadable(array $seed): array
490+
{
491+
$classes = $seed;
492+
$prev = [];
493+
while ($prev !== $classes) {
494+
$prev = $classes;
495+
foreach ($classes as $c) {
496+
// warm preloadableCache
497+
$this->isPreloadable($c);
498+
}
499+
$classes = array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits());
500+
}
501+
$classes = [];
502+
foreach ($this->preloadableCache as $class => $preloadable) {
503+
if ($preloadable === false) {
504+
if (class_exists($class, false) || interface_exists($class, false) || trait_exists($class, false)) {
505+
$classes[] = $class;
506+
}
507+
}
508+
}
509+
510+
return $classes;
511+
}
512+
400513
/**
401514
* Retrieves the currently set proxy dumper or instantiates one.
402515
*/

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services10_as_files.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -131,14 +131,17 @@ if (in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) {
131131
}
132132

133133
require dirname(__DIR__, %d).'%svendor/autoload.php';
134+
if (\PHP_VERSION_ID >= 80100) {
134135
(require __DIR__.'/ProjectServiceContainer.php')->set(\Container%s\ProjectServiceContainer::class, null);
135136
require __DIR__.'/Container%s/getClosureService.php';
137+
}
136138

137139
$classes = [];
140+
$preloaded = [];
138141
$classes[] = 'FooClass';
139142
$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface';
140143

141-
$preloaded = Preloader::preload($classes);
144+
$preloaded = Preloader::preload($classes, $preloaded);
142145

143146
[ProjectServiceContainer.php] => <?php
144147

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_as_files.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -897,6 +897,7 @@ if (in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) {
897897
}
898898

899899
require dirname(__DIR__, %d).'%svendor/autoload.php';
900+
if (\PHP_VERSION_ID >= 80100) {
900901
(require __DIR__.'/ProjectServiceContainer.php')->set(\Container%s\ProjectServiceContainer::class, null);
901902
require __DIR__.'/Container%s/getThrowingOneService.php';
902903
require __DIR__.'/Container%s/getTaggedIteratorService.php';
@@ -922,8 +923,10 @@ require __DIR__.'/Container%s/getBazService.php';
922923
require __DIR__.'/Container%s/getBar23Service.php';
923924
require __DIR__.'/Container%s/getBAR22Service.php';
924925
require __DIR__.'/Container%s/getBAR2Service.php';
926+
}
925927

926928
$classes = [];
929+
$preloaded = [];
927930
$classes[] = 'Bar\FooClass';
928931
$classes[] = 'Baz';
929932
$classes[] = 'ConfClass';
@@ -938,7 +941,7 @@ $classes[] = 'Some\Sidekick2';
938941
$classes[] = 'Request';
939942
$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface';
940943

941-
$preloaded = Preloader::preload($classes);
944+
$preloaded = Preloader::preload($classes, $preloaded);
942945

943946
[ProjectServiceContainer.php] => <?php
944947

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_inlined_factories.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -552,9 +552,12 @@ if (in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) {
552552
}
553553

554554
require dirname(__DIR__, %d).'%svendor/autoload.php';
555+
if (\PHP_VERSION_ID >= 80100) {
555556
(require __DIR__.'/ProjectServiceContainer.php')->set(\Container%s\ProjectServiceContainer::class, null);
557+
}
556558

557559
$classes = [];
560+
$preloaded = [];
558561
$classes[] = 'Bar\FooClass';
559562
$classes[] = 'Baz';
560563
$classes[] = 'ConfClass';
@@ -569,7 +572,7 @@ $classes[] = 'Some\Sidekick2';
569572
$classes[] = 'Request';
570573
$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface';
571574

572-
$preloaded = Preloader::preload($classes);
575+
$preloaded = Preloader::preload($classes, $preloaded);
573576

574577
[ProjectServiceContainer.php] => <?php
575578

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services9_lazy_inlined_factories.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -169,14 +169,17 @@ if (in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) {
169169
}
170170

171171
require dirname(__DIR__, %d).'%svendor/autoload.php';
172+
if (\PHP_VERSION_ID >= 80100) {
172173
(require __DIR__.'/ProjectServiceContainer.php')->set(\Container%s\ProjectServiceContainer::class, null);
174+
}
173175

174176
$classes = [];
177+
$preloaded = [];
175178
$classes[] = 'Bar\FooClass';
176179
$classes[] = 'Bar\FooLazyClass';
177180
$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface';
178181

179-
$preloaded = Preloader::preload($classes);
182+
$preloaded = Preloader::preload($classes, $preloaded);
180183

181184
[ProjectServiceContainer.php] => <?php
182185

src/Symfony/Component/DependencyInjection/Tests/Fixtures/php/services_non_shared_lazy_as_files.txt

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -130,15 +130,18 @@ if (in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) {
130130
}
131131

132132
require dirname(__DIR__, %d).'%svendor/autoload.php';
133+
if (\PHP_VERSION_ID >= 80100) {
133134
(require __DIR__.'/ProjectServiceContainer.php')->set(\Container%s\ProjectServiceContainer::class, null);
134135
require __DIR__.'/Container%s/proxy.php';
135136
require __DIR__.'/Container%s/getNonSharedFooService.php';
137+
}
136138

137139
$classes = [];
140+
$preloaded = [];
138141
$classes[] = 'Bar\FooLazyClass';
139142
$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface';
140143

141-
$preloaded = Preloader::preload($classes);
144+
$preloaded = Preloader::preload($classes, $preloaded);
142145

143146
[ProjectServiceContainer.php] => <?php
144147

0 commit comments

Comments
 (0)
0