8000 [FrameworkBundle] Introduce a cache warmer for Serializer based on Ph… · symfony/symfony@140ef34 · GitHub
[go: up one dir, main page]

Skip to content

Commit 140ef34

Browse files
committed
[FrameworkBundle] Introduce a cache warmer for Serializer based on PhpArrayAdapter
1 parent 983b560 commit 140ef34

File tree

12 files changed

+330
-32
lines changed

12 files changed

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

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

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
use Doctrine\Common\Annotations\Reader;
1515
use Symfony\Component\Cache\Adapter\AdapterInterface;
16+
use Symfony\Component\Cache\DoctrineProvider;
1617
use Symfony\Component\DependencyInjection\ContainerBuilder;
1718
use Symfony\Component\DependencyInjection\ContainerInterface;
1819
use Symfony\Component\DependencyInjection\Definition;
@@ -1067,24 +1068,34 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
10671068
}
10681069

10691070
$chainLoader->replaceArgument(0, $serializerLoaders);
1071+
$container->getDefinition('serializer.mapping.cache_warmer')->replaceArgument(0, $serializerLoaders);
1072+
1073+
$cache = null;
10701074

10711075
if (isset($config['cache']) && $config['cache']) {
10721076
@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);
10731077

1078+
$cache = new Definition(DoctrineProvider::class, array(new Reference($config['cache'])));
1079+
$cache->setPublic(false);
1080+
} elseif (!$container->getParameter('kernel.debug')) {
1081+
$cache = new Reference('serializer.mapping.cache.symfony');
1082+
1083+
$this->addClassesToCompile(array(
1084+
'Symfony\Component\Cache\Adapter\PhpArrayAdapter',
1085+
));
1086+
}
1087+
1088+
if ($cache) {
10741089
$container->setParameter(
10751090
'serializer.mapping.cache.prefix',
10761091
'serializer_'.$this->getKernelRootHash($container)
10771092
);
10781093

1079-
$container->getDefinition('serializer.mapping.class_metadata_factory')->replaceArgument(
1080-
1, new Reference($config['cache'])
1081-
);
1082-
} elseif (!$container->getParameter('kernel.debug')) {
10831094
$cacheMetadataFactory = new Definition(
10841095
CacheClassMetadataFactory::class,
10851096
array(
10861097
new Reference('serializer.mapping.cache_class_metadata_factory.inner'),
1087-
new Reference('cache.serializer'),
1098+
$cache,
10881099
)
10891100
);
10901101
$cacheMetadataFactory->setPublic(false);

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

Lines 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>
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
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\Loader\XmlFileLoader;
18+
use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
19+
20+
class SerializerCacheWarmerTest extends TestCase
21+
{
22+
public function testWarmUp()
23+
{
24+
$loaders = array(
25+
new XmlFileLoader(__DIR__.'/../Fixtures/Serialization/Resources/person.xml'),
26+
new YamlFileLoader(__DIR__.'/../Fixtures/Serialization/Resources/author.yml'),
27+
);
28+
29+
$file = sys_get_temp_dir().'/cache-serializer.php';
30+
@unlink($file);
31+
32+
$fallbackPool = new ArrayAdapter();
33+
34+
$warmer = new SerializerCacheWarmer($loaders, $file, $fallbackPool);
35+
$warmer->warmUp(dirname($file));
36+
37+
$this->assertFileExists($file);
38+
39+
$values = require $file;
40+
41+
$this->assertInternalType('array', $values);
42+
$this->assertCount(2, $values);
43+
$this->assertArrayHasKey('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Person', $values);
44+
$this->assertArrayHasKey('Symfony_Bundle_FrameworkBundle_Tests_Fixtures_Serialization_Author', $values);
45+
46+
$values = $fallbackPool->getValues();
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+
54+
public function testWarmUpWithoutLoader()
55+
{
56+
$file = sys_get_temp_dir().'/cache-serializer-without-loader.php';
57+
@unlink($file);
58+
59+
$fallbackPool = new ArrayAdapter();
60+
61+
$warmer = new SerializerCacheWarmer(array(), $file, $fallbackPool);
62+
$warmer->warmUp(dirname($file));
63+
64+
$this->assertFileExists($file);
65+
66+
$values = require $file;
67+
68+
$this->assertInternalType('array', $values);
69+
$this->assertCount(0, $values);
70+
71+
$values = $fallbackPool->getValues();
72+
73+
$this->assertInternalType('array', $values);
74+
$this->assertCount(0, $values);
75+
}
76+
}

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

Lines changed: 9 additions & 2 deletions
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;
@@ -543,7 +544,11 @@ public function testObjectNormalizerRegistered()
543544
public function testSerializerCacheActivated()
544545
{
545546
$container = $this->createContainerFromFile('serializer_enabled');
547+
546548
$this->assertTrue($container->hasDefinition('serializer.mapping.cache_class_metadata_factory'));
549+
550+
$cache = $container->getDefinition('serializer.mapping.cache_class_metadata_factory')->getArgument(1);
551+
$this->assertEquals(new Reference('serializer.mapping.cache.symfony'), $cache);
547552
}
548553

549554
public function testSerializerCacheDisabled()
@@ -561,8 +566,10 @@ public function testDeprecatedSerializerCacheOption()
561566
ErrorAssert::assertDeprecationsAreTriggered('The "framework.serializer.cache" option is deprecated', function () {
562567
$container = $this->createContainerFromFile('serializer_legacy_cache', array('kernel.debug' => true, 'kernel.container_class' => __CLASS__));
563568

564-
$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));
569+
$this->assertTrue($container->hasDefinition('serializer.mapping.cache_class_metadata_factory'));
570+
571+
$cache = $container->getDefinition('serializer.mapping.cache_class_metadata_factory')->getArgument(1);
572+
$this->assertEquals(new Reference('foo'), $cache->getArguments()[0]);
566573
});
567574
}
568575

Lines 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+
}
Lines 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+
}
Lines 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']
Lines 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

Lines 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": "~3.2",
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

Lines 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