Skip to content

[FrameworkBundle] Introduce a cache warmer for Validator based on PhpArrayAdapter #19485

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 our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 14, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;

use Psr\Cache\CacheItemPoolInterface;
use Symfony\Component\Cache\Adapter\AdapterInterface;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
use Symfony\Component\Cache\Adapter\ProxyAdapter;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
use Symfony\Component\Validator\Mapping\Cache\Psr6Cache;
use Symfony\Component\Validator\Mapping\Factory\LazyLoadingMetadataFactory;
use Symfony\Component\Validator\Mapping\Loader\LoaderChain;
use Symfony\Component\Validator\Mapping\Loader\LoaderInterface;
use Symfony\Component\Validator\Mapping\Loader\XmlFileLoader;
use Symfony\Component\Validator\Mapping\Loader\YamlFileLoader;
use Symfony\Component\Validator\ValidatorBuilderInterface;

/**
* Warms up XML and YAML validator metadata.
*
* @author Titouan Galopin <galopintitouan@gmail.com>
*/
class ValidatorCacheWarmer implements CacheWarmerInterface
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This cache warmer is very similar to the one you proposed for the serializer, can't we create a new abstract class ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Even if it seems similar, there is only a few lines that can be shared. I don't think it's worth it.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it can be used in third party libraries and promote cache warmers by making them much easier to use.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's discuss about this in another PR: it's not directly related to this PR and would deserve a dedicated line in the changelog.

{
private $validatorBuilder;
private $phpArrayFile;
private $fallbackPool;

/**
* @param ValidatorBuilderInterface $validatorBuilder
* @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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I only have a last suggestion: shouldn't we inject an instance of PhpArrayAdapter and add a method `getFallbackPool`` to it to avoid duplicating data in several places ?

Copy link
Member

@nicolas-grekas nicolas-grekas Aug 3, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suggest to consider all potential factorizations in the next PR we're talking about just above.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok fine for me then :)

{
$this->validatorBuilder = $validatorBuilder;
$this->phpArrayFile = $phpArrayFile;
if (!$fallbackPool instanceof AdapterInterface) {
$fallbackPool = new ProxyAdapter($fallbackPool);
}
$this->fallbackPool = $fallbackPool;
}

/**
* {@inheritdoc}
*/
public function warmUp($cacheDir)
{
if (!method_exists($this->validatorBuilder, 'getLoaders')) {
return;
}

$adapter = new PhpArrayAdapter($this->phpArrayFile, $this->fallbackPool);
$arrayPool = new ArrayAdapter(0, false);

$loaders = $this->validatorBuilder->getLoaders();
$metadataFactory = new LazyLoadingMetadataFactory(new LoaderChain($loaders), new Psr6Cache($arrayPool));

foreach ($this->extractSupportedLoaders($loaders) as $loader) {
foreach ($loader->getMappedClasses() as $mappedClass) {
$metadataFactory->getMetadataFor($mappedClass);
}
}

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

foreach ($values as $k => $v) {
$item = $this->fallbackPool->getItem($k);
$this->fallbackPool->saveDeferred($item->set($v));
}
$this->fallbackPool->commit();
}

/**
* {@inheritdoc}
*/
public function isOptional()
{
return true;
}

/**
* @param LoaderInterface[] $loaders
*
* @return XmlFileLoader[]|YamlFileLoader[]
*/
private function extractSupportedLoaders(array $loaders)
{
$supportedLoaders = array();

foreach ($loaders as $loader) {
if ($loader instanceof XmlFileLoader || $loader instanceof YamlFileLoader) {
$supportedLoaders[] = $loader;
} elseif ($loader instanceof LoaderChain) {
$supportedLoaders = array_merge($supportedLoaders, $this->extractSupportedLoaders($loader->getDelegatedLoaders()));
}
}

return $supportedLoaders;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ private function addValidationSection(ArrayNodeDefinition $rootNode)
->info('validation configuration')
->canBeEnabled()
->children()
->scalarNode('cache')->defaultValue('validator.mapping.cache.symfony')->end()
->scalarNode('cache')->end()
->booleanNode('enable_annotations')->defaultFalse()->end()
->arrayNode('static_method')
->defaultValue(array('loadValidatorMetadata'))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -858,13 +858,17 @@ private function registerValidationConfiguration(array $config, ContainerBuilder
}
}

if (!$container->getParameter('kernel.debug')) {
if (isset($config['cache']) && $config['cache']) {
@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);

$container->setParameter(
'validator.mapping.cache.prefix',
'validator_'.$this->getKernelRootHash($container)
);

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@

<parameters>
<parameter key="validator.mapping.cache.prefix" />
<parameter key="validator.mapping.cache.file">%kernel.cache_dir%/validation.php</parameter>
</parameters>

<services>
Expand All @@ -28,8 +29,21 @@

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

<service id="validator.mapping.cache.symfony" class="Symfony\Component\Validator\Mapping\Cache\Psr6Cache" public="false">
<service id="validator.mapping.cache_warmer" class="Symfony\Bundle\FrameworkBundle\CacheWarmer\ValidatorCacheWarmer" public="false">
<argument type="service" id="validator.builder" />
<argument>%validator.mapping.cache.file%</argument>
<argument type="service" id="cache.validator" />
<tag name="kernel.cache_warmer" />
</service>

<service id="validator.mapping.cache.symfony" class="Symfony\Component\Validator\Mapping\Cache\Psr6Cache" public="false">
<argument type="service">
<service class="Symfony\Component\Cache\Adapter\PhpArrayAdapter">
<factory class="Symfony\Component\Cache\Adapter\PhpArrayAdapter" method="create" />
<argument>%validator.mapping.cache.file%</argument>
<argument type="service" id="cache.validator" />
</service>
</argument>
</service>

<service id="validator.mapping.cache.doctrine.apc" class="Symfony\Component\Validator\Mapping\Cache\DoctrineCache" public="false">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\Tests\CacheWarmer;

use Symfony\Bundle\FrameworkBundle\CacheWarmer\ValidatorCacheWarmer;
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
use Symfony\Component\Cache\Adapter\ArrayAdapter;
use Symfony\Component\Validator\ValidatorBuilder;

class ValidatorCacheWarmerTest extends TestCase
{
public function testWarmUp()
{
$validatorBuilder = new ValidatorBuilder();
$validatorBuilder->addXmlMapping(__DIR__.'/../Fixtures/Validation/Resources/person.xml');
$validatorBuilder->addYamlMapping(__DIR__.'/../Fixtures/Validation/Resources/author.yml');
$validatorBuilder->addMethodMapping('loadValidatorMetadata');
$validatorBuilder->enableAnnotationMapping();

$file = sys_get_temp_dir().'/cache-validator.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(2, $values);
$this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Person', $values);
$this->assertArrayHasKey('Symfony.Bundle.FrameworkBundle.Tests.Fixtures.Validation.Author', $values);

$values = $fallbackPool->getValues();

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

public function testWarmUpWithoutLoader()
{
$validatorBuilder = new ValidatorBuilder();

$file = sys_get_temp_dir().'/cache-validator-without-loaders.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(0, $values);

$values = $fallbackPool->getValues();

$this->assertInternalType('array', $values);
$this->assertCount(0, $values);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -211,7 +211,6 @@ protected static function getBundleDefaultConfig()
'static_method' => array('loadValidatorMetadata'),
'translation_domain' => 'validators',
'strict_email' => false,
'cache' => 'validator.mapping.cache.symfony',
),
'annotations' => array(
'cache' => 'php_array',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,6 @@
),
'validation' => array(
'enabled' => true,
'cache' => 'validator.mapping.cache.doctrine.apc',
),
'annotations' => array(
'cache' => 'file',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@
<framework:translator enabled="true" fallback="fr" logging="true">
<framework:path>%kernel.root_dir%/Fixtures/translations</framework:path>
</framework:translator>
<framework:validation enabled="true" cache="validator.mapping.cache.doctrine.apc" />
<framework:validation enabled="true" />
<framework:annotations cache="file" debug="true" file-cache-dir="%kernel.cache_dir%/annotations" />
<framework:serializer enabled="true" enable-annotations="true" name-converter="serializer.name_converter.camel_case_to_snake_case" />
<framework:property-info />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,6 @@ framework:
paths: ['%kernel.root_dir%/Fixtures/translations']
validation:
enabled: true
cache: validator.mapping.cache.doctrine.apc
annotations:
cache: file
debug: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -336,7 +336,7 @@ public function testValidation()
$this->assertSame('addMethodMapping', $calls[4][0]);
$this->assertSame(array('loadValidatorMetadata'), $calls[4][1]);
$this->assertSame('setMetadataCache', $calls[5][0]);
$this->assertEquals(array(new Reference('validator.mapping.cache.doctrine.apc')), $calls[5][1]);
$this->assertEquals(array(new Reference('validator.mapping.cache.symfony')), $calls[5][1]);
}

public function testValidationService()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

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

class Author
{
public $gender;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

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

class Person
{
public $gender;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation\Author:
properties:
gender:
- Choice: { choices: [male, female, other], message: Choose a valid gender. }
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8" ?>
<constraint-mapping xmlns="http://symfony.com/schema/dic/constraint-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/constraint-mapping http://symfony.com/schema/dic/constraint-mapping/constraint-mapping-1.0.xsd">

<class name="Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Validation\Person">
<property name="gender">
<constraint name="Choice">
<option name="choices">
<value>male</value>
<value>female</value>
<value>other</value>
</option>
<option name="message">Choose a valid gender.</option>
</constraint>
</property>
</class>
</constraint-mapping>
2 changes: 1 addition & 1 deletion src/Symfony/Bundle/FrameworkBundle/composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@
"symfony/expression-language": "~2.8|~3.0",
"symfony/process": "~2.8|~3.0",
"symfony/serializer": "~2.8|^3.0",
"symfony/validator": "~3.1",
"symfony/validator": "~3.2",
"symfony/yaml": "~3.2",
"symfony/property-info": "~2.8|~3.0",
"phpdocumentor/reflection-docblock": "^3.0",
Expand Down
Loading