Skip to content

Commit a83ff96

Browse files
committed
[FrameworkBundle] Introduce a cache warmer for Serializer based on PhpArrayAdapter
1 parent 5cc9ed2 commit a83ff96

File tree

12 files changed

+344
-28
lines changed

12 files changed

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

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

+3-2
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
10671067
}
10681068

10691069
$chainLoader->replaceArgument(0, $serializerLoaders);
1070+
$container->getDefinition('serializer.mapping.cache_warmer')->replaceArgument(0, $serializerLoaders);
10701071

10711072
if (isset($config['cache']) && $config['cache']) {
10721073
@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 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
10791080
$container->getDefinition('serializer.mapping.class_metadata_factory')->replaceArgument(
10801081
1, new Reference($config['cache'])
10811082
);
1082-
} elseif (!$container->getParameter('kernel.debug')) {
1083+
} elseif (!$container->getParameter('kernel.debug') && class_exists(CacheClassMetadataFactory::class)) {
10831084
$cacheMetadataFactory = new Definition(
10841085
CacheClassMetadataFactory::class,
10851086
array(
10861087
new Reference('serializer.mapping.cache_class_metadata_factory.inner'),
1087-
new Reference('cache.serializer'),
1088+
new Reference('serializer.mapping.cache.symfony'),
10881089
)
10891090
);
10901091
$cacheMetadataFactory->setPublic(false);

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

+14
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
66

77
<parameters>
8+
<parameter key="serializer.mapping.cache.file">%kernel.cache_dir%/serialization.php</parameter>
89
<parameter key="serializer.mapping.cache.prefix" />
910
</parameters>
1011

@@ -39,6 +40,19 @@
3940
</service>
4041

4142
<!-- Cache -->
43+
<service id="serializer.mapping.cache_warmer" class="Symfony\Bundle\FrameworkBundle\CacheWarmer\SerializerCacheWarmer" public="false">
44+
<argument type="collection" /><!-- Loaders injected by the extension -->
45+
<argument>%serializer.mapping.cache.file%</argument>
46+
<argument type="service" id="cache.serializer" />
47+
<tag name="kernel.cache_warmer" />
48+
</service>
49+
50+
<service id="serializer.mapping.cache.symfony" class="Symfony\Component\Cache\Adapter\PhpArrayAdapter">
51+
<factory class="Symfony\Component\Cache\Adapter\PhpArrayAdapter" method="create" />
52+
<argument>%serializer.mapping.cache.file%</argument>
53+
<argument type="service" id="cache.serializer" />
54+
</service>
55+
4256
<service id="serializer.mapping.cache.doctrine.apc" class="Doctrine\Common\Cache\ApcCache" public="false">
4357
<call method="setNamespace">
4458
<argument>%serializer.mapping.cache.prefix%</argument>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
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\SerializerCacheWarmer;
15+
use Symfony\Bundle\FrameworkBundle\Tests\TestCase;
16+
use Symfony\Component\Cache\Adapter\ArrayAdapter;
17+
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
18+
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
19+
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
20+
21+
class SerializerCacheWarmerTest extends TestCase
22+
{
23+
public function testWarmUp()
24+
{
25+
if (!class_exists(CacheClassMetadataFactory::class)
26+
|| !method_exists(XmlFileLoader::class, 'getMappedClasses')
27+
|| !method_exists(YamlFileLoader::class, 'getMappedClasses')) {
28+
$this->markTestSkipped('The Serializer default cache warmer has been introduced in the Serializer Component version 3.2.');
29+
}
30+
31+
$loaders = array(
32+
new XmlFileLoader(__DIR__.'/../Fixtures/Serialization/Resources/person.xml'),
33+
new YamlFileLoader(__DIR__.'/../Fixtures/Serialization/Resources/author.yml'),
34+
);
35+
36+
$file = sys_get_temp_dir().'/cache-serializer.php';
37+
@unlink($file);
38+
39+
$fallbackPool = new ArrayAdapter();
40+
41+
$warmer = new SerializerCacheWarmer($loaders, $file, $fallbackPool);
42+
$warmer->warmUp(dirname($file));
43+
44+
$this->assertFileExists($file);
45+
46+
$values = require $file;
47+
48+
$this->assertInternalType('array', $values);
49+
$this->assertCount(2, $values);
50+
$this->assertArrayHasKey('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Person', $values);
51+
$this->assertArrayHasKey('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Author', $values);
52+
53+
$values = $fallbackPool->getValues();
54+
55+
$this->assertInternalType('array', $values);
56+
$this->assertCount(2, $values);
57+
$this->assertArrayHasKey('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Person', $values);
58+
$this->assertArrayHasKey('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Author', $values);
59+
}
60+
61+
public function testWarmUpWithoutLoader()
62+
{
63+
if (!class_exists(CacheClassMetadataFactory::class)
64+
|| !method_exists(XmlFileLoader::class, 'getMappedClasses')
65+
|| !method_exists(YamlFileLoader::class, 'getMappedClasses')) {
66+
$this->markTestSkipped('The Serializer default cache warmer has been introduced in the Serializer Component version 3.2.');
67+
}
68+
69+
$file = sys_get_temp_dir().'/cache-serializer-without-loader.php';
70+
@unlink($file);
71+
72+
$fallbackPool = new ArrayAdapter();
73+
74+
$warmer = new SerializerCacheWarmer(array(), $file, $fallbackPool);
75+
$warmer->warmUp(dirname($file));
76+
77+
$this->assertFileExists($file);
78+
79+
$values = require $file;
80+
81+
$this->assertInternalType('array', $values);
82+
$this->assertCount(0, $values);
83+
84+
$values = $fallbackPool->getValues();
85+
86+
$this->assertInternalType('array', $values);
87+
$this->assertCount(0, $values);
88+
}
89+
}

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

+18-1
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,14 @@
2121
use Symfony\Component\Cache\Adapter\ProxyAdapter;
2222
use Symfony\Component\Cache\Adapter\RedisAdapter;
2323
use Symfony\Component\DependencyInjection\ContainerBuilder;
24+
use Symfony\Component\DependencyInjection\Definition;
2425
use Symfony\Component\DependencyInjection\DefinitionDecorator;
2526
use Symfony\Component\DependencyInjection\Loader\ClosureLoader;
2627
use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
2728
use Symfony\Component\DependencyInjection\Reference;
29+
use Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory;
30+
use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
31+
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
2832
use Symfony\Component\Serializer\Normalizer\DataUriNormalizer;
2933
use Symfony\Component\Serializer\Normalizer\DateTimeNormalizer;
3034
use Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer;
@@ -542,8 +546,18 @@ public function testObjectNormalizerRegistered()
542546

543547
public function testSerializerCacheActivated()
544548
{
549+
if (!class_exists(CacheClassMetadataFactory::class)
550+
|| !method_exists(XmlFileLoader::class, 'getMappedClasses')
551+
|| !method_exists(YamlFileLoader::class, 'getMappedClasses')) {
552+
$this->markTestSkipped('The Serializer default cache warmer has been introduced in the Serializer Component version 3.2.');
553+
}
554+
545555
$container = $this->createContainerFromFile('serializer_enabled');
556+
546557
$this->assertTrue($container->hasDefinition('serializer.mapping.cache_class_metadata_factory'));
558+
559+
$cache = $container->getDefinition('serializer.mapping.cache_class_metadata_factory')->getArgument(1);
560+
$this->assertEquals(new Reference('serializer.mapping.cache.symfony'), $cache);
547561
}
548562

549563
public function testSerializerCacheDisabled()
@@ -562,7 +576,10 @@ public function testDeprecatedSerializerCacheOption()
562576
$container = $this->createContainerFromFile('serializer_legacy_cache', array('kernel.debug' => true, 'kernel.container_class' => __CLASS__));
563577

564578
$this->assertFalse($container->hasDefinition('serializer.mapping.cache_class_metadata_factory'));
565-
$this->assertEquals(new Reference('foo'), $container->getDefinition('serializer.mapping.class_metadata_factory')->getArgument(1));
579+
$this->assertTrue($container->hasDefinition('serializer.mapping.class_metadata_factory'));
580+
581+
$cache = $container->getDefinition('serializer.mapping.class_metadata_factory')->getArgument(1);
582+
$this->assertEquals(new Reference('foo'), $cache);
566583
});
567584
}
568585

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Serialization;
4+
5+
class Author
6+
{
7+
public $gender;
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Serialization;
4+
5+
class Person
6+
{
7+
public $gender;
8+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Serialization\Author:
2+
attributes:
3+
gender:
4+
groups: ['group1', 'group2']
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?xml version="1.0" ?>
2+
<serializer xmlns="http://symfony.com/schema/dic/serializer-mapping"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://symfony.com/schema/dic/serializer-mapping
5+
http://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd"
6+
>
7+
<class name="Symfony\Bundle\FrameworkBundle\Tests\Fixtures\Serialization\Person">
8+
<attribute name="gender">
9+
<group>group1</group>
10+
<group>group2</group>
11+
</attribute>
12+
</class>
13+
</serializer>

src/Symfony/Bundle/FrameworkBundle/composer.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,7 @@
4747
"symfony/form": "~2.8|~3.0",
4848
"symfony/expression-language": "~2.8|~3.0",
4949
"symfony/process": "~2.8|~3.0",
50-
"symfony/serializer": "~2.8|^3.0",
50+
"symfony/serializer": "~2.8|~3.0",
5151
"symfony/validator": "~3.1",
5252
"symfony/yaml": "~3.2",
5353
"symfony/property-info": "~2.8|~3.0",

0 commit comments

Comments
 (0)