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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy