Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit 37fbb30

Browse filesBrowse files
committed
[FrameworkBundle] Introduce a cache warmer for Serializer based on PhpArrayAdapter
1 parent 5cc9ed2 commit 37fbb30
Copy full SHA for 37fbb30

File tree

12 files changed

+341
-28
lines changed
Filter options

12 files changed

+341
-28
lines changed
+115Lines changed: 115 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php
+3-2Lines changed: 3 additions & 2 deletions
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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.xml
+14Lines changed: 14 additions & 0 deletions
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>
+89Lines changed: 89 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
+15-1Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
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;
@@ -542,8 +543,18 @@ public function testObjectNormalizerRegistered()
542543

543544
public function testSerializerCacheActivated()
544545
{
546+
if (!class_exists('Symfony\Component\Serializer\Mapping\Factory\CacheClassMetadataFactory')
547+
|| !method_exists('Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader', 'getMappedClasses')
548+
|| !method_exists('Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader', 'getMappedClasses')) {
549+
$this->markTestSkipped('The Serializer default cache warmer has been introduced in the Serializer Component version 3.2.');
550+
}
551+
545552
$container = $this->createContainerFromFile('serializer_enabled');
553+
546554
$this->assertTrue($container->hasDefinition('serializer.mapping.cache_class_metadata_factory'));
555+
556+
$cache = $container->getDefinition('serializer.mapping.cache_class_metadata_factory')->getArgument(1);
557+
$this->assertEquals(new Reference('serializer.mapping.cache.symfony'), $cache);
547558
}
548559

549560
public function testSerializerCacheDisabled()
@@ -562,7 +573,10 @@ public function testDeprecatedSerializerCacheOption()
562573
$container = $this->createContainerFromFile('serializer_legacy_cache', array('kernel.debug' => true, 'kernel.container_class' => __CLASS__));
563574

564575
$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));
576+
$this->assertTrue($container->hasDefinition('serializer.mapping.class_metadata_factory'));
577+
578+
$cache = $container->getDefinition('serializer.mapping.class_metadata_factory')->getArgument(1);
579+
$this->assertEquals(new Reference('foo'), $cache);
566580
});
567581
}
568582

+8Lines 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\Serialization;
4+
5+
class Author
6+
{
7+
public $gender;
8+
}
+8Lines 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\Serialization;
4+
5+
class Person
6+
{
7+
public $gender;
8+
}
+4Lines changed: 4 additions & 0 deletions
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']
+13Lines changed: 13 additions & 0 deletions
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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/composer.json
+1-1Lines changed: 1 addition & 1 deletion
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",

‎src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php

Copy file name to clipboardExpand all lines: src/Symfony/Component/Serializer/Mapping/Loader/XmlFileLoader.php
+30-5Lines changed: 30 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -36,12 +36,11 @@ class XmlFileLoader extends FileLoader
3636
public function loadClassMetadata(ClassMetadataInterface $classMetadata)
3737
{
3838
if (null === $this->classes) {
39-
$this->classes = array();
40-
$xml = $this->parseFile($this->file);
39+
$this->classes = $this->getClassesFromXml();
40+
}
4141

42-
foreach ($xml->class as $class) {
43-
$this->classes[(string) $class['name']] = $class;
44-
}
42+
if (!$this->classes) {
43+
return false;
4544
}
4645

4746
$attributesMetadata = $classMetadata->getAttributesMetadata();
@@ -74,6 +73,20 @@ public function loadClassMetadata(ClassMetadataInterface $classMetadata)
7473
return false;
7574
}
7675

76+
/**
77+
* Return the names of the classes mapped in this file.
78+
*
79+
* @return string[] The classes names
80+
*/
81+
public function getMappedClasses()
82+
{
83+
if (null === $this->classes) {
84+
$this->classes = $this->getClassesFromXml();
85+
}
86+
87+
return array_keys($this->classes);
88+
}
89+
7790
/**
7891
* Parses a XML File.
7992
*
@@ -93,4 +106,16 @@ private function parseFile($file)
93106

94107
return simplexml_import_dom($dom);
95108
}
109+
110+
private function getClassesFromXml()
111+
{
112+
$xml = $this->parseFile($this->file);
113+
$classes = array();
114+
115+
foreach ($xml->class as $class) {
116+
$classes[(string) $class['name']] = $class;
117+
}
118+
119+
return $classes;
120+
}
96121
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.