8000 [DI] generate preload.php file for PHP 7.4 in cache folder · symfony/symfony@517bd3e · GitHub
[go: up one dir, main page]

Skip to content

Commit 517bd3e

Browse files
[DI] generate preload.php file for PHP 7.4 in cache folder
1 parent d6ac452 commit 517bd3e

8 files changed

+171
-30
lines changed

src/Symfony/Component/DependencyInjection/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ CHANGELOG
44
4.4.0
55
-----
66

7+
* added support for opcache.preload by generating a preloading script in the cache folder
78
* added support for dumping the container in one file instead of many files
89
* deprecated support for short factories and short configurators in Yaml
910
* deprecated `tagged` in favor of `tagged_iterator`

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

Lines changed: 49 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,7 @@ class PhpDumper extends Dumper
8282
private $locatedIds = [];
8383
private $serviceLocatorTag;
8484
private $exportedVariables = [];
85+
private $baseClass;
8586

8687
/**
8788
* @var ProxyDumper
@@ -151,11 +152,11 @@ public function dump(array $options = [])
151152

152153
if (0 !== strpos($baseClass = $options['base_class'], '\\') && 'Container' !== $baseClass) {
153154
$baseClass = sprintf('%s\%s', $options['namespace'] ? '\\'.$options['namespace'] : '', $baseClass);
154-
$baseClassWithNamespace = $baseClass;
155+
$this->baseClass = $baseClass;
155156
} elseif ('Container' === $baseClass) {
156-
$baseClassWithNamespace = Container::class;
157+
$this->baseClass = Container::class;
157158
} else {
158-
$baseClassWithNamespace = $baseClass;
159+
$this->baseClass = $baseClass;
159160
}
160161

161162
$this->initializeMethodNamesMap('Container' === $baseClass ? Container::class : $baseClass);
@@ -222,7 +223,7 @@ public function dump(array $options = [])
222223
$proxyClasses = $this->inlineFactories ? $this->generateProxyClasses() : null;
223224

224225
$code =
225-
$this->startClass($options['class'], $baseClass, $baseClassWithNamespace).
226+
$this->startClass($options['class'], $baseClass, $preload).
226227
$this->addServices($services).
227228
$this->addDeprecatedAliases().
228229
$this->addDefaultParametersMethod()
@@ -296,6 +297,33 @@ public function dump(array $options = [])
296297
$time = $options['build_time'];
297298
$id = hash('crc32', $hash.$time);
298299

300+
if ($preload) {
301+
$code[$options['class'].'.preload.php'] = <<<EOF
302+
<?php
303+
304+
// This file has been auto-generated by the Symfony Dependency Injection Component
305+
// You can reference it in the "opcache.preload" php.ini setting on PHP >= 7.4 when preloading is desired
306+
307+
use Symfony\Component\DependencyInjection\Dumper\Preloader;
308+
309+
require dirname(__DIR__, 3).'/vendor/autoload.php';
310+
require __DIR__.'/Container{$hash}/{$options['class']}.php';
311+
312+
\$classes = [];
313+
314+
EOF;
315+
316+
foreach ($preload as $class) {
317+
$code[$options['class'].'.preload.php'] .= sprintf("\$classes[] = '%s';\n", $class);
318+
}
319+
320+
$code[$options['class'].'.preload.php'] .= <<<'EOF'
321+
322+
Preloader::preload($classes);
323+
324+
EOF;
325+
}
326+
299327
$code[$options['class'].'.php'] = <<<EOF
300328
<?php
301329
{$namespaceLine}
@@ -426,14 +454,16 @@ private function collectLineage(string $class, array &$lineage)
426454
if (!$r = $this->container->getReflectionClass($class, false)) {
427455
return;
428456
}
429-
if ($this->container instanceof $class) {
457+
if (is_a($class, $this->baseClass, true)) {
430458
return;
431459
}
432460
$file = $r->getFileName();
433461
if (!$file || $this->doExport($file) === $exportedFile = $this->export($file)) {
434462
return;
435463
}
436464

465+
$lineage[$class] = substr($exportedFile, 1, -1);
466+
437467
if ($parent = $r->getParentClass()) {
438468
$this->collectLineage($parent->name, $lineage);
439469
}
@@ -446,6 +476,7 @@ private function collectLineage(string $class, array &$lineage)
446476
$this->collectLineage($parent->name, $lineage);
447477
}
448478

479+
unset($lineage[$class]);
449480
$lineage[$class] = substr($exportedFile, 1, -1);
450481
}
451482

@@ -522,13 +553,17 @@ private function addServiceInclude(string $cId, Definition $definition): string
522553
}
523554

524555
foreach (array_diff_key(array_flip($lineage), $this->inlinedRequires) as $file => $class) {
556+
$file = preg_replace('#^\\$this->targetDirs\[(\d++)\]#', sprintf('\dirname(__DIR__, %d + $1)', $this->asFiles), $file);
525557
$code .= sprintf(" include_once %s;\n", $file);
526558
}
527559
}
528560

529561
foreach ($this->inlinedDefinitions as $def) {
530562
if ($file = $def->getFile()) {
531-
$code .= sprintf(" include_once %s;\n", $this->dumpValue($file));
563+
$file = $this->dumpValue($file);
564+
$file = '(' === $file[0] ? substr($file, 1, -1) : $file;
565+
$file = preg_replace('#^\\$this->targetDirs\[(\d++)\]#', sprintf('\dirname(__DIR__, %d + $1)', $this->asFiles), $file);
566+
$code .= sprintf(" include_once %s;\n", $file);
532567
}
533568
}
534569

@@ -1016,7 +1051,7 @@ private function addNewInstance(Definition $definition, string $return = '', str
10161051
return $return.sprintf('new %s(%s)', $this->dumpLiteralClass($this->dumpValue($class)), implode(', ', $arguments)).$tail;
10171052
}
10181053

1019-
private function startClass(string $class, string $baseClass, string $baseClassWithNamespace): string
1054+
private function startClass(string $class, string $baseClass, ?array &$preload): string
10201055
{
10211056
$namespaceLine = ! 10000 $this->asFiles && $this->namespace ? "\nnamespace {$this->namespace};\n" : '';
10221057

@@ -1064,8 +1099,8 @@ public function __construct()
10641099
$code .= " \$this->containerDir = \$containerDir;\n";
10651100
}
10661101

1067-
if (Container::class !== $baseClassWithNamespace) {
1068-
$r = $this->container->getReflectionClass($baseClassWithNamespace, false);
1102+
if (Container::class !== $this->baseClass) {
1103+
$r = $this->container->getReflectionClass($this->baseClass, false);
10691104
if (null !== $r
10701105
&& (null !== $constructor = $r->getConstructor())
10711106
&& 0 === $constructor->getNumberOfRequiredParameters()
@@ -1085,7 +1120,7 @@ public function __construct()
10851120
$code .= $this->addMethodMap();
10861121
$code .= $this->asFiles && !$this->inlineFactories ? $this->addFileMap() : '';
10871122
$code .= $this->addAliases();
1088-
$code .= $this->addInlineRequires();
1123+
$code .= $this->addInlineRequires($preload);
10891124
$code .= <<<EOF
10901125
}
10911126
@@ -1285,7 +1320,7 @@ protected function {$methodNameAlias}()
12851320
return $code;
12861321
}
12871322

1288-
private function addInlineRequires(): string
1323+
private function addInlineRequires(?array &$preload): string
12891324
{
12901325
if (!$this->hotPathTag || !$this->inlineRequires) {
12911326
return '';
@@ -1304,6 +1339,7 @@ private function addInlineRequires(): string
13041339

13051340
foreach ($inlinedDefinitions as $def) {
13061341
if (\is_string($class = \is_array($factory = $def->getFactory()) && \is_string($factory[0]) ? $factory[0] : $def->getClass())) {
1342+
$preload[$class] = $class;
13071343
$this->collectLineage($class, $lineage);
13081344
}
13091345
}
@@ -1314,11 +1350,12 @@ private function addInlineRequires(): string
13141350
foreach ($lineage as $file) {
13151351
if (!isset($this->inlinedRequires[$file])) {
13161352
$this->inlinedRequires[$file] = true;
1353+
$file = preg_replace('#^\\$this->targetDirs\[(\d++)\]#', sprintf('\dirname(__DIR__, %d + $1)', $this->asFiles), $file);
13171354
$code .= sprintf("\n include_once %s;", $file);
13181355
}
13191356
}
13201357

1321-
return $code ? sprintf("\n \$this->privates['service_container'] = function () {%s\n };\n", $code) : '';
1358+
return $code ? sprintf("\n \$this->privates['service_container'] = static function () {%s\n };\n", $code) : '';
13221359
}
13231360

13241361
private function addDefaultParametersMethod(): string
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
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+
/**
15+
* @author Nicolas Grekas <p@tchwork.com>
16+
*
17+
* @internal
18+
*/
19+
class Preloader
20+
{
21+
public static function preload(array $classes)
22+
{
23+
set_error_handler(function ($t, $m, $f, $l) {
24+
if (error_reporting() & $t) {
25+
if (__FILE__ !== $f) {
26+
throw new \ErrorException($m, 0, $t, $f, $l);
27+
}
28+
29+
throw new \ReflectionException($m);
30+
}
31+
});
32+
33+
$prev = [];
34+
$preloaded = [];
35+
36+
try {
37+
while ($prev !== $classes) {
38+
$prev = $classes;
39+
foreach ($classes as $c) {
40+
if (isset($preloaded[$c]) || in_array($c, ['self', 'static', 'parent'], true)) {
41+
$preloaded[$c] = true;
42+
self::doPreload($c);
43+
}
44+
}
45+
$classes = array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits());
46+
}
47+
} finally {
48+
restore_error_handler();
49+
}
50+
}
51+
52+
private static function doPreload(string $class)
53+
{
54+
try {
55+
$r = new \ReflectionClass($class);
56+
57+
if ($r->isInternal()) {
58+
return;
59+
}
60+
61+
$r->getConstants();
62+
$r->getDefaultProperties();
63+
64+
if (\PHP_VERSION_ID >= 70400) {
65+
foreach ($r->getProperties() as $p) {
66+
if (($t = $p->getType()) && !$t->isBuiltin()) {
67+
self::doPreload($t->getName());
68+
}
69+
}
70+
}
71+
72+
foreach ($r->getMethods() as $m) {
73+
foreach ($m->getParameters() as $p) {
74+
if ($p->isDefaultValueAvailable() && $p->isDefaultValueConstant()) {
75+
$c = $p->getDefaultValueConstantName();
76+
77+
if ($i = strpos($c, '::')) {
78+
self::doPreload(substr($c, 0, $i));
79+
}
80+
}
81+
82+
if (($t = $p->getType()) && !$t->isBuiltin()) {
83+
self::doPreload($t->getName());
84+
}
85+
}
86+
87+
if (($t = $m->getReturnType()) && !$t->isBuiltin()) {
88+
self::doPreload($t->getName());
89+
}
90+
}
91+
} catch (\ReflectionException $e) {
92+
// ignore missing classes
93+
}
94+
}
95+
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -265,7 +265,7 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException;
265265
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
266266
// Returns the public 'method_call1' shared service.
267267

268-
include_once ($this->targetDirs[0].'/Fixtures/includes/foo.php');
268+
include_once \dirname(__DIR__, 1 + 0).'/Fixtures/includes/foo.php';
269269

270270
$this->services['method_call1'] = $instance = new \Bar\FooClass();
271271

@@ -300,7 +300,7 @@ use Symfony\Component\DependencyInjection\Exception\RuntimeException;
300300
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.
301301
// Returns the public 'non_shared_foo' service.
302302

303-
include_once ($this->targetDirs[0].'/Fixtures/includes/foo.php');
303+
include_once \dirname(__DIR__, 1 + 0).'/Fixtures/includes/foo.php';
304304

305305
$this->factories['non_shared_foo'] = function () {
306306
return new \Bar\FooClass();

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

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ class ProjectServiceContainer extends Container
9090
'decorated' => 'decorator_service_with_name',
9191
];
9292

93-
$this->privates['service_container'] = function () {
94-
include_once $this->targetDirs[0].'/Fixtures/includes/foo.php';
93+
$this->privates['service_container'] = static function () {
94+
include_once \dirname(__DIR__, 1 + 0).'/Fixtures/includes/foo.php';
9595
};
9696
}
9797

@@ -287,7 +287,7 @@ class ProjectServiceContainer extends Container
287287
*/
288288
protected function getFoo_BazService()
289289
{
290-
include_once $this->targetDirs[0].'/Fixtures/includes/classes.php';
290+
include_once \dirname(__DIR__, 1 + 0).'/Fixtures/includes/classes.php';
291291

292292
$this->services['foo.baz'] = $instance = \BazClass::getInstance();
293293

@@ -331,7 +331,7 @@ class ProjectServiceContainer extends Container
331331
*/
332332
protected function getLazyContextService()
333333
{
334-
include_once $this->targetDirs[0].'/Fixtures/includes/classes.php';
334+
include_once \dirname(__DIR__, 1 + 0).'/Fixtures/includes/classes.php';
335335

336336
return $this->services['lazy_context'] = new \LazyContext(new RewindableGenerator(function () {
337337
yield 'k1' => ($this->services['foo.baz'] ?? $this->getFoo_BazService());
@@ -348,7 +348,7 @@ class ProjectServiceContainer extends Container
348348
*/
349349
protected function getLazyContextIgnoreInvalidRefService()
350350
{
351-
include_once $this->targetDirs[0].'/Fixtures/includes/classes.php';
351+
include_once \dirname(__DIR__, 1 + 0).'/Fixtures/includes/classes.php';
352352

353353
return $this->services['lazy_context_ignore_invalid_ref'] = new \LazyContext(new RewindableGenerator(function () {
354354
yield 0 => ($this->services['foo.baz'] ?? $this->getFoo_BazService());
@@ -364,7 +364,7 @@ class ProjectServiceContainer extends Container
364364
*/
365365
protected function getMethodCall1Service()
366366
{
367-
include_once ($this->targetDirs[0].'/Fixtures/includes/foo.php');
367+
include_once \dirname(__DIR__, 1 + 0).'/Fixtures/includes/foo.php';
368368

369369
$this->services['method_call1'] = $instance = new \Bar\FooClass();
370370

@@ -399,7 +399,7 @@ class ProjectServiceContainer extends Container
399399
*/
400400
protected function getNonSharedFooService()
401401
{
402-
include_once ($this->targetDirs[0].'/Fixtures/includes/foo.php');
402+
include_once \dirname(__DIR__, 1 + 0).'/Fixtures/includes/foo.php';
403403

404404
return new \Bar\FooClass();
405405
}
@@ -534,6 +534,14 @@ class ProjectServiceContainer extends Container
534534
}
535535
}
536536

537+
[ProjectServiceContainer.preload.php] => <?php
538+
%A
539+
540+
$classes = [];
541+
$classes[] = 'Bar\FooClass';
542+
543+
%A
544+
537545
[ProjectServiceContainer.php] => <?php
538546

539547
// This file has been auto-generated by the Symfony Dependency Injection Component for internal use.

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

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ class ProjectServiceContainer extends Container
9292
});
9393
}
9494

95-
include_once $this->targetDirs[0].'/Fixtures/includes/foo_lazy.php';
95+
include_once \dirname(__DIR__, 1 + 0).'/Fixtures/includes/foo_lazy.php';
9696

9797
return new \Bar\FooClass(new \Bar\FooLazyClass());
9898
}

0 commit comments

Comments
 (0)
0