feature #19276 [ClassLoader] Add ClassCollectionLoader::inline() to generate inlined-classes files (nicolas-grekas)

This PR was squashed before being merged into the 3.2-dev branch (closes #19276).

Discussion
----------

[ClassLoader] Add ClassCollectionLoader::inline() to generate inlined-classes files

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | no
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

Unfortunately, can't be tested because the method relies too much on side effects.
Coupled with https://github.com/sensiolabs/SensioDistributionBundle/pull/272, allows inlining `ClassCollectionLoader` itself into the `bootstrap.php.cache` file.

Commits
-------

88fdcea [ClassLoader] Add ClassCollectionLoader::inline() to generate inlined-classes files
This commit is contained in:
Fabien Potencier 2016-07-18 14:56:02 +02:00
commit 583a45d91f
13 changed files with 189 additions and 8 deletions

View File

@ -21,6 +21,13 @@ use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
*/
class ClassCacheCacheWarmer implements CacheWarmerInterface
{
private $declaredClasses;
public function __construct(array $declaredClasses = null)
{
$this->declaredClasses = $declaredClasses;
}
/**
* Warms up the cache.
*
@ -37,8 +44,9 @@ class ClassCacheCacheWarmer implements CacheWarmerInterface
if (file_exists($cacheDir.'/classes.php')) {
return;
}
$declared = null !== $this->declaredClasses ? $this->declaredClasses : array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits());
ClassCollectionLoader::load(include($classmap), $cacheDir, 'classes', false);
ClassCollectionLoader::inline(include($classmap), $cacheDir.'/classes.php', $declared);
}
/**

View File

@ -170,15 +170,23 @@ class FrameworkExtension extends Extension
}
$this->addClassesToCompile(array(
'Symfony\\Component\\Config\\ConfigCache',
'Symfony\\Component\\Config\\FileLocator',
'Symfony\\Component\\Debug\\ErrorHandler',
'Symfony\\Component\\DependencyInjection\\ContainerAwareInterface',
'Symfony\\Component\\DependencyInjection\\Container',
'Symfony\\Component\\EventDispatcher\\Event',
'Symfony\\Component\\EventDispatcher\\ContainerAwareEventDispatcher',
'Symfony\\Component\\HttpFoundation\\Response',
'Symfony\\Component\\HttpFoundation\\ResponseHeaderBag',
'Symfony\\Component\\HttpKernel\\EventListener\\ResponseListener',
'Symfony\\Component\\HttpKernel\\EventListener\\RouterListener',
'Symfony\\Component\\HttpKernel\\Bundle\\Bundle',
'Symfony\\Component\\HttpKernel\\Controller\\ControllerResolver',
'Symfony\\Component\\HttpKernel\\Controller\\ArgumentResolver',
'Symfony\\Component\\HttpKernel\\ControllerMetadata\\ArgumentMetadata',
@ -189,13 +197,18 @@ class FrameworkExtension extends Extension
'Symfony\\Component\\HttpKernel\\Event\\GetResponseEvent',
'Symfony\\Component\\HttpKernel\\Event\\GetResponseForControllerResultEvent',
'Symfony\\Component\\HttpKernel\\Event\\GetResponseForExceptionEvent',
'Symfony\\Component\\HttpKernel\\HttpKernel',
'Symfony\\Component\\HttpKernel\\KernelEvents',
'Symfony\\Component\\HttpKernel\\Config\\FileLocator',
'Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerNameParser',
'Symfony\\Bundle\\FrameworkBundle\\Controller\\ControllerResolver',
// Cannot be included because annotations will parse the big compiled class file
// 'Symfony\\Bundle\\FrameworkBundle\\Controller\\Controller',
// cannot be included as commands are discovered based on the path to this class via Reflection
// 'Symfony\\Bundle\\FrameworkBundle\\FrameworkBundle',
));
}

View File

@ -24,6 +24,17 @@
<service id="kernel.class_cache.cache_warmer" class="Symfony\Bundle\FrameworkBundle\CacheWarmer\ClassCacheCacheWarmer">
<tag name="kernel.cache_warmer" />
<argument type="collection">
<argument>Doctrine\Common\Annotations\AnnotationRegistry</argument>
<argument>Symfony\Component\HttpFoundation\ParameterBag</argument>
<argument>Symfony\Component\HttpFoundation\HeaderBag</argument>
<argument>Symfony\Component\HttpFoundation\FileBag</argument>
<argument>Symfony\Component\HttpFoundation\ServerBag</argument>
<argument>Symfony\Component\HttpFoundation\Request</argument>
<argument>Symfony\Component\HttpKernel\Kernel</argument>
<argument>Symfony\Component\ClassLoader\ClassCollectionLoader</argument>
<argument>Symfony\Component\ClassLoader\ApcClassLoader</argument>
</argument>
</service>
<service id="cache_clearer" class="Symfony\Component\HttpKernel\CacheClearer\ChainCacheClearer">

View File

@ -0,0 +1,48 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Bundle\FrameworkBundle\Tests\CacheWarmer;
use Symfony\Bundle\FrameworkBundle\CacheWarmer\ClassCacheCacheWarmer;
use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\DeclaredClass;
use Symfony\Bundle\FrameworkBundle\Tests\Fixtures\WarmedClass;
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
class ClassCacheCacheWarmerTest extends TestCase
{
public function testWithDeclaredClasses()
{
$this->assertTrue(class_exists(WarmedClass::class, true));
$dir = sys_get_temp_dir();
@unlink($dir.'/classes.php');
file_put_contents($dir.'/classes.map', sprintf('<?php return %s;', var_export(array(WarmedClass::class), true)));
$warmer = new ClassCacheCacheWarmer(array(DeclaredClass::class));
$warmer->warmUp($dir);
$this->assertSame(<<<'EOTXT'
<?php
namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures
{
class WarmedClass extends DeclaredClass
{
}
}
EOTXT
, file_get_contents($dir.'/classes.php')
);
@unlink($dir.'/classes.map');
@unlink($dir.'/classes.php');
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures;
class DeclaredClass
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures;
class WarmedClass extends DeclaredClass
{
}

View File

@ -19,7 +19,7 @@
"php": ">=5.5.9",
"symfony/asset": "~2.8|~3.0",
"symfony/cache": "~3.1",
"symfony/class-loader": "~2.8|~3.0",
"symfony/class-loader": "~3.2",
"symfony/dependency-injection": "~3.2",
"symfony/config": "~2.8|~3.0",
"symfony/event-dispatcher": "~2.8|~3.0",

View File

@ -93,14 +93,41 @@ class ClassCollectionLoader
$declared = array_merge(get_declared_classes(), get_declared_interfaces(), get_declared_traits());
}
$files = self::inline($classes, $cache, $declared);
if ($autoReload) {
// save the resources
self::writeCacheFile($metadata, serialize(array(array_values($files), $classes)));
}
}
/**
* Generates a file where classes and their parents are inlined.
*
* @param array $classes An array of classes to load
* @param string $cache The file where classes are inlined
* @param array $excluded An array of classes that won't be inlined
*
* @return array The source map of inlined classes, with classes as keys and files as values
*
* @throws \RuntimeException When class can't be loaded
*/
public static function inline($classes, $cache, array $excluded)
{
$declared = array();
foreach (self::getOrderedClasses($excluded) as $class) {
$declared[$class->getName()] = true;
}
$files = array();
$content = '';
foreach (self::getOrderedClasses($classes) as $class) {
if (in_array($class->getName(), $declared)) {
if (isset($declared[$class->getName()])) {
continue;
}
$declared[$class->getName()] = true;
$files[] = $class->getFileName();
$files[$class->getName()] = $class->getFileName();
$c = preg_replace(array('/^\s*<\?php/', '/\?>\s*$/'), '', file_get_contents($class->getFileName()));
@ -116,15 +143,13 @@ class ClassCollectionLoader
}
// cache the core classes
$cacheDir = dirname($cache);
if (!is_dir($cacheDir) && !@mkdir($cacheDir, 0777, true) && !is_dir($cacheDir)) {
throw new \RuntimeException(sprintf('Class Collection Loader was not able to create directory "%s"', $cacheDir));
}
self::writeCacheFile($cache, '<?php '.$content);
if ($autoReload) {
// save the resources
self::writeCacheFile($metadata, serialize(array($files, $classes)));
}
return $files;
}
/**

View File

@ -12,6 +12,8 @@
namespace Symfony\Component\ClassLoader\Tests;
use Symfony\Component\ClassLoader\ClassCollectionLoader;
use Symfony\Component\ClassLoader\Tests\Fixtures\DeclaredClass;
use Symfony\Component\ClassLoader\Tests\Fixtures\WarmedClass;
require_once __DIR__.'/Fixtures/ClassesWithParents/GInterface.php';
require_once __DIR__.'/Fixtures/ClassesWithParents/CInterface.php';
@ -271,4 +273,36 @@ EOF
unlink($file);
}
public function testInline()
{
$this->assertTrue(class_exists(WarmedClass::class, true));
@unlink($cache = sys_get_temp_dir().'/inline.php');
$classes = array(WarmedClass::class);
$excluded = array(DeclaredClass::class);
ClassCollectionLoader::inline($classes, $cache, $excluded);
$this->assertSame(<<<'EOTXT'
<?php
namespace Symfony\Component\ClassLoader\Tests\Fixtures
{
interface WarmedInterface
{
}
}
namespace Symfony\Component\ClassLoader\Tests\Fixtures
{
class WarmedClass extends DeclaredClass implements WarmedInterface
{
}
}
EOTXT
, file_get_contents($cache)
);
unlink($cache);
}
}

View File

@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\ClassLoader\Tests\Fixtures;
class DeclaredClass implements DeclaredInterface
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\ClassLoader\Tests\Fixtures;
interface DeclaredInterface
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\ClassLoader\Tests\Fixtures;
class WarmedClass extends DeclaredClass implements WarmedInterface
{
}

View File

@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\ClassLoader\Tests\Fixtures;
interface WarmedInterface
{
}