[FrameworkBundle] Introduce a cache warmer for Serializer based on PhpArrayAdapter
This commit is contained in:
parent
5cc9ed2048
commit
810f4694af
@ -0,0 +1,110 @@
|
||||
<?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\Serializer\Mapping\Factory\CacheClassMetadataFactory;
|
||||
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
|
||||
use Symfony\Component\Serializer\Mapping\Loader\LoaderChain;
|
||||
use Symfony\Component\Serializer\Mapping\Loader\LoaderInterface;
|
||||
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
|
||||
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
|
||||
|
||||
/**
|
||||
* Warms up XML and YAML serializer metadata.
|
||||
*
|
||||
* @author Titouan Galopin <galopintitouan@gmail.com>
|
||||
*/
|
||||
class SerializerCacheWarmer implements CacheWarmerInterface
|
||||
{
|
||||
private $loaders;
|
||||
private $phpArrayFile;
|
||||
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.
|
||||
*/
|
||||
public function __construct(array $loaders, $phpArrayFile, CacheItemPoolInterface $fallbackPool)
|
||||
{
|
||||
$this->loaders = $loaders;
|
||||
$this->phpArrayFile = $phpArrayFile;
|
||||
if (!$fallbackPool instanceof AdapterInterface) {
|
||||
$fallbackPool = new ProxyAdapter($fallbackPool);
|
||||
}
|
||||
$this->fallbackPool = $fallbackPool;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@inheritdoc}
|
||||
*/
|
||||
public function warmUp($cacheDir)
|
||||
{
|
||||
if (!class_exists(CacheClassMetadataFactory::class) || !method_exists(XmlFileLoader::class, 'getMappedClasses') || !method_exists(YamlFileLoader::class, 'getMappedClasses')) {
|
||||
return;
|
||||
}
|
||||
|
||||
$adapter = new PhpArrayAdapter($this->phpArrayFile, $this->fallbackPool);
|
||||
$arrayPool = new ArrayAdapter(0, false);
|
||||
|
||||
$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);
|
||||
}
|
||||
}
|
||||
|
||||
$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;
|
||||
}
|
||||
}
|
@ -1067,6 +1067,7 @@ class FrameworkExtension extends Extension
|
||||
}
|
||||
|
||||
$chainLoader->replaceArgument(0, $serializerLoaders);
|
||||
$container->getDefinition('serializer.mapping.cache_warmer')->replaceArgument(0, $serializerLoaders);
|
||||
|
||||
if (isset($config['cache']) && $config['cache']) {
|
||||
@trigger_error('The "framework.serializer.cache" option is deprecated since Symfony 3.1 and will be removed in 4.0. Configure the "cache.serializer" service under "framework.cache.pools" instead.', E_USER_DEPRECATED);
|
||||
@ -1079,12 +1080,12 @@ class FrameworkExtension extends Extension
|
||||
$container->getDefinition('serializer.mapping.class_metadata_factory')->replaceArgument(
|
||||
1, new Reference($config['cache'])
|
||||
);
|
||||
} elseif (!$container->getParameter('kernel.debug')) {
|
||||
} elseif (!$container->getParameter('kernel.debug') && class_exists(CacheClassMetadataFactory::class)) {
|
||||
$cacheMetadataFactory = new Definition(
|
||||
CacheClassMetadataFactory::class,
|
||||
array(
|
||||
new Reference('serializer.mapping.cache_class_metadata_factory.inner'),
|
||||
new Reference('cache.serializer'),
|
||||
new Reference('serializer.mapping.cache.symfony'),
|
||||
)
|
||||
);
|
||||
$cacheMetadataFactory->setPublic(false);
|
||||
|
@ -5,6 +5,7 @@
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
|
||||
|
||||
<parameters>
|
||||
<parameter key="serializer.mapping.cache.file">%kernel.cache_dir%/serialization.php</parameter>
|
||||
<parameter key="serializer.mapping.cache.prefix" />
|
||||
</parameters>
|
||||
|
||||
@ -39,6 +40,19 @@
|
||||
</service>
|
||||
|
||||
<!-- Cache -->
|
||||
<service id="serializer.mapping.cache_warmer" class="Symfony\Bundle\FrameworkBundle\CacheWarmer\SerializerCacheWarmer" public="false">
|
||||
<argument type="collection" /><!-- Loaders injected by the extension -->
|
||||
<argument>%serializer.mapping.cache.file%</argument>
|
||||
<argument type="service" id="cache.serializer" />
|
||||
<tag name="kernel.cache_warmer" />
|
||||
</service>
|
||||
|
||||
<service id="serializer.mapping.cache.symfony" class="Symfony\Component\Cache\Adapter\PhpArrayAdapter">
|
||||
<factory class="Symfony\Component\Cache\Adapter\PhpArrayAdapter" method="create" />
|
||||
<argument>%serializer.mapping.cache.file%</argument>
|
||||
<argument type="service" id="cache.serializer" />
|
||||
</service>
|
||||
|
||||
<service id="serializer.mapping.cache.doctrine.apc" class="Doctrine\Common\Cache\ApcCache" public="false">
|
||||
<call method="setNamespace">
|
||||
<argument>%serializer.mapping.cache.prefix%</argument>
|
||||
|
@ -0,0 +1,85 @@
|
||||
<?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\SerializerCacheWarmer;
|
||||
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
|
||||
use Symfony\Component\Cache\Adapter\ArrayAdapter;
|
||||
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
|
||||
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
|
||||
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
|
||||
|
||||
class SerializerCacheWarmerTest extends TestCase
|
||||
{
|
||||
public function testWarmUp()
|
||||
{
|
||||
if (!class_exists(CacheClassMetadataFactory::class) || !method_exists(XmlFileLoader::class, 'getMappedClasses') || !method_exists(YamlFileLoader::class, 'getMappedClasses')) {
|
||||
$this->markTestSkipped('The Serializer default cache warmer has been introduced in the Serializer Component version 3.2.');
|
||||
}
|
||||
|
||||
$loaders = array(
|
||||
new XmlFileLoader(__DIR__.'/../Fixtures/Serialization/Resources/person.xml'),
|
||||
new YamlFileLoader(__DIR__.'/../Fixtures/Serialization/Resources/author.yml'),
|
||||
);
|
||||
|
||||
$file = sys_get_temp_dir().'/cache-serializer.php';
|
||||
@unlink($file);
|
||||
|
||||
$fallbackPool = new ArrayAdapter();
|
||||
|
||||
$warmer = new SerializerCacheWarmer($loaders, $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_Serialization_Person', $values);
|
||||
$this->assertArrayHasKey('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Author', $values);
|
||||
|
||||
$values = $fallbackPool->getValues();
|
||||
|
||||
$this->assertInternalType('array', $values);
|
||||
$this->assertCount(2, $values);
|
||||
$this->assertArrayHasKey('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Person', $values);
|
||||
$this->assertArrayHasKey('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Author', $values);
|
||||
}
|
||||
|
||||
public function testWarmUpWithoutLoader()
|
||||
{
|
||||
if (!class_exists(CacheClassMetadataFactory::class) || !method_exists(XmlFileLoader::class, 'getMappedClasses') || !method_exists(YamlFileLoader::class, 'getMappedClasses')) {
|
||||
$this->markTestSkipped('The Serializer default cache warmer has been introduced in the Serializer Component version 3.2.');
|
||||
}
|
||||
|
||||
$file = sys_get_temp_dir().'/cache-serializer-without-loader.php';
|
||||
@unlink($file);
|
||||
|
||||
$fallbackPool = new ArrayAdapter();
|
||||
|
||||
$warmer = new SerializerCacheWarmer(array(), $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);
|
||||
}
|
||||
}
|
@ -21,10 +21,14 @@ use Symfony\Component\Cache\Adapter\FilesystemAdapter;
|
||||
use Symfony\Component\Cache\Adapter\ProxyAdapter;
|
||||
use Symfony\Component\Cache\Adapter\RedisAdapter;
|
||||
use Symfony\Component\DependencyInjection\ContainerBuilder;
|
||||
use Symfony\Component\DependencyInjection\Definition;
|
||||
use Symfony\Component\DependencyInjection\DefinitionDecorator;
|
||||
use Symfony\Component\DependencyInjection\Loader\ClosureLoader;
|
||||
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
|
||||
use Symfony\Component\DependencyInjection\Reference;
|
||||
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
|
||||
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
|
||||
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
|
||||
use Symfony\Component\Serializer\Normalizer\DataUriNormalizer;
|
||||
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
|
||||
use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
|
||||
@ -542,8 +546,16 @@ abstract class FrameworkExtensionTest extends TestCase
|
||||
|
||||
public function testSerializerCacheActivated()
|
||||
{
|
||||
if (!class_exists(CacheClassMetadataFactory::class) || !method_exists(XmlFileLoader::class, 'getMappedClasses') || !method_exists(YamlFileLoader::class, 'getMappedClasses')) {
|
||||
$this->markTestSkipped('The Serializer default cache warmer has been introduced in the Serializer Component version 3.2.');
|
||||
}
|
||||
|
||||
$container = $this->createContainerFromFile('serializer_enabled');
|
||||
|
||||
$this->assertTrue($container->hasDefinition('serializer.mapping.cache_class_metadata_factory'));
|
||||
|
||||
$cache = $container->getDefinition('serializer.mapping.cache_class_metadata_factory')->getArgument(1);
|
||||
$this->assertEquals(new Reference('serializer.mapping.cache.symfony'), $cache);
|
||||
}
|
||||
|
||||
public function testSerializerCacheDisabled()
|
||||
@ -562,7 +574,10 @@ abstract class FrameworkExtensionTest extends TestCase
|
||||
$container = $this->createContainerFromFile('serializer_legacy_cache', array('kernel.debug' => true, 'kernel.container_class' => __CLASS__));
|
||||
|
||||
$this->assertFalse($container->hasDefinition('serializer.mapping.cache_class_metadata_factory'));
|
||||
$this->assertEquals(new Reference('foo'), $container->getDefinition('serializer.mapping.class_metadata_factory')->getArgument(1));
|
||||
$this->assertTrue($container->hasDefinition('serializer.mapping.class_metadata_factory'));
|
||||
|
||||
$cache = $container->getDefinition('serializer.mapping.class_metadata_factory')->getArgument(1);
|
||||
$this->assertEquals(new Reference('foo'), $cache);
|
||||
});
|
||||
}
|
||||
|
||||
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Serialization;
|
||||
|
||||
class Author
|
||||
{
|
||||
public $gender;
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
<?php
|
||||
|
||||
namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Serialization;
|
||||
|
||||
class Person
|
||||
{
|
||||
public $gender;
|
||||
}
|
@ -0,0 +1,4 @@
|
||||
Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Serialization\Author:
|
||||
attributes:
|
||||
gender:
|
||||
groups: ['group1', 'group2']
|
@ -0,0 +1,13 @@
|
||||
<?xml version="1.0" ?>
|
||||
<serializer xmlns="http://symfony.com/schema/dic/serializer-mapping"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://symfony.com/schema/dic/serializer-mapping
|
||||
http://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd"
|
||||
>
|
||||
<class name="Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Serialization\Person">
|
||||
<attribute name="gender">
|
||||
<group>group1</group>
|
||||
<group>group2</group>
|
||||
</attribute>
|
||||
</class>
|
||||
</serializer>
|
@ -47,7 +47,7 @@
|
||||
"symfony/form": "~2.8|~3.0",
|
||||
"symfony/expression-language": "~2.8|~3.0",
|
||||
"symfony/process": "~2.8|~3.0",
|
||||
"symfony/serializer": "~2.8|^3.0",
|
||||
"symfony/serializer": "~2.8|~3.0",
|
||||
"symfony/validator": "~3.1",
|
||||
"symfony/yaml": "~3.2",
|
||||
"symfony/property-info": "~2.8|~3.0",
|
||||
|
@ -36,12 +36,11 @@ class XmlFileLoader extends FileLoader
|
||||
public function loadClassMetadata(ClassMetadataInterface $classMetadata)
|
||||
{
|
||||
if (null === $this->classes) {
|
||||
$this->classes = array();
|
||||
$xml = $this->parseFile($this->file);
|
||||
$this->classes = $this->getClassesFromXml();
|
||||
}
|
||||
|
||||
foreach ($xml->class as $class) {
|
||||
$this->classes[(string) $class['name']] = $class;
|
||||
}
|
||||
if (!$this->classes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$attributesMetadata = $classMetadata->getAttributesMetadata();
|
||||
@ -74,6 +73,20 @@ class XmlFileLoader extends FileLoader
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the names of the classes mapped in this file.
|
||||
*
|
||||
* @return string[] The classes names
|
||||
*/
|
||||
public function getMappedClasses()
|
||||
{
|
||||
if (null === $this->classes) {
|
||||
$this->classes = $this->getClassesFromXml();
|
||||
}
|
||||
|
||||
return array_keys($this->classes);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a XML File.
|
||||
*
|
||||
@ -93,4 +106,16 @@ class XmlFileLoader extends FileLoader
|
||||
|
||||
return simplexml_import_dom($dom);
|
||||
}
|
||||
|
||||
private function getClassesFromXml()
|
||||
{
|
||||
$xml = $this->parseFile($this->file);
|
||||
$classes = array();
|
||||
|
||||
foreach ($xml->class as $class) {
|
||||
$classes[(string) $class['name']] = $class;
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
}
|
||||
|
@ -38,26 +38,11 @@ class YamlFileLoader extends FileLoader
|
||||
public function loadClassMetadata(ClassMetadataInterface $classMetadata)
|
||||
{
|
||||
if (null === $this->classes) {
|
||||
if (!stream_is_local($this->file)) {
|
||||
throw new MappingException(sprintf('This is not a local file "%s".', $this->file));
|
||||
}
|
||||
$this->classes = $this->getClassesFromYaml();
|
||||
}
|
||||
|
||||
if (null === $this->yamlParser) {
|
||||
$this->yamlParser = new Parser();
|
||||
}
|
||||
|
||||
$classes = $this->yamlParser->parse(file_get_contents($this->file));
|
||||
|
||||
if (empty($classes)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// not an array
|
||||
if (!is_array($classes)) {
|
||||
throw new MappingException(sprintf('The file "%s" must contain a YAML array.', $this->file));
|
||||
}
|
||||
|
||||
$this->classes = $classes;
|
||||
if (!$this->classes) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isset($this->classes[$classMetadata->getName()])) {
|
||||
@ -103,4 +88,41 @@ class YamlFileLoader extends FileLoader
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the names of the classes mapped in this file.
|
||||
*
|
||||
* @return string[] The classes names
|
||||
*/
|
||||
public function getMappedClasses()
|
||||
{
|
||||
if (null === $this->classes) {
|
||||
$this->classes = $this->getClassesFromYaml();
|
||||
}
|
||||
|
||||
return array_keys($this->classes);
|
||||
}
|
||||
|
||||
private function getClassesFromYaml()
|
||||
{
|
||||
if (!stream_is_local($this->file)) {
|
||||
throw new MappingException(sprintf('This is not a local file "%s".', $this->file));
|
||||
}
|
||||
|
||||
if (null === $this->yamlParser) {
|
||||
$this->yamlParser = new Parser();
|
||||
}
|
||||
|
||||
$classes = $this->yamlParser->parse(file_get_contents($this->file));
|
||||
|
||||
if (empty($classes)) {
|
||||
return array();
|
||||
}
|
||||
|
||||
if (!is_array($classes)) {
|
||||
throw new MappingException(sprintf('The file "%s" must contain a YAML array.', $this->file));
|
||||
}
|
||||
|
||||
return $classes;
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user