Uh oh!
There was an error while loading.Please reload this page.
- Notifications
You must be signed in to change notification settings - Fork9.7k
[FrameworkBundle] fix ValidatorCacheWarmer: use serializing ArrayAdapter#23558
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to ourterms of service andprivacy statement. We’ll occasionally send you account related emails.
Already on GitHub?Sign in to your account
Uh oh!
There was an error while loading.Please reload this page.
Conversation
ddd9afd to9b84d72Comparenicolas-grekas commentedJul 18, 2017 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
Good catch! |
| $adapter =newPhpArrayAdapter($this->phpArrayFile,$this->fallbackPool); | ||
| $arrayPool =newArrayAdapter(0,false); | ||
| $arrayPool =newArrayAdapter(0); |
nicolas-grekasJul 18, 2017 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
0 is the default so it can be removed also
7eb5bf8 tod8e5fa7Compared8e5fa7 to8753b06Compare| $this->assertCount(2,$values); | ||
| $this->assertCount(1,$values); | ||
| $this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Category',$values); | ||
| $this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.SubCategory',$values); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
@nicolas-grekas due to moving thearray_filter I needed to fix this test. It seems before we stored empty data in the cache?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Before the change the array contained this:
array:2 [ "Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Category" => "O:49:"Symfony\Component\Validator\Mapping\ClassMetadata":10:{s:11:"constraints";a:0:{}s:18:"constraintsByGroup";a:0:{}s:17:"traversalStrategy";i:1;s:7:"getters";a:0:{}s:13:"groupSequence";a:0:{}s:21:"groupSequenceProvider";b:0;s:7:"members";a:2:{s:2:"id";a:1:{i:0;O:52:"Symfony\Component\Validator\Mapping\PropertyMetadata":7:{s:11:"constraints";a:1:{i:0;O:44:"Symfony\Component\Validator\Constraints\Type":4:{s:7:"message";s:40:"This value should be of type {{ type }}.";s:4:"type";s:3:"int";s:7:"payload";N;s:6:"groups";a:2:{i:0;s:7:"Default";i:1;s:8:"Category";}}}s:18:"constraintsByGroup";a:2:{s:7:"Default";a:1:{i:0;r:12;}s:8:"Category";a:1:{i:0;r:12;}}s:17:"cascadingStrategy";i:1;s:17:"traversalStrategy";i:2;s:5:"class";s:65:"Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation\Category";s:4:"name";s:2:"id";s:8:"property";s:2:"id";}}s:4:"name";a:1:{i:0;O:52:"Symfony\Component\Validator\Mapping\PropertyMetadata":7:{s:11:"constraints";a:1:{i:0;O:44:"Symfony\Component\Validator\Constraints\Type":4:{s:7:"message";s:40:"This value should be of type {{ type }}.";s:4:"type";s:6:"string";s:7:"payload";N;s:6:"groups";a:2:{i:0;s:7:"Default";i:1;s:8:"Category";}}}s:18:"constraintsByGroup";a:2:{s:7:"Default";a:1:{i:0;r:32;}s:8:"Category";a:1:{i:0;r:32;}}s:17:"cascadingStrategy";i:1;s:17:"traversalStrategy";i:2;s:5:"class";s:65:"Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation\Category";s:4:"name";s:4:"name";s:8:"property";s:4:"name";}}}s:4:"name";s:65:"Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation\Category";s:10:"properties";a:2:{s:2:"id";r:10;s:4:"name";r:30;}s:12:"defaultGroup";s:8:"Category";}" "Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.SubCategory" => "b:0;"]There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
the behavior should not change at all so this test must not be changed
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
The change you requested makes this test fail though:
There is also one thing to fix: the array_filter should happen before the array_map (misses are stored as null, which can't "unserialized" and shouldn't be cached.)
| // the ArrayAdapter stores the values serialized | ||
| // to avoid mutation of the data after it was written to the cache | ||
| // so here we un-serialize the values first | ||
| $values =array_map(function ($val) {returnunserialize($val); },array_filter($arrayAdapter->getValues())); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
@nicolas-grekas is it fine to applyarray_filter here as well for the other cache warmers? Tests are fine although theAnnotationsCacheWarmer does not have any tests 😢
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
$values = array_map(function ($val) { return null !== $val ? unserialize($val) : null; }, $arrayAdapter->getValues());
and array_filter should be called only for ValidatorCacheWarmer, and only when feeding phpArrayAdapter.
the foreach then should iterate over all values, null or not
| * {@inheritdoc} | ||
| */ | ||
| publicfunctionwarmUp($cacheDir) | ||
| publicfunctionisOptional() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
this could also be moved to the parent I guess
| useSymfony\Component\Cache\Adapter\ProxyAdapter; | ||
| useSymfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; | ||
| abstractclass AbstractPhpFileCacheWarmerimplements CacheWarmerInterface |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
should be marked@internal
ff47aa1 to71b41f7Comparedmaicher commentedJul 18, 2017
@nicolas-grekas changes done 👍 |
| useSymfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface; | ||
| /** | ||
| * @internal This class is meant for internal use only. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
usually we don't add the additional comment so we should remove it I guess
| abstractclass AbstractPhpFileCacheWarmerimplements CacheWarmerInterface | ||
| { | ||
| protected$phpArrayFile; | ||
| protected$fallbackPool; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
shouldn't these both be private?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Indeed 👍
| $phpArrayAdapter->warmUp(array()); | ||
| return; | ||
| returnfalse; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
this makes me think that doWarmUp doesn't need the $phpArrayAdapter argument: it's used only here, but returning !== false here would ship the same behavior without the need for this arg, isn't it?
which then triggers: shouldn't we have a clean "bool" return type for all dowarmup methods? ie return true on success? (and here also then)
| spl_autoload_register(array($phpArrayAdapter,'throwOnRequiredClass')); | ||
| try { | ||
| $this->doWarmUp($cacheDir,$arrayAdapter); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
you still need the if (!doWarmUp) then return; check
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
But then this is not done anymore?
https://github.com/symfony/symfony/pull/23558/files#diff-ec41b7a9f7c5b4c51f70679eb93504b8L61
nicolas-grekasJul 18, 2017 • edited
Loading Uh oh!
There was an error while loading.Please reload this page.
edited
Uh oh!
There was an error while loading.Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
it will if we make dowarmup return true or false to allow/prevent the warmup
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
Ah now I see what you meant 😄 👍 Done.
add8c87 to8ab2ad6Compare| $phpArrayAdapter =newPhpArrayAdapter($this->phpArrayFile,$this->fallbackPool); | ||
| $arrayAdapter =newArrayAdapter(); | ||
| spl_autoload_register(array($phpArrayAdapter,'throwOnRequiredClass')); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
since we don't need the instance yet, let's use PhpArrayAdapter::class here instead, and create the object later, (or never when dowarmup returns false)
| if (!is_file($annotatedClassPatterns)) { | ||
| $adapter->warmUp(array()); | ||
| return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
return true
| protectedfunctiondoWarmUp($cacheDir,ArrayAdapter$arrayAdapter) | ||
| { | ||
| if (!class_exists(CacheClassMetadataFactory::class) || !method_exists(XmlFileLoader::class,'getMappedClasses') || !method_exists(YamlFileLoader::class,'getMappedClasses')) { | ||
| return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
return false
| protectedfunctiondoWarmUp($cacheDir,ArrayAdapter$arrayAdapter) | ||
| { | ||
| if (!method_exists($this->validatorBuilder,'getLoaders')) { | ||
| return; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
return false
nicolas-grekas left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others.Learn more.
LGTM, thanks for your reactivity tonight :)
fabpot commentedJul 19, 2017
Thank you@dmaicher. |
…g ArrayAdapter (dmaicher)This PR was squashed before being merged into the 3.2 branch (closes#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 |#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?```diffdiff --git a/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.php b/src/Symfony/Component/Validator/Mapping/Factory/LazyLoadingMetadataFactory.phpindex79ad1f2..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
Uh oh!
There was an error while loading.Please reload this page.
The
ValidatorCacheWarmerwas using anArrayAdapterwith$storeSerialized=false. This is a problem as inside theLazyLoadingMetadataFactorythe 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
ArrayAdapterand written into thePhpFilesAdapter.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?
Opinions?