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 46dd1d5

Browse filesBrowse files
committed
feature #19507 [FrameworkBundle] Introduce a cache warmer for Serializer based on PhpArrayAdapter (tgalopin)
This PR was merged into the 3.2-dev branch. Discussion ---------- [FrameworkBundle] Introduce a cache warmer for Serializer based on PhpArrayAdapter | Q | A | ------------- | --- | Branch? | master | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | - | License | MIT | Doc PR | - Following the cache warmer for annotations (#18533) and for the validator (#19485), this PR introduces a cache warmer for the Serializer YAML and XML metadata configuration (mainly groups). Based on the PhpArrayAdapter, it uses the naming conventions (Resources/config/serialization) 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 previous PR: - serialization metadata cache 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) As previous work on the Serializer cache system introduced issues (see 96e418a), it would be interesting to pay careful attention to the backward compatibility during the review (ping @Ener-Getick). Commits ------- 810f469 [FrameworkBundle] Introduce a cache warmer for Serializer based on PhpArrayAdapter
2 parents 65e254c + 810f469 commit 46dd1d5
Copy full SHA for 46dd1d5

File tree

12 files changed

+333
-28
lines changed
Filter options

12 files changed

+333
-28
lines changed
+110Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
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) || !method_exists(XmlFileLoader::class, 'getMappedClasses') || !method_exists(YamlFileLoader::class, 'getMappedClasses')) {
59+
return;
60+
}
61+
62+
$adapter = new PhpArrayAdapter($this->phpArrayFile, $this->fallbackPool);
63+
$arrayPool = new ArrayAdapter(0, false);
64+
65+
$metadataFactory = new CacheClassMetadataFactory(new ClassMetadataFactory(new LoaderChain($this->loaders)), $arrayPool);
66+
67+
foreach ($this->extractSupportedLoaders($this->loaders) as $loader) {
68+
foreach ($loader->getMappedClasses() as $mappedClass) {
69+
$metadataFactory->getMetadataFor($mappedClass);
70+
}
71+
}
72+
73+
$values = $arrayPool->getValues();
74+
$adapter->warmUp($values);
75+
76+
foreach ($values as $k => $v) {
77+
$item = $this->fallbackPool->getItem($k);
78+
$this->fallbackPool->saveDeferred($item->set($v));
79+
}
80+
$this->fallbackPool->commit();
81+
}
82+
83+
/**
84+
* {@inheritdoc}
85+
*/
86+
public function isOptional()
87+
{
88+
return true;
89+
}
90+
91+
/**
92+
* @param LoaderInterface[] $loaders
93+
*
94+
* @return XmlFileLoader[]|YamlFileLoader[]
95+
*/
96+
private function extractSupportedLoaders(array $loaders)
97+
{
98+
$supportedLoaders = array();
99+
100+
foreach ($loaders as $loader) {
101+
if ($loader instanceof XmlFileLoader || $loader instanceof YamlFileLoader) {
102+
$supportedLoaders[] = $loader;
103+
} elseif ($loader instanceof LoaderChain) {
104+
$supportedLoaders = array_merge($supportedLoaders, $this->extractSupportedLoaders($loader->getDelegatedLoaders()));
105+
}
106+
}
107+
108+
return $supportedLoaders;
109+
}
110+
}

‎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
@@ -1087,6 +1087,7 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
10871087
}
10881088

10891089
$chainLoader->replaceArgument(0, $serializerLoaders);
1090+
$container->getDefinition('serializer.mapping.cache_warmer')->replaceArgument(0, $serializerLoaders);
10901091

10911092
if (isset($config['cache']) && $config['cache']) {
10921093
@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);
@@ -1099,12 +1100,12 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
10991100
$container->getDefinition('serializer.mapping.class_metadata_factory')->replaceArgument(
11001101
1, new Reference($config['cache'])
11011102
);
1102-
} elseif (!$container->getParameter('kernel.debug')) {
1103+
} elseif (!$container->getParameter('kernel.debug') && class_exists(CacheClassMetadataFactory::class)) {
11031104
$cacheMetadataFactory = new Definition(
11041105
CacheClassMetadataFactory::class,
11051106
array(
11061107
new Reference('serializer.mapping.cache_class_metadata_factory.inner'),
1107-
new Reference('cache.serializer'),
1108+
new Reference('serializer.mapping.cache.symfony'),
11081109
)
11091110
);
11101111
$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>
+85Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
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) || !method_exists(XmlFileLoader::class, 'getMappedClasses') || !method_exists(YamlFileLoader::class, 'getMappedClasses')) {
26+
$this->markTestSkipped('The Serializer default cache warmer has been introduced in the Serializer Component version 3.2.');
27+
}
28+
29+
$loaders = array(
30+
new XmlFileLoader(__DIR__.'/../Fixtures/Serialization/Resources/person.xml'),
31+
new YamlFileLoader(__DIR__.'/../Fixtures/Serialization/Resources/author.yml'),
32+
);
33+
34+
$file = sys_get_temp_dir().'/cache-serializer.php';
35+
@unlink($file);
36+
37+
$fallbackPool = new ArrayAdapter();
38+
39+
$warmer = new SerializerCacheWarmer($loaders, $file, $fallbackPool);
40+
$warmer->warmUp(dirname($file));
41+
42+
$this->assertFileExists($file);
43+
44+
$values = require $file;
45+
46+
$this->assertInternalType('array', $values);
47+
$this->assertCount(2, $values);
48+
$this->assertArrayHasKey('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Person', $values);
49+
$this->assertArrayHasKey('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Author', $values);
50+
51+
$values = $fallbackPool->getValues();
52+
53+
$this->assertInternalType('array', $values);
54+
$this->assertCount(2, $values);
55+
$this->assertArrayHasKey('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Person', $values);
56+
$this->assertArrayHasKey('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Author', $values);
57+
}
58+
59+
public function testWarmUpWithoutLoader()
60+
{
61+
if (!class_exists(CacheClassMetadataFactory::class) || !method_exists(XmlFileLoader::class, 'getMappedClasses') || !method_exists(YamlFileLoader::class, 'getMappedClasses')) {
62+
$this->markTestSkipped('The Serializer default cache warmer has been introduced in the Serializer Component version 3.2.');
63+
}
64+
65+
$file = sys_get_temp_dir().'/cache-serializer-without-loader.php';
66+
@unlink($file);
67+
68+
$fallbackPool = new ArrayAdapter();
69+
70+
$warmer = new SerializerCacheWarmer(array(), $file, $fallbackPool);
71+
$warmer->warmUp(dirname($file));
72+
73+
$this->assertFileExists($file);
74+
75+
$values = require $file;
76+
77+
$this->assertInternalType('array', $values);
78+
$this->assertCount(0, $values);
79+
80+
$values = $fallbackPool->getValues();
81+
82+
$this->assertInternalType('array', $values);
83+
$this->assertCount(0, $values);
84+
}
85+
}

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

Copy file name to clipboardExpand all lines: src/Symfony/Bundle/FrameworkBundle/Tests/DependencyInjection/FrameworkExtensionTest.php
+16-1Lines changed: 16 additions & 1 deletion
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,16 @@ public function testObjectNormalizerRegistered()
542546

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

549561
public function testSerializerCacheDisabled()
@@ -562,7 +574,10 @@ public function testDeprecatedSerializerCacheOption()
562574
$container = $this->createContainerFromFile('serializer_legacy_cache', array('kernel.debug' => true, 'kernel.container_class' => __CLASS__));
563575

564576
$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));
577+
$this->assertTrue($container->hasDefinition('serializer.mapping.class_metadata_factory'));
578+
579+
$cache = $container->getDefinition('serializer.mapping.class_metadata_factory')->getArgument(1);
580+
$this->assertEquals(new Reference('foo'), $cache);
566581
});
567582
}
568583

+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.