Skip to content

Commit 1375601

Browse files
committed
bug symfony#23558 [FrameworkBundle] fix ValidatorCacheWarmer: use serializing ArrayAdapter (dmaicher)
This PR was squashed before being merged into the 3.2 branch (closes symfony#23558). Discussion ---------- [FrameworkBundle] fix ValidatorCacheWarmer: use serializing ArrayAdapter | Q | A | ------------- | --- | Branch? | 3.2 | Bug fix? | yes | New feature? | no | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | symfony#23544 | License | MIT | Doc PR | - The `ValidatorCacheWarmer` was using an `ArrayAdapter` with `$storeSerialized=false`. This is a problem as inside the `LazyLoadingMetadataFactory` the metaData objects are mutated (parent class constraints are merged into it) after they have been written into the cache. So this means that currently when warming up the validator cache actually the merged metaData version is finally taken from the `ArrayAdapter` and written into the `PhpFilesAdapter`. Which then caused some duplicate constraints as the parent constraints are merged again after fetching from the cache inside `LazyLoadingMetadataFactory`. This fix makes sure we serialize objects into the `ArrayAdapter`. Writing a test case for this does not seem easy to me. Any ideas? EDIT: Maybe its even safer to just clone the object when writing it into the cache? ```diff diff --git a/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php b/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php index 79ad1f2..88eaf33 100644 --- a/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php +++ b/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php @@ -117,7 +117,7 @@ class LazyLoadingMetadataFactory implements MetadataFactoryInterface } if (null !== $this->cache) { - $this->cache->write($metadata); + $this->cache->write(clone $metadata); } ``` Opinions? Commits ------- c0556cb [FrameworkBundle] fix ValidatorCacheWarmer: use serializing ArrayAdapter
2 parents 1f8cc17 + c0556cb commit 1375601

File tree

4 files changed

+143
-145
lines changed

4 files changed

+143
-145
lines changed
Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
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\Bundle\FrameworkBundle\CacheWarmer;
13+
14+
use Psr\Cache\CacheItemPoolInterface;
15+
use Symfony\Component\Cache\Adapter\AdapterInterface;
16+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
17+
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
18+
use Symfony\Component\Cache\Adapter\ProxyAdapter;
19+
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
20+
21+
/**
22+
* @internal
23+
*/
24+
abstract class AbstractPhpFileCacheWarmer implements CacheWarmerInterface
25+
{
26+
private $phpArrayFile;
27+
private $fallbackPool;
28+
29+
/**
30+
* @param string $phpArrayFile The PHP file where metadata are cached
31+
* @param CacheItemPoolInterface $fallbackPool The pool where runtime-discovered metadata are cached
32+
*/
33+
public function __construct($phpArrayFile, CacheItemPoolInterface $fallbackPool)
34+
{
35+
$this->phpArrayFile = $phpArrayFile;
36+
if (!$fallbackPool instanceof AdapterInterface) {
37+
$fallbackPool = new ProxyAdapter($fallbackPool);
38+
}
39+
$this->fallbackPool = $fallbackPool;
40+
}
41+
42+
/**
43+
* {@inheritdoc}
44+
*/
45+
public function isOptional()
46+
{
47+
return true;
48+
}
49+
50+
/**
51+
* {@inheritdoc}
52+
*/
53+
public function warmUp($cacheDir)
54+
{
55+
$arrayAdapter = new ArrayAdapter();
56+
57+
spl_autoload_register(array(PhpArrayAdapter::class, 'throwOnRequiredClass'));
58+
try {
59+
if (!$this->doWarmUp($cacheDir, $arrayAdapter)) {
60+
return;
61+
}
62+
} finally {
63+
spl_autoload_unregister(array(PhpArrayAdapter::class, 'throwOnRequiredClass'));
64+
}
65+
66+
// the ArrayAdapter stores the values serialized
67+
// to avoid mutation of the data after it was written to the cache
68+
// so here we un-serialize the values first
69+
$values = array_map(function ($val) { return null !== $val ? unserialize($val) : null; }, $arrayAdapter->getValues());
70+
71+
$this->warmUpPhpArrayAdapter(new PhpArrayAdapter($this->phpArrayFile, $this->fallbackPool), $values);
72+
73+
foreach ($values as $k => $v) {
74+
$item = $this->fallbackPool->getItem($k);
75+
$this->fallbackPool->saveDeferred($item->set($v));
76+
}
77+
$this->fallbackPool->commit();
78+
}
79+
80+
protected function warmUpPhpArrayAdapter(PhpArrayAdapter $phpArrayAdapter, array $values)
81+
{
82+
$phpArrayAdapter->warmUp($values);
83+
}
84+
85+
/**
86+
* @param string $cacheDir
87+
* @param ArrayAdapter $arrayAdapter
88+
*
89+
* @return bool false if there is nothing to warm-up
90+
*/
91+
abstract protected function doWarmUp($cacheDir, ArrayAdapter $arrayAdapter);
92+
}

src/Symfony/Bundle/FrameworkBundle/CacheWarmer/AnnotationsCacheWarmer.php

Lines changed: 20 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -15,24 +15,18 @@
1515
use Doctrine\Common\Annotations\CachedReader;
1616
use Doctrine\Common\Annotations\Reader;
1717
use Psr\Cache\CacheItemPoolInterface;
18-
use Symfony\Component\Cache\Adapter\AdapterInterface;
1918
use Symfony\Component\Cache\Adapter\ArrayAdapter;
20-
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
21-
use Symfony\Component\Cache\Adapter\ProxyAdapter;
2219
use Symfony\Component\Cache\DoctrineProvider;
23-
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
2420

2521
/**
2622
* Warms up annotation caches for classes found in composer's autoload class map
2723
* and declared in DI bundle extensions using the addAnnotatedClassesToCache method.
2824
*
2925
* @author Titouan Galopin <galopintitouan@gmail.com>
3026
*/
31-
class AnnotationsCacheWarmer implements CacheWarmerInterface
27+
class AnnotationsCacheWarmer extends AbstractPhpFileCacheWarmer
3228
{
3329
private $annotationReader;
34-
private $phpArrayFile;
35-
private $fallbackPool;
3630

3731
/**
3832
* @param Reader $annotationReader
@@ -41,70 +35,41 @@ class AnnotationsCacheWarmer implements CacheWarmerInterface
4135
*/
4236
public function __construct(Reader $annotationReader, $phpArrayFile, CacheItemPoolInterface $fallbackPool)
4337
{
38+
parent::__construct($phpArrayFile, $fallbackPool);
4439
$this->annotationReader = $annotationReader;
45-
$this->phpArrayFile = $phpArrayFile;
46-
if (!$fallbackPool instanceof AdapterInterface) {
47-
$fallbackPool = new ProxyAdapter($fallbackPool);
48-
}
49-
$this->fallbackPool = $fallbackPool;
5040
}
5141

5242
/**
5343
* {@inheritdoc}
5444
*/
55-
public function warmUp($cacheDir)
45+
protected function doWarmUp($cacheDir, ArrayAdapter $arrayAdapter)
5646
{
57-
$adapter = new PhpArrayAdapter($this->phpArrayFile, $this->fallbackPool);
5847
$annotatedClassPatterns = $cacheDir.'/annotations.map';
5948

6049
if (!is_file($annotatedClassPatterns)) {
61-
$adapter->warmUp(array());
62-
63-
return;
50+
return true;
6451
}
6552

6653
$annotatedClasses = include $annotatedClassPatterns;
67-
68-
$arrayPool = new ArrayAdapter(0, false);
69-
$reader = new CachedReader($this->annotationReader, new DoctrineProvider($arrayPool));
70-
71-
spl_autoload_register(array($adapter, 'throwOnRequiredClass'));
72-
try {
73-
foreach ($annotatedClasses as $class) {
74-
try {
75-
$this->readAllComponents($reader, $class);
76-
} catch (\ReflectionException $e) {
77-
// ignore failing reflection
78-
} catch (AnnotationException $e) {
79-
/*
80-
* Ignore any AnnotationException to not break the cache warming process if an Annotation is badly
81-
* configured or could not be found / read / etc.
82-
*
83-
* In particular cases, an Annotation in your code can be used and defined only for a specific
84-
* environment but is always added to the annotations.map file by some Symfony default behaviors,
85-
* and you always end up with a not found Annotation.
86-
*/
87-
}
54+
$reader = new CachedReader($this->annotationReader, new DoctrineProvider($arrayAdapter));
55+
56+
foreach ($annotatedClasses as $class) {
57+
try {
58+
$this->readAllComponents($reader, $class);
59+
} catch (\ReflectionException $e) {
60+
// ignore failing reflection
61+
} catch (AnnotationException $e) {
62+
/*
63+
* Ignore any AnnotationException to not break the cache warming process if an Annotation is badly
64+
* configured or could not be found / read / etc.
65+
*
66+
* In particular cases, an Annotation in your code can be used and defined only for a specific
67+
* environment but is always added to the annotations.map file by some Symfony default behaviors,
68+
* and you always end up with a not found Annotation.
69+
*/
8870
}
89-
} finally {
90-
spl_autoload_unregister(array($adapter, 'throwOnRequiredClass'));
91-
}
92-
93-
$values = $arrayPool->getValues();
94-
$adapter->warmUp($values);
95-
96-
foreach ($values as $k => $v) {
97-
$item = $this->fallbackPool->getItem($k);
98-
$this->fallbackPool->saveDeferred($item->set($v));
9971
}
100-
$this->fallbackPool->commit();
101-
}
10272

103-
/**
104-
* {@inheritdoc}
105-
*/
106-
public function isOptional()
107-
{
10873
return true;
10974
}
11075

src/Symfony/Bundle/FrameworkBundle/CacheWarmer/SerializerCacheWarmer.php

Lines changed: 13 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,7 @@
1313

1414
use Doctrine\Common\Annotations\AnnotationException;
1515
use Psr\Cache\CacheItemPoolInterface;
16-
use Symfony\Component\Cache\Adapter\AdapterInterface;
1716
use Symfony\Component\Cache\Adapter\ArrayAdapter;
18-
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
19-
use Symfony\Component\Cache\Adapter\ProxyAdapter;
20-
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
2117
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
2218
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
2319
use Symfony\Component\Serializer\Mapping\Loader\LoaderChain;
@@ -30,11 +26,9 @@
3026
*
3127
* @author Titouan Galopin <galopintitouan@gmail.com>
3228
*/
33-
class SerializerCacheWarmer implements CacheWarmerInterface
29+
class SerializerCacheWarmer extends AbstractPhpFileCacheWarmer
3430
{
3531
private $loaders;
36-
private $phpArrayFile;
37-
private $fallbackPool;
3832

3933
/**
4034
* @param LoaderInterface[] $loaders The serializer metadata loaders
@@ -43,60 +37,33 @@ class SerializerCacheWarmer implements CacheWarmerInterface
4337
*/
4438
public function __construct(array $loaders, $phpArrayFile, CacheItemPoolInterface $fallbackPool)
4539
{
40+
parent::__construct($phpArrayFile, $fallbackPool);
4641
$this->loaders = $loaders;
47-
$this->phpArrayFile = $phpArrayFile;
48-
if (!$fallbackPool instanceof AdapterInterface) {
49-
$fallbackPool = new ProxyAdapter($fallbackPool);
50-
}
51-
$this->fallbackPool = $fallbackPool;
5242
}
5343

5444
/**
5545
* {@inheritdoc}
5646
*/
57-
public function warmUp($cacheDir)
47+
protected function doWarmUp($cacheDir, ArrayAdapter $arrayAdapter)
5848
{
5949
if (!class_exists(CacheClassMetadataFactory::class) || !method_exists(XmlFileLoader::class, 'getMappedClasses') || !method_exists(YamlFileLoader::class, 'getMappedClasses')) {
60-
return;
50+
return false;
6151
}
6252

63-
$adapter = new PhpArrayAdapter($this->phpArrayFile, $this->fallbackPool);
64-
$arrayPool = new ArrayAdapter(0, false);
65-
66-
$metadataFactory = new CacheClassMetadataFactory(new ClassMetadataFactory(new LoaderChain($this->loaders)), $arrayPool);
53+
$metadataFactory = new CacheClassMetadataFactory(new ClassMetadataFactory(new LoaderChain($this->loaders)), $arrayAdapter);
6754

68-
spl_autoload_register(array($adapter, 'throwOnRequiredClass'));
69-
try {
70-
foreach ($this->extractSupportedLoaders($this->loaders) as $loader) {
71-
foreach ($loader->getMappedClasses() as $mappedClass) {
72-
try {
73-
$metadataFactory->getMetadataFor($mappedClass);
74-
} catch (\ReflectionException $e) {
75-
// ignore failing reflection
76-
} catch (AnnotationException $e) {
77-
// ignore failing annotations
78-
}
55+
foreach ($this->extractSupportedLoaders($this->loaders) as $loader) {
56+
foreach ($loader->getMappedClasses() as $mappedClass) {
57+
try {
58+
$metadataFactory->getMetadataFor($mappedClass);
59+
} catch (\ReflectionException $e) {
60+
// ignore failing reflection
61+
} catch (AnnotationException $e) {
62+
// ignore failing annotations
7963
}
8064
}
81-
} finally {
82-
spl_autoload_unregister(array($adapter, 'throwOnRequiredClass'));
83-
}
84-
85-
$values = $arrayPool->getValues();
86-
$adapter->warmUp($values);
87-
88-
foreach ($values as $k => $v) {
89-
$item = $this->fallbackPool->getItem($k);
90-
$this->fallbackPool->saveDeferred($item->set($v));
9165
}
92-
$this->fallbackPool->commit();
93-
}
9466

95-
/**
96-
* {@inheritdoc}
97-
*/
98-
public function isOptional()
99-
{
10067
return true;
10168
}
10269

0 commit comments

Comments
 (0)