Skip to content

Commit

Permalink
bug #21243 [FrameworkBundle] Fix class_exists() checks in PhpArrayAda…
Browse files Browse the repository at this point in the history
…pter-related cache warmers (nicolas-grekas, mpajunen)

This PR was merged into the 3.2 branch.

Discussion
----------

[FrameworkBundle] Fix class_exists() checks in PhpArrayAdapter-related cache warmers

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

Follow up of #21102

Commits
-------

e09dccc [FrameworkBundle] Add annotated validator cache test case
c60009e [FrameworkBundle] Fix class_exists() checks in PhpArrayAdapter-related cache warmers
  • Loading branch information
fabpot committed Jan 12, 2017
2 parents 7ba3bc6 + e09dccc commit 75de5eb
Show file tree
Hide file tree
Showing 9 changed files with 141 additions and 19 deletions.
Expand Up @@ -36,8 +36,8 @@ class AnnotationsCacheWarmer implements CacheWarmerInterface

/**
* @param Reader $annotationReader
* @param string $phpArrayFile The PHP file where annotations are cached.
* @param CacheItemPoolInterface $fallbackPool The pool where runtime-discovered annotations are cached.
* @param string $phpArrayFile The PHP file where annotations are cached
* @param CacheItemPoolInterface $fallbackPool The pool where runtime-discovered annotations are cached
*/
public function __construct(Reader $annotationReader, $phpArrayFile, CacheItemPoolInterface $fallbackPool)
{
Expand Down Expand Up @@ -67,9 +67,8 @@ public function warmUp($cacheDir)

$arrayPool = new ArrayAdapter(0, false);
$reader = new CachedReader($this->annotationReader, new DoctrineProvider($arrayPool));
$throwingAutoloader = function ($class) { throw new \ReflectionException(sprintf('Class %s does not exist', $class)); };
spl_autoload_register($throwingAutoloader);

spl_autoload_register(array($adapter, 'throwOnRequiredClass'));
try {
foreach ($annotatedClasses as $class) {
try {
Expand All @@ -88,7 +87,7 @@ public function warmUp($cacheDir)
}
}
} finally {
spl_autoload_unregister($throwingAutoloader);
spl_autoload_unregister(array($adapter, 'throwOnRequiredClass'));
}

$values = $arrayPool->getValues();
Expand Down
Expand Up @@ -11,6 +11,7 @@

namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;

use Doctrine\Common\Annotations\AnnotationException;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
Expand All @@ -36,9 +37,9 @@ class SerializerCacheWarmer implements CacheWarmerInterface
private $fallbackPool;

/**
* @param LoaderInterface[] $loaders The serializer metadata loaders.
* @param string $phpArrayFile The PHP file where metadata are cached.
* @param CacheItemPoolInterface $fallbackPool The pool where runtime-discovered metadata are cached.
* @param LoaderInterface[] $loaders The serializer metadata loaders
* @param string $phpArrayFile The PHP file where metadata are cached
* @param CacheItemPoolInterface $fallbackPool The pool where runtime-discovered metadata are cached
*/
public function __construct(array $loaders, $phpArrayFile, CacheItemPoolInterface $fallbackPool)
{
Expand All @@ -64,10 +65,21 @@ public function warmUp($cacheDir)

$metadataFactory = new CacheClassMetadataFactory(new ClassMetadataFactory(new LoaderChain($this->loaders)), $arrayPool);

foreach ($this->extractSupportedLoaders($this->loaders) as $loader) {
foreach ($loader->getMappedClasses() as $mappedClass) {
$metadataFactory->getMetadataFor($mappedClass);
spl_autoload_register(array($adapter, 'throwOnRequiredClass'));
try {
foreach ($this->extractSupportedLoaders($this->loaders) as $loader) {
foreach ($loader->getMappedClasses() as $mappedClass) {
try {
$metadataFactory->getMetadataFor($mappedClass);
} catch (\ReflectionException $e) {
// ignore failing reflection
} catch (AnnotationException $e) {
// ignore failing annotations
}
}
}
} finally {
spl_autoload_unregister(array($adapter, 'throwOnRequiredClass'));
}

$values = $arrayPool->getValues();
Expand Down
Expand Up @@ -11,6 +11,7 @@

namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;

use Doctrine\Common\Annotations\AnnotationException;
use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
Expand Down Expand Up @@ -38,8 +39,8 @@ class ValidatorCacheWarmer implements CacheWarmerInterface

/**
* @param ValidatorBuilderInterface $validatorBuilder
* @param string $phpArrayFile The PHP file where metadata are cached.
* @param CacheItemPoolInterface $fallbackPool The pool where runtime-discovered metadata are cached.
* @param string $phpArrayFile The PHP file where metadata are cached
* @param CacheItemPoolInterface $fallbackPool The pool where runtime-discovered metadata are cached
*/
public function __construct(ValidatorBuilderInterface $validatorBuilder, $phpArrayFile, CacheItemPoolInterface $fallbackPool)
{
Expand All @@ -66,9 +67,7 @@ public function warmUp($cacheDir)
$loaders = $this->validatorBuilder->getLoaders();
$metadataFactory = new LazyLoadingMetadataFactory(new LoaderChain($loaders), new Psr6Cache($arrayPool));

$throwingAutoloader = function ($class) { throw new \ReflectionException(sprintf('Class %s does not exist', $class)); };
spl_autoload_register($throwingAutoloader);

spl_autoload_register(array($adapter, 'throwOnRequiredClass'));
try {
foreach ($this->extractSupportedLoaders($loaders) as $loader) {
foreach ($loader->getMappedClasses() as $mappedClass) {
Expand All @@ -78,15 +77,17 @@ public function warmUp($cacheDir)
}
} catch (\ReflectionException $e) {
// ignore failing reflection
} catch (AnnotationException $e) {
// ignore failing annotations
}
}
}
} finally {
spl_autoload_unregister($throwingAutoloader);
spl_autoload_unregister(array($adapter, 'throwOnRequiredClass'));
}

$values = $arrayPool->getValues();
$adapter->warmUp($values);
$adapter->warmUp(array_filter($values));

foreach ($values as $k => $v) {
$item = $this->fallbackPool->getItem($k);
Expand Down
Expand Up @@ -51,6 +51,39 @@ public function testWarmUp()
$this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Author', $values);
}

public function testWarmUpWithAnnotations()
{
$validatorBuilder = new ValidatorBuilder();
$validatorBuilder->addYamlMapping(__DIR__.'/../Fixtures/Validation/Resources/categories.yml');
$validatorBuilder->enableAnnotationMapping();

$file = sys_get_temp_dir().'/cache-validator-with-annotations.php';
@unlink($file);

$fallbackPool = new ArrayAdapter();

$warmer = new ValidatorCacheWarmer($validatorBuilder, $file, $fallbackPool);
$warmer->warmUp(dirname($file));

$this->assertFileExists($file);

$values = require $file;

$this->assertInternalType('array', $values);
$this->assertCount(1, $values);
$this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Category', $values);

// Simple check to make sure that at least one constraint is actually cached, in this case the "id" property Type.
$this->assertContains('"int"', $values['Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Category']);

$values = $fallbackPool->getValues();

$this->assertInternalType('array', $values);
$this->assertCount(2, $values);
$this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Category', $values);
$this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.SubCategory', $values);
}

public function testWarmUpWithoutLoader()
{
$validatorBuilder = new ValidatorBuilder();
Expand Down
@@ -0,0 +1,17 @@
<?php

namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation;

use Symfony\Component\Validator\Constraints as Assert;

class Category
{
const NAME_PATTERN = '/\w+/';

public $id;

/**
* @Assert\Type(Category::NAME_PATTERN)
*/
public $name;
}
@@ -0,0 +1,9 @@
Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation\Category:
properties:
id:
- Type: int

Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation\SubCategory:
properties:
id:
- Type: int
@@ -0,0 +1,13 @@
<?php

namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation;

// Missing "use" for Assert\Type is on purpose

class SubCategory extends Category
{
/**
* @Assert\Type(Category::class)
*/
public $main;
}
2 changes: 1 addition & 1 deletion src/Symfony/Bundle/FrameworkBundle/composer.json
Expand Up @@ -17,7 +17,7 @@
],
"require": {
"php": ">=5.5.9",
"symfony/cache": "~3.2",
"symfony/cache": "~3.2.2|~3.3",
"symfony/class-loader": "~3.2",
"symfony/dependency-injection": "~3.2.1|~3.3",
"symfony/config": "~2.8|~3.0",
Expand Down
38 changes: 38 additions & 0 deletions src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php
Expand Up @@ -365,4 +365,42 @@ private function generateItems(array $keys)
}
}
}

/**
* @throws \ReflectionException When $class is not found and is required
*
* @internal
*/
public static function throwOnRequiredClass($class)
{
$e = new \ReflectionException(sprintf('Class %s does not exist', $class));
$trace = $e->getTrace();
$autoloadFrame = array(
'function' => 'spl_autoload_call',
'args' => array($class),
);
$i = array_search($autoloadFrame, $trace);

if (false !== $i++ && isset($trace[$i]['function']) && !isset($trace[$i]['class'])) {
switch ($trace[$i]['function']) {
case 'get_class_methods':
case 'get_class_vars':
case 'get_parent_class':
case 'is_a':
case 'is_subclass_of':
case 'class_exists':
case 'class_implements':
case 'class_parents':
case 'trait_exists':
case 'defined':
case 'interface_exists':
case 'method_exists':
case 'property_exists':
case 'is_callable':
return;
}
}

throw $e;
}
}

0 comments on commit 75de5eb

Please sign in to comment.