Skip to content

Commit 4c87358

Browse files
committed
Remove non-autoloadable classes from preload
1 parent 0e56bf2 commit 4c87358

File tree

7 files changed

+136
-28
lines changed

7 files changed

+136
-28
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 $serviceLocatorTag;
9191
private $exportedVariables = [];
9292
private $baseClass;
93+
private $preloadableCache = [];
9394

9495
/**
9596
* @var ProxyDumper
@@ -325,24 +326,31 @@ class %s extends {$options['class']}
325326
}
326327
327328
require $autoloadFile;
329+
if (\PHP_VERSION_ID >= 80100 || \PHP_VERSION_ID < 70400) {
328330
require __DIR__.'/$preloadedFiles';
331+
}
329332
330333
\$classes = [];
334+
\$preloaded = [];
331335
332336
EOF;
333337

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

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

347355
EOF;
348356
}
@@ -398,6 +406,111 @@ class %s extends {$options['class']}
398406
return $code;
399407
}
400408

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

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

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ public static function append(string $file, array $list): void
3737
file_put_contents($file, sprintf("\n\$classes = [];\n%sPreloader::preload(\$classes);\n", implode('', $classes)), \FILE_APPEND);
3838
}
3939

40-
public static function preload(array $classes): void
40+
public static function preload(array $classes, array $preloaded = []): void
4141
{
4242
set_error_handler(function ($t, $m, $f, $l) {
4343
if (error_reporting() & $t) {
@@ -50,7 +50,6 @@ public static function preload(array $classes): void
5050
});
5151

5252
$prev = [];
53-
$preloaded = [];
5453

5554
try {
5655
while ($prev !== $classes) {

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

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -133,14 +133,16 @@ if (in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) {
133133
}
134134

135135
require dirname(__DIR__, %d).'%svendor/autoload.php';
136+
if (\PHP_VERSION_ID >= 80100 || \PHP_VERSION_ID < 70400) {
136137
require __DIR__.'/Container%s/ProjectServiceContainer.php';
137138
require __DIR__.'/Container%s/getClosureService.php';
139+
}
138140

139141
$classes = [];
140-
$classes[] = 'FooClass';
142+
$preloaded = [];
141143
$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface';
142144

143-
Preloader::preload($classes);
145+
Preloader::preload($classes, $preloaded);
144146

145147
[ProjectServiceContainer.php] => <?php
146148

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

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -907,6 +907,7 @@ if (in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) {
907907
}
908908

909909
require dirname(__DIR__, %d).'%svendor/autoload.php';
910+
if (\PHP_VERSION_ID >= 80100 || \PHP_VERSION_ID < 70400) {
910911
require __DIR__.'/Container%s/ProjectServiceContainer.php';
911912
require __DIR__.'/Container%s/getThrowingOneService.php';
912913
require __DIR__.'/Container%s/getTaggedIteratorService.php';
@@ -932,23 +933,16 @@ require __DIR__.'/Container%s/getBazService.php';
932933
require __DIR__.'/Container%s/getBar23Service.php';
933934
require __DIR__.'/Container%s/getBAR22Service.php';
934935
require __DIR__.'/Container%s/getBAR2Service.php';
936+
}
935937

936938
$classes = [];
939+
$preloaded = [];
937940
$classes[] = 'Bar\FooClass';
938-
$classes[] = 'Baz';
939-
$classes[] = 'ConfClass';
940-
$classes[] = 'Bar';
941941
$classes[] = 'BazClass';
942-
$classes[] = 'Foo';
943942
$classes[] = 'LazyContext';
944-
$classes[] = 'FooBarBaz';
945-
$classes[] = 'FactoryClass';
946-
$classes[] = 'Some\Sidekick1';
947-
$classes[] = 'Some\Sidekick2';
948-
$classes[] = 'Request';
949943
$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface';
950944

951-
Preloader::preload($classes);
945+
Preloader::preload($classes, $preloaded);
952946

953947
[ProjectServiceContainer.php] => <?php
954948

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

Lines changed: 4 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -562,24 +562,18 @@ if (in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) {
562562
}
563563

564564
require dirname(__DIR__, %d).'%svendor/autoload.php';
565+
if (\PHP_VERSION_ID >= 80100 || \PHP_VERSION_ID < 70400) {
565566
require __DIR__.'/Container%s/ProjectServiceContainer.php';
567+
}
566568

567569
$classes = [];
570+
$preloaded = [];
568571
$classes[] = 'Bar\FooClass';
569-
$classes[] = 'Baz';
570-
$classes[] = 'ConfClass';
571-
$classes[] = 'Bar';
572572
$classes[] = 'BazClass';
573-
$classes[] = 'Foo';
574573
$classes[] = 'LazyContext';
575-
$classes[] = 'FooBarBaz';
576-
$classes[] = 'FactoryClass';
577-
$classes[] = 'Some\Sidekick1';
578-
$classes[] = 'Some\Sidekick2';
579-
$classes[] = 'Request';
580574
$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface';
581575

582-
Preloader::preload($classes);
576+
Preloader::preload($classes, $preloaded);
583577

584578
[ProjectServiceContainer.php] => <?php
585579

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
@@ -179,14 +179,17 @@ if (in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) {
179179
}
180180

181181
require dirname(__DIR__, %d).'%svendor/autoload.php';
182+
if (\PHP_VERSION_ID >= 80100 || \PHP_VERSION_ID < 70400) {
182183
require __DIR__.'/Container%s/ProjectServiceContainer.php';
184+
}
183185

184186
$classes = [];
187+
$preloaded = [];
185188
$classes[] = 'Bar\FooClass';
186189
$classes[] = 'Bar\FooLazyClass';
187190
$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface';
188191

189-
Preloader::preload($classes);
192+
Preloader::preload($classes, $preloaded);
190193

191194
[ProjectServiceContainer.php] => <?php
192195

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
@@ -144,15 +144,18 @@ if (in_array(PHP_SAPI, ['cli', 'phpdbg'], true)) {
144144
}
145145

146146
require dirname(__DIR__, %d).'%svendor/autoload.php';
147+
if (\PHP_VERSION_ID >= 80100 || \PHP_VERSION_ID < 70400) {
147148
require __DIR__.'/Container%s/ProjectServiceContainer.php';
148149
require __DIR__.'/Container%s/proxy.php';
149150
require __DIR__.'/Container%s/getNonSharedFooService.php';
151+
}
150152

151153
$classes = [];
154+
$preloaded = [];
152155
$classes[] = 'Bar\FooLazyClass';
153156
$classes[] = 'Symfony\Component\DependencyInjection\ContainerInterface';
154157

155-
Preloader::preload($classes);
158+
Preloader::preload($classes, $preloaded);
156159

157160
[ProjectServiceContainer.php] => <?php
158161

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