Skip to content

Commit

Permalink
feature #19276 [ClassLoader] Add ClassCollectionLoader::inline() to g…
Browse files Browse the repository at this point in the history
…enerate 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 sensiolabs/SensioDistributionBundle#272, allows inlining `ClassCollectionLoader` itself into the `bootstrap.php.cache` file.

Commits
-------

88fdcea [ClassLoader] Add ClassCollectionLoader::inline() to generate inlined-classes files
  • Loading branch information
fabpot committed Jul 18, 2016
2 parents 56c7206 + 88fdcea commit 583a45d
Show file tree
Hide file tree
Showing 13 changed files with 189 additions and 8 deletions.
Expand Up @@ -21,6 +21,13 @@
*/
class ClassCacheCacheWarmer implements CacheWarmerInterface
{
private $declaredClasses;

public function __construct(array $declaredClasses = null)
{
$this->declaredClasses = $declaredClasses;
}

/**
* Warms up the cache.
*
Expand All @@ -37,8 +44,9 @@ public function warmUp($cacheDir)
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);
}

/**
Expand Down
Expand Up @@ -170,15 +170,23 @@ public function load(array $configs, ContainerBuilder $container)
}

$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',
Expand All @@ -189,13 +197,18 @@ public function load(array $configs, ContainerBuilder $container)
'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',
));
}

Expand Down
11 changes: 11 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Resources/config/services.xml
Expand Up @@ -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">
Expand Down
@@ -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');
}
}
@@ -0,0 +1,7 @@
<?php

namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures;

class DeclaredClass
{
}
@@ -0,0 +1,7 @@
<?php

namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures;

class WarmedClass extends DeclaredClass
{
}
2 changes: 1 addition & 1 deletion src/Symfony/Bundle/FrameworkBundle/composer.json
Expand Up @@ -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",
Expand Down
37 changes: 31 additions & 6 deletions src/Symfony/Component/ClassLoader/ClassCollectionLoader.php
Expand Up @@ -93,14 +93,41 @@ public static function load($classes, $cacheDir, $name, $autoReload, $adaptive =
$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()));

Expand All @@ -116,15 +143,13 @@ public static function load($classes, $cacheDir, $name, $autoReload, $adaptive =
}

// 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;
}

/**
Expand Down
Expand Up @@ -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';
Expand Down Expand Up @@ -271,4 +273,36 @@ class Pearlike_WithComments

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);
}
}
@@ -0,0 +1,7 @@
<?php

namespace Symfony\Component\ClassLoader\Tests\Fixtures;

class DeclaredClass implements DeclaredInterface
{
}
@@ -0,0 +1,7 @@
<?php

namespace Symfony\Component\ClassLoader\Tests\Fixtures;

interface DeclaredInterface
{
}
@@ -0,0 +1,7 @@
<?php

namespace Symfony\Component\ClassLoader\Tests\Fixtures;

class WarmedClass extends DeclaredClass implements WarmedInterface
{
}
@@ -0,0 +1,7 @@
<?php

namespace Symfony\Component\ClassLoader\Tests\Fixtures;

interface WarmedInterface
{
}

0 comments on commit 583a45d

Please sign in to comment.