10000 [Serializer] Add CompiledClassMetadataFactory · symfony/symfony@63cbf0a · GitHub
[go: up one dir, main page]

Skip to content

Commit 63cbf0a

Browse files
fbourigaultfabpot
authored andcommitted
[Serializer] Add CompiledClassMetadataFactory
1 parent e29d230 commit 63cbf0a

11 files changed

+510
-1
lines changed

.php_cs.dist

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,8 @@ return PhpCsFixer\Config::create()
3636
->notPath('#Symfony/Bridge/PhpUnit/.*Legacy#')
3737
// file content autogenerated by `var_export`
3838
->notPath('Symfony/Component/Translation/Tests/fixtures/resources.php')
39+
// file content autogenerated by `VarExporter::export`
40+
->notPath('Symfony/Component/Serializer/Tests/Fixtures/serializer.class.metadata.php')
3941
// test template
4042
->notPath('Symfony/Bundle/FrameworkBundle/Tests/Templating/Helper/Resources/Custom/_name_entry_label.html.php')
4143
// explicit trigger_error tests

src/Symfony/Component/Serializer/CHANGELOG.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,11 @@
11
CHANGELOG
22
=========
33

4+
5.2.0
5+
-----
6+
7+
* added `CompiledClassMetadataFactory` and `ClassMetadataFactoryCompiler` for faster metadata loading.
8+
49
5.1.0
510
-----
611

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
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\Component\Serializer\CacheWarmer;
13+
14+
use Symfony\Component\Filesystem\Filesystem;
15+
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
16+
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryCompiler;
17+
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
18+
19+
/**
20+
* @author Fabien Bourigault <bourigaultfabien@gmail.com>
21+
*/
22+
final class CompiledClassMetadataCacheWarmer implements CacheWarmerInterface
23+
{
24+
private $classesToCompile;
25+
26+
private $classMetadataFactory;
27+
28+
private $classMetadataFactoryCompiler;
29+
30+
private $filesystem;
31+
32+
public function __construct(array $classesToCompile, ClassMetadataFactoryInterface $classMetadataFactory, ClassMetadataFactoryCompiler $classMetadataFactoryCompiler, Filesystem $filesystem)
33+
{
34+
$this->classesToCompile = $classesToCompile;
35+
$this->classMetadataFactory = $classMetadataFactory;
36+
$this->classMetadataFactoryCompiler = $classMetadataFactoryCompiler;
37+
$this->filesystem = $filesystem;
38+
}
39+
40+
/**
41+
* {@inheritdoc}
42+
*/
43+
public function warmUp($cacheDir)
44+
{
45+
$metadatas = [];
46+
47+
foreach ($this->classesToCompile as $classToCompile) {
48+
$metadatas[] = $this->classMetadataFactory->getMetadataFor($classToCompile);
49+
}
50+
51+
$code = $this->classMetadataFactoryCompiler->compile($metadatas);
52+
53+
$this->filesystem->dumpFile("{$cacheDir}/serializer.class.metadata.php", $code);
54+
}
55+
56+
/**
57+
* {@inheritdoc}
58+
*/
59+
public function isOptional()
60+
{
61+
return true;
62+
}
63+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
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\Component\Serializer\Mapping\Factory;
13+
14+
use Symfony\Component\Serializer\Mapping\ClassMetadataInterface;
15+
use Symfony\Component\VarExporter\VarExporter;
16+
17+
/**
18+
* @author Fabien Bourigault <bourigaultfabien@gmail.com>
19+
*/
20+
final class ClassMetadataFactoryCompiler
21+
{
22+
/**
23+
* @param ClassMetadataInterface[] $classMetadatas
24+
*/
25+
public function compile(array $classMetadatas): string
26+
{
27+
return <<<EOF
28+
<?php
29+
30+
// This file has been auto-generated by the Symfony Serializer Component.
31+
32+
return [{$this->generateDeclaredClassMetadata($classMetadatas)}
33+
];
34+
EOF;
35+
}
36+
37+
/**
38+
* @param ClassMetadataInterface[] $classMetadatas
39+
*/
40+
private function generateDeclaredClassMetadata(array $classMetadatas): string
41+
{
42+
$compiled = '';
43+
44+
foreach ($classMetadatas as $classMetadata) {
45+
$attributesMetadata = [];
46+
foreach ($classMetadata->getAttributesMetadata() as $attributeMetadata) {
47+
$attributesMetadata[$attributeMetadata->getName()] = [
48+
$attributeMetadata->getGroups(),
49+
$attributeMetadata->getMaxDepth(),
50+
$attributeMetadata->getSerializedName(),
51+
];
52+
}
53+
54+
$classDiscriminatorMapping = $classMetadata->getClassDiscriminatorMapping() ? [
55+
$classMetadata->getClassDiscriminatorMapping()->getTypeProperty(),
56+
$classMetadata->getClassDiscriminatorMapping()->getTypesMapping(),
57+
] : null;
58+
59+
$compiled .= sprintf("\n'%s' => %s,", $classMetadata->getName(), VarExporter::export([
60+
$attributesMetadata,
61+
$classDiscriminatorMapping,
62+
]));
63+
}
64+
65+
return $compiled;
66+
}
67+
}
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
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\Component\Serializer\Mapping\Factory;
13+
14+
use Symfony\Component\Serializer\Mapping\AttributeMetadata;
15+
use Symfony\Component\Serializer\Mapping\ClassDiscriminatorMapping;
16+
use Symfony\Component\Serializer\Mapping\ClassMetadata;
17+
18+
/**
19+
* @author Fabien Bourigault <bourigaultfabien@gmail.com>
20+
*/
21+
final class CompiledClassMetadataFactory implements ClassMetadataFactoryInterface
22+
{
23+
private $compiledClassMetadata = [];
24+
25+
private $loadedClasses = [];
26+
27+
private $classMetadataFactory;
28+
29+
public function __construct(string $compiledClassMetadataFile, ClassMetadataFactoryInterface $classMetadataFactory)
30+
{
31+
if (!file_exists($compiledClassMetadataFile)) {
32+
throw new \RuntimeException("File \"{$compiledClassMetadataFile}\" could not be found.");
33+
}
34+
35+
$compiledClassMetadata = require $compiledClassMetadataFile;
36+
if (!\is_array($compiledClassMetadata)) {
37+
throw new \RuntimeException(sprintf('Compiled metadata must be of the type array, %s given.', \gettype($compiledClassMetadata)));
38+
}
39+
40+
$this->compiledClassMetadata = $compiledClassMetadata;
41+
$this->classMetadataFactory = $classMetadataFactory;
42+
}
43+
44+
/**
45+
* {@inheritdoc}
46+
*/
47+
public function getMetadataFor($value)
48+
{
49+
$className = \is_object($value) ? \get_class($value) : $value;
50+
51+
if (!isset($this->compiledClassMetadata[$className])) {
52+
return $this->classMetadataFactory->getMetadataFor($value);
53+
}
54+
55+
if (!isset($this->loadedClasses[$className])) {
56+
$classMetadata = new ClassMetadata($className);
57+
foreach ($this->compiledClassMetadata[$className][0] as $name => $compiledAttributesMetadata) {
58+
$classMetadata->attributesMetadata[$name] = $attributeMetadata = new AttributeMetadata($name);
59+
[$attributeMetadata->groups, $attributeMetadata->maxDepth, $attributeMetadata->serializedName] = $compiledAttributesMetadata;
60+
}
61+
$classMetadata->classDiscriminatorMapping = $this->compiledClassMetadata[$className][1]
62+
? new ClassDiscriminatorMapping(...$this->compiledClassMetadata[$className][1])
63+
: null
64+
;
65+
66+
$this->loadedClasses[$className] = $classMetadata;
67+
}
68+
69+
return $this->loadedClasses[$className];
70+
}
71+
72+
/**
73+
* {@inheritdoc}
74+
*/
75+
public function hasMetadataFor($value)
76+
{
77+
$className = \is_object($value) ? \get_class($value) : $value;
78+
79+
return isset($this->compiledClassMetadata[$className]) || $this->classMetadataFactory->hasMetadataFor($value);
80+
}
81+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
<?php
2+
3+
namespace Symfony\Component\Serializer\Tests\CacheWarmer;
4+
5+
use PHPUnit\Framework\TestCase;
6+
use Symfony\Component\Filesystem\Filesystem;
7+
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
8+
use Symfony\Component\Serializer\CacheWarmer\CompiledClassMetadataCacheWarmer;
9+
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryCompiler;
10+
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactoryInterface;
11+
12+
final class CompiledClassMetadataCacheWarmerTest extends TestCase
13+
{
14+
public function testItImplementsCacheWarmerInterface()
15+
{
16+
$classMetadataFactory = $this->createMock(ClassMetadataFactoryInterface::class);
17+
$filesystem = $this->createMock(Filesystem::class);
18+
19+
$compiledClassMetadataCacheWarmer = new CompiledClassMetadataCacheWarmer([], $classMetadataFactory, new ClassMetadataFactoryCompiler(), $filesystem);
20+
21+
$this->assertInstanceOf(CacheWarmerInterface::class, $compiledClassMetadataCacheWarmer);
22+
}
23+
24+
public function testItIsAnOptionalCacheWarmer()
25+
{
26+
$classMetadataFactory = $this->createMock(ClassMetadataFactoryInterface::class);
27+
$filesystem = $this->createMock(Filesystem::class);
28+
29+
$compiledClassMetadataCacheWarmer = new CompiledClassMetadataCacheWarmer([], $classMetadataFactory, new ClassMetadataFactoryCompiler(), $filesystem);
30+
31+
$this->assertTrue($compiledClassMetadataCacheWarmer->isOptional());
32+
}
33+
34+
public function testItDumpCompiledClassMetadatas()
35+
{
36+
$classMetadataFactory = $this->createMock(ClassMetadataFactoryInterface::class);
37+
38+
$code = <<<EOF
39+
<?php
40+
41+
// This file has been auto-generated by the Symfony Serializer Component.
42+
43+
return [
44+
];
45+
EOF;
46+
47+
$filesystem = $this->createMock(Filesystem::class);
48+
$filesystem
49+
->expects($this->once())
50+
->method('dumpFile')
51+
->with('/var/cache/prod/serializer.class.metadata.php', $code)
52+
;
53+
54+
$compiledClassMetadataCacheWarmer = new CompiledClassMetadataCacheWarmer([], $classMetadataFactory, new ClassMetadataFactoryCompiler(), $filesystem);
55+
56+
$compiledClassMetadataCacheWarmer->warmUp('/var/cache/prod');
57+
}
58+
}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<?php
2+
3+
return new DateTime();
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
<?php
2+
3+
// This file has been auto-generated by the Symfony Serializer Component.
4+
5+
return [
6+
'Symfony\Component\Serializer\Tests\Fixtures\Dummy' => [
7+
[
8+
'foo' => [[], null, null],
9+
'bar' => [[], null, null],
10+
'baz' => [[], null, null],
11+
'qux' => [[], null, null],
12+
],
13+
null,
14+
],
15+
];
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
1+
<?php
2+
3+
namespace Symfony\Component\Serializer\Tests\Mapping\Factory;
4+
5+
use Doctrine\Common\Annotations\AnnotationReader;
6+
use PHPUnit\Framework\TestCase;
7+
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
8+
use Symfony\Component\Serializer\< 10000 span class=pl-v>Mapping\Factory\ClassMetadataFactoryCompiler;
9+
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
10+
use Symfony\Component\Serializer\Tests\Fixtures\Dummy;
11+
use Symfony\Component\Serializer\Tests\Fixtures\MaxDepthDummy;
12+
use Symfony\Component\Serializer\Tests\Fixtures\SerializedNameDummy;
13+
14+
final class ClassMetadataFactoryCompilerTest extends TestCase
15+
{
16+
/**
17+
* @var string
18+
*/
19+
private $dumpPath;
20+
21+
protected function setUp()
22+
{
23+
$this->dumpPath = sys_get_temp_dir().\DIRECTORY_SEPARATOR.'php_serializer_metadata.'.uniqid('CompiledClassMetadataFactory').'.php';
24+
}
25+
26+
protected function tearDown()
27+
{
28+
@unlink($this->dumpPath);
29+
}
30+
31+
public function testItDumpMetadata()
32+
{
33+
$classMetatadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
34+
35+
$dummyMetadata = $classMetatadataFactory->getMetadataFor(Dummy::class);
36+
$maxDepthDummyMetadata = $classMetatadataFactory->getMetadataFor(MaxDepthDummy::class);
37+
$serializedNameDummyMetadata = $classMetatadataFactory->getMetadataFor(SerializedNameDummy::class);
38+
39+
$code = (new ClassMetadataFactoryCompiler())->compile([
40+
$dummyMetadata,
41+
$maxDepthDummyMetadata,
42+
$serializedNameDummyMetadata,
43+
]);
44+
45+
file_put_contents($this->dumpPath, $code);
46+
$compiledMetadata = require $this->dumpPath;
47+
48+
$this->assertCount(3, $compiledMetadata);
49+
50+
$this->assertArrayHasKey(Dummy::class, $compiledMetadata);
51+
$this->assertEquals([
52+
[
53+
'foo' => [[], null, null],
54+
'bar' => [[], null, null],
55+
'baz' => [[], null, null],
56+
'qux' => [[], null, null],
57+
],
58+
null,
59+
], $compiledMetadata[Dummy::class]);
60+
61+
$this->assertArrayHasKey(MaxDepthDummy::class, $compiledMetadata);
62+
$this->assertEquals([
63+
[
64+
'foo' => [[], 2, null],
65+
'bar' => [[], 3, null],
66+
'child' => [[], null, null],
67+
],
68+
null,
69+
], $compiledMetadata[MaxDepthDummy::class]);
70+
71+
$this->assertArrayHasKey(SerializedNameDummy::class, $compiledMetadata);
72+
$this->assertEquals([
73+
[
74+
'foo' => [[], null, 'baz'],
75+
'bar' => [[], null, 'qux'],
76+
'quux' => [[], null, null],
77+
'child' => [[], null, null],
78+
],
79+
null,
80+
], $compiledMetadata[SerializedNameDummy::class]);
81+
}
82+
}

0 commit comments

Comments
 (0)
0