From c60009eae8506cfd31786fdcd867b4e76478345a Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 11 Jan 2017 22:40:22 +0100 Subject: [PATCH 1/2] [FrameworkBundle] Fix class_exists() checks in PhpArrayAdapter-related cache warmers --- .../CacheWarmer/AnnotationsCacheWarmer.php | 9 ++--- .../CacheWarmer/SerializerCacheWarmer.php | 24 +++++++++--- .../CacheWarmer/ValidatorCacheWarmer.php | 15 ++++---- .../Bundle/FrameworkBundle/composer.json | 2 +- .../Cache/Adapter/PhpArrayAdapter.php | 38 +++++++++++++++++++ 5 files changed, 69 insertions(+), 19 deletions(-) diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php index 446deddab89c..b6aafbeb709a 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php @@ -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) { @@ -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 { @@ -88,7 +87,7 @@ public function warmUp($cacheDir) } } } finally { - spl_autoload_unregister($throwingAutoloader); + spl_autoload_unregister(array($adapter, 'throwOnRequiredClass')); } $values = $arrayPool->getValues(); diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php index 23e6142be499..05ea69131678 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php @@ -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; @@ -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) { @@ -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(); diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php index d450cc1f8523..44d321b79081 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php @@ -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; @@ -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) { @@ -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) { @@ -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); diff --git a/src/Symfony/Bundle/FrameworkBundle/composer.json b/src/Symfony/Bundle/FrameworkBundle/composer.json index 5125ae9eb197..34c0b2fe691d 100644 --- a/src/Symfony/Bundle/FrameworkBundle/composer.json +++ b/src/Symfony/Bundle/FrameworkBundle/composer.json @@ -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", diff --git a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php index 37b885fa32d3..e4d8ad5eea31 100644 --- a/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php +++ b/src/Symfony/Component/Cache/Adapter/PhpArrayAdapter.php @@ -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; + } } From e09dccc7622987b1ff1af9e7115ce7ad4a022d91 Mon Sep 17 00:00:00 2001 From: Mikael Pajunen Date: Fri, 30 Dec 2016 00:33:11 +0200 Subject: [PATCH 2/2] [FrameworkBundle] Add annotated validator cache test case --- .../CacheWarmer/AnnotationsCacheWarmer.php | 4 +-- .../CacheWarmer/SerializerCacheWarmer.php | 6 ++-- .../CacheWarmer/ValidatorCacheWarmer.php | 4 +-- .../CacheWarmer/ValidatorCacheWarmerTest.php | 33 +++++++++++++++++++ .../Tests/Fixtures/Validation/Category.php | 17 ++++++++++ .../Validation/Resources/categories.yml | 9 +++++ .../Tests/Fixtures/Validation/SubCategory.php | 13 ++++++++ 7 files changed, 79 insertions(+), 7 deletions(-) create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Category.php create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Resources/categories.yml create mode 100644 src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/SubCategory.php diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php index b6aafbeb709a..a6fb4ed095d2 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php @@ -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) { diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php index 05ea69131678..c017f51268b3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php @@ -37,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) { diff --git a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php index 44d321b79081..81291d772fbf 100644 --- a/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php +++ b/src/Symfony/Bundle/FrameworkBundle/CacheWarmer/ValidatorCacheWarmer.php @@ -39,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) { diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/ValidatorCacheWarmerTest.php b/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/ValidatorCacheWarmerTest.php index 0e3fe47ce5ff..23b4732afcb3 100644 --- a/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/ValidatorCacheWarmerTest.php +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/CacheWarmer/ValidatorCacheWarmerTest.php @@ -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(); diff --git a/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Category.php b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Category.php new file mode 100644 index 000000000000..da8963dafa6b --- /dev/null +++ b/src/Symfony/Bundle/FrameworkBundle/Tests/Fixtures/Validation/Category.php @@ -0,0 +1,17 @@ +