8000 feature #19485 [FrameworkBundle] Introduce a cache warmer for Validat… · symfony/symfony@68737eb · GitHub
[go: up one dir, main page]

Skip to content

Commit 68737eb

Browse files
committed
feature #19485 [FrameworkBundle] Introduce a cache warmer for Validator based on PhpArrayAdapter (tgalopin)
This PR was merged into the 3.2-dev branch. Discussion ---------- [FrameworkBundle] Introduce a cache warmer for Validator based on PhpArrayAdapter | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | yes | Tests pass? | - | Fixed tickets | - | License | MIT | Doc PR | - Following the cache warmer for annotations PR (#18533), this PR introduces a cache warmer for YAML and XML Validator configuration. Based on the PhpArrayAdapter, it uses the naming conventions (`Resources/config/validation`) to find the files and compile them into a single PHP file stored in the cache directory. This file uses shared memory on PHP 7. The benefit of this PR are the same than the ones of the annotations PR: - validation configuration can be warmed up offline - on PHP 7, there is no need for user extension to get maximum performances (ie. if you use this PR and the other one, you probably won't need to enable APCu to have great performances) - on PHP 7 again, we are not sensitive to APCu memory fragmentation - last but not least, global performance is slightly better (I get 30us per class gain in Blackfire) This PR also deprecates the framework.validator.cache key in favor of the cache pool introduced in #18544. Commits ------- 6bdaf0b [FrameworkBundle] Introduce a cache warmer for Validator based on PhpArrayAdapter
2 parents 3416f21 + 6bdaf0b commit 68737eb

File tree

18 files changed

+349
-70
lines changed

18 files changed

+349
-70
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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+
use Symfony\Component\Validator\Mapping\Cache\Psr6Cache;
21+
use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
22+
use Symfony\Component\Validator\Mapping\Loader\LoaderChain;
23+
use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
24+
use Symfony\Component\Validator\Mapping\Loader\XmlFileLoader;
25+
use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader;
26+
use Symfony\Component\Validator\ValidatorBuilderInterface;
27+
28+
/**
29+
* Warms up XML and YAML validator metadata.
30+
*
31+
* @author Titouan Galopin <galopintitouan@gmail.com>
32+
*/
33+
class ValidatorCacheWarmer implements CacheWarmerInterface
34+
{
35+
private $validatorBuilder;
36+
private $phpArrayFile;
37+
private $fallbackPool;
38+
39+
/**
40+
* @param ValidatorBuilderInterface $validatorBuilder
41+
* @param string $phpArrayFile The PHP file where metadata are cached.
42+
* @param CacheItemPoolInterface $fallbackPool The pool where runtime-discovered metadata are cached.
43+
*/
44+
public function __construct(ValidatorBuilderInterface $validatorBuilder, $phpArrayFile, CacheItemPoolInterface $fallbackPool)
45+
{
46+
$this->validatorBuilder = $validatorBuilder;
47+
$this->phpArrayFile = $phpArrayFile;
48+
if (!$fallbackPool instanceof AdapterInterface) {
49+
$fallbackPool = new ProxyAdapter($fallbackPool);
50+
}
51+
$this->fallbackPool = $fallbackPool;
52+
}
53+
54+
/**
55+
* {@inheritdoc}
56+
*/
57+
public function warmUp($cacheDir)
58+
{
59+
if (!method_exists($this->validatorBuilder, 'getLoaders')) {
60+
return;
61+
}
62+
63+
$adapter = new PhpArrayAdapter($this->phpArrayFile, $this->fallbackPool);
64+
$arrayPool = new ArrayAdapter(0, false);
65+
66+
$loaders = $this->validatorBuilder->getLoaders();
67+
$metadataFactory = new LazyLoadingMetadataFactory(new LoaderChain($loaders), new Psr6Cache($arrayPool));
68+
69+
foreach ($this->extractSupportedLoaders($loaders) as $loader) {
70+
foreach ($loader->getMappedClasses() as $mappedClass) {
71+
$metadataFactory->getMetadataFor($mappedClass);
72+
}
73+
}
74+
75+
$values = $arrayPool->getValues();
76+
$adapter->warmUp($values);
77+
78+
foreach ($values as $k => $v) {
79+
$item = $this->fallbackPool->getItem($k);
80+
$this->fallbackPool->saveDeferred($item->set($v));
81+
}
82+
$this->fallbackPool->commit();
83+
}
84+
85+
/**
86+
* {@inheritdoc}
87+
*/
88+
public function isOptional()
89+
{
90+
return true;
91+
}
92+
93+
/**
94+
* @param LoaderInterface[] $loaders
95+
*
96+
* @return XmlFileLoader[]|YamlFileLoader[]
97+
*/
98+
private function extractSupportedLoaders(array $loaders)
99+
{
100+
$supportedLoaders = array();
101+
102+
foreach ($loaders as $loader) {
103+
if ($loader instanceof XmlFileLoader || $loader instanceof YamlFileLoader) {
104+
$supportedLoaders[] = $loader;
105+
} elseif ($loader instanceof LoaderChain) {
106+
$supportedLoaders = array_merge($supportedLoaders, $this->extractSupportedLoaders($loader->getDelegatedLoaders()));
107+
}
108+
}
109+
110+
return $supportedLoaders;
111+
}
112+
}

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Configuration.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -568,7 +568,7 @@ private function addValidationSection(ArrayNodeDefinition $rootNode)
568568
->info('validation configuration')
569569
->canBeEnabled()
570570
->children()
571-
->scalarNode('cache')->defaultValue('validator.mapping.cache.symfony')->end()
571+
->scalarNode('cache')->end()
572572
->booleanNode('enable_annotations')->defaultFalse()->end()
573573
->arrayNode('static_method')
574574
->defaultValue(array('loadValidatorMetadata'))

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -878,13 +878,17 @@ private function registerValidationConfiguration(array $config, ContainerBuilder
878878
}
879879
}
880880

881-
if (!$container->getParameter('kernel.debug')) {
881+
if (isset($config['cache']) && $config['cache']) {
882+
@trigger_error('The "framework.validation.cache" option is deprecated since Symfony 3.2 and will be removed in 4.0. Configure the "cache.validator" service under "framework.cache.pools" instead.', E_USER_DEPRECATED);
883+
882884
$container->setParameter(
883885
'validator.mapping.cache.prefix',
884886
'validator_'.$this->getKernelRootHash($container)
885887
);
886888

887889
$validatorBuilder->addMethodCall('setMetadataCache', array(new Reference($config['cache'])));
890+
} elseif (!$container->getParameter('kernel.debug')) {
891+
$validatorBuilder->addMethodCall('setMetadataCache', array(new Reference('validator.mapping.cache.symfony')));
888892
}
889893
}
890894

src/Symfony/Bundle/FrameworkBundle/Resources/config/validator.xml

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
<parameters>
88
<parameter key="validator.mapping.cache.prefix" />
9+
<parameter key="validator.mapping.cache.file">%kernel.cache_dir%/validation.php</parameter>
910
</parameters>
1011

1112
<services>
@@ -28,8 +29,21 @@
2829

2930
<service id="validator.mapping.class_metadata_factory" alias="validator" public="false" />
3031

31-
<service id="validator.mapping.cache.symfony" class="Symfony\Component\Validator\Mapping\Cache\Psr6Cache" public="false">
32+
<service id="validator.mapping.cache_warmer" class="Symfony\Bundle\FrameworkBundle\CacheWarmer\ValidatorCacheWarmer" public="false">
33+
<argument type="service" id="validator.builder" />
34+
<argument>%validator.mapping.cache.file%</argument>
3235
<argument type="service" id="cache.validator" />
36+
<tag name="kernel.cache_warmer" />
37+
</service>
38+
39+
<service id="validator.mapping.cache.symfony" class="Symfony\Component\Validator\Mapping\Cache\Psr6Cache" public="false">
40+
<argument type="service">
41+
<service class="Symfony\Component\Cache\Adapter\PhpArrayAdapter">
42+
<factory class="Symfony\Component\Cache\Adapter\PhpArrayAdapter" method="create" />
43+
<argument>%validator.mapping.cache.file%</argument>
44+
<argument type="service" id="cache.validator" />
45+
</service>
46+
</argument>
3347
</service>
3448

3549
<service id="validator.mapping.cache.doctrine.apc" class="Symfony\Component\Validator\Mapping\Cache\DoctrineCache" public="false">
Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
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\Tests\CacheWarmer;
13+
14+
use Symfony\Bundle\FrameworkBundle\CacheWarmer\ValidatorCacheWarmer;
15+
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
16+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
17+
use Symfony\Component\Validator\ValidatorBuilder;
18+
19+
class ValidatorCacheWarmerTest extends TestCase
20+
{
21+
public function testWarmUp()
22+
{
23+
$validatorBuilder = new ValidatorBuilder();
24+
$validatorBuilder->addXmlMapping(__DIR__.'/../Fixtures/Validation/Resources/person.xml');
25+
$validatorBuilder->addYamlMapping(__DIR__.'/../Fixtures/Validation/Resources/author.yml');
26+
$validatorBuilder->addMethodMapping('loadValidatorMetadata');
27+
$validatorBuilder->enableAnnotationMapping();
28+
29+
$file = sys_get_temp_dir().'/cache-validator.php';
30+
@unlink($file);
31+
32+
$fallbackPool = new ArrayAdapter();
33+
34+
$warmer = new ValidatorCacheWarmer($validatorBuilder, $file, $fallbackPool);
35+
$warmer->warmUp(dirname($file));
36+
37+
$this->assertFileExists($file);
38+
39+
$values = require $file;
40+
41+
$this->assertInternalType('array', $values);
42+
$this->assertCount(2, $values);
43+
$this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Person', $values);
44+
$this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Author', $values);
45+
46+
$values = $fallbackPool->getValues();
47+
48+
$this->assertInternalType('array', $values);
49+
$this->assertCount(2, $values);
50+
$this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Person', $values);
51+
$this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Author', $values);
52+
}
53+
54+
public function testWarmUpWithoutLoader()
55+
{
56+
$validatorBuilder = new ValidatorBuilder();
57+
58+
$file = sys_get_temp_dir().'/cache-validator-without-loaders.php';
59+
@unlink($file);
60+
61+
$fallbackPool = new ArrayAdapter();
62+
63+
$warmer = new ValidatorCacheWarmer($validatorBuilder, $file, $fallbackPool);
64+
$warmer->warmUp(dirname($file));
65+
66+
$this->assertFileExists($file);
67+
68+
$values = require $file;
69+
70+
$this->assertInternalType('array', $values);
71+
$this->assertCount(0, $values);
72+
73+
$values = $fallbackPool->getValues();
74+
75+
$this->assertInternalType('array', $values);
76+
$this->assertCount(0, $values);
77+
}
78+
}

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/ConfigurationTest.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -211,7 +211,6 @@ protected static function getBundleDefaultConfig()
211211
'static_method' => array('loadValidatorMetadata'),
212212
'translation_domain' => 'validators',
213213
'strict_email' => false,
214-
'cache' => 'validator.mapping.cache.symfony',
215214
),
216215
'annotations' => array(
217216
'cache' => 'php_array',

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/php/full.php

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,6 @@
5656
),
5757
'validation' => array(
5858
'enabled' => true,
59-
'cache' => 'validator.mapping.cache.doctrine.apc',
6059
),
6160
'annotations' => array(
6261
'cache' => 'file',

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/Fixtures/xml/full.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -38,7 +38,7 @@
3838
<framework:translator enabled="true" fallback="fr" logging="true">
3939
<framework:path>%kernel.root_dir%/Fixtures/translations</framework:path>
4040
</framework:translator>
41-
<framework:validation enabled="true" cache="validator.mapping.cache.doctrine.apc" />
41+
<framework:validation enabled="true" />
4242
<framework:annotations cache="file" debug="true" file-cache-dir="%kernel.cache_dir%/annotations" />
4343
<framework:serializer enabled="true" enable-annotations="true" name-converter="serializer.name_converter.camel_case_to_snake_case" />
4444
<framework:property-info />

src/Symfony/Bundle/FrameworkBundle/Tests/Dep 4E34 endencyInjection/Fixtures/yml/full.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,6 @@ framework:
4444
paths: ['%kernel.root_dir%/Fixtures/translations']
4545
validation:
4646
enabled: true
47-
cache: validator.mapping.cache.doctrine.apc
4847
annotations:
4948
cache: file
5049
debug: true

src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -340,7 +340,7 @@ public function testValidation()
340340
$this->assertSame('addMethodMapping', $calls[4][0]);
341341
$this->assertSame(array('loadValidatorMetadata'), $calls[4][1]);
342342
$this->assertSame('setMetadataCache', $calls[5][0]);
343-
$this->assertEquals(array(new Reference('validator.mapping.cache.doctrine.apc')), $calls[5][1]);
343+
$this->assertEquals(array(new Reference('validator.mapping.cache.symfony')), $calls[5][1]);
344344
}
345345

346346
public function testValidationService()
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation;
4+
5+
class Author
6+
{
7+
public $gender;
8+
}
Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation;
4+
5+
class Person
6+
{
7+
public $gender;
8+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation\Author:
2+
properties:
3+
gender:
4+
- Choice: { choices: [male, female, other], message: Choose a valid gender. }
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
<?xml version="1.0" encoding="UTF-8" ?>
2+
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">
5+
6+
<class name="Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation\Person">
7+
<property name="gender">
8+
<constraint name="Choice">
9+
<option name="choices">
10+
<value>male</value>
11+
<value>female</value>
12+
<value>other</value>
13+
</option>
14+
<option name="message">Choose a valid gender.</option>
15+
</constraint>
16+
</property>
17+
</class>
18+
</constraint-mapping>

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@
4848
"symfony/expression-language": "~2.8|~3.0",
4949
"symfony/process": "~2.8|~3.0",
5050
"symfony/serializer": "~2.8|~3.0",
51-
"symfony/validator": "~3.1",
51+
"symfony/validator": "~3.2",
5252
"symfony/yaml": "~3.2",
5353
"symfony/property-info": "~2.8|~3.0",
5454
"phpdocumentor/reflection-docblock": "^3.0",

0 commit comments

Comments
 (0)
0