Skip to content

Commit baa3d3d

Browse files
committed
[SerDes] Introduce component
1 parent 38b5992 commit baa3d3d

File tree

206 files changed

+13144
-1
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

206 files changed

+13144
-1
lines changed

composer.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@
8383
"symfony/ldap": "self.version",
8484
"symfony/lock": "self.version",
8585
"symfony/mailer": "self.version",
86+
"symfony/ser-des": "self.version",
8687
"symfony/messenger": "self.version",
8788
"symfony/mime": "self.version",
8889
"symfony/monolog-bridge": "self.version",
@@ -180,7 +181,8 @@
180181
"Symfony\\Component\\": "src/Symfony/Component/"
181182
},
182183
"files": [
183-
"src/Symfony/Component/String/Resources/functions.php"
184+
"src/Symfony/Component/String/Resources/functions.php",
185+
"src/Symfony/Component/SerDes/Resources/functions.php"
184186
],
185187
"classmap": [
186188
"src/Symfony/Component/Cache/Traits/ValueWrapper.php"
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Log\LoggerInterface;
15+
use Psr\Log\NullLogger;
16+
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmerInterface;
17+
use Symfony\Component\SerDes\Attribute\Serializable;
18+
use Symfony\Component\SerDes\Context\ContextBuilderInterface;
19+
use Symfony\Component\SerDes\Exception\ExceptionInterface;
20+
use Symfony\Component\SerDes\SerializableResolverInterface;
21+
use Symfony\Component\VarExporter\ProxyHelper;
22+
23+
use function Symfony\Component\SerDes\serialize_generate;
24+
25+
/**
26+
* @author Mathias Arlaud <[email protected]>
27+
*
28+
* @experimental in 6.3
29+
*/
30+
final class SerDesCacheWarmer implements CacheWarmerInterface
31+
{
32+
/**
33+
* @param iterable<ContextBuilderInterface> $contextBuilders
34+
* @param list<string> $formats
35+
*/
36+
public function __construct(
37+
private readonly SerializableResolverInterface $serializableResolver,
38+
private readonly iterable $contextBuilders,
39+
private readonly string $templateCacheDir,
40+
private readonly string $lazyObjectCacheDir,
41+
private readonly array $formats,
42+
private readonly bool $nullableData,
43+
private readonly LoggerInterface $logger = new NullLogger(),
44+
) {
45+
}
46+
47+
public function warmUp(string $cacheDir): array
48+
{
49+
if (!file_exists($this->templateCacheDir)) {
50+
mkdir($this->templateCacheDir, recursive: true);
51+
}
52+
53+
if (!file_exists($this->lazyObjectCacheDir)) {
54+
mkdir($this->lazyObjectCacheDir, recursive: true);
55+
}
56+
57+
foreach ($this->serializableResolver->resolve() as $class => $attribute) {
58+
foreach ($this->formats as $format) {
59+
$this->warmClassTemplate($class, $attribute, $format);
60+
}
61+
62+
$this->warmClassLazyObject($class);
63+
}
64+
65+
return [];
66+
}
67+
68+
public function isOptional(): bool
69+
{
70+
return true;
71+
}
72+
73+
/**
74+
* @param class-string $class
75+
*/
76+
private function warmClassTemplate(string $class, Serializable $attribute, string $format): void
77+
{
78+
if ($attribute->nullable ?? $this->nullableData) {
79+
$class = '?'.$class;
80+
}
81+
82+
if (file_exists($path = sprintf('%s%s%s.%s.php', $this->templateCacheDir, \DIRECTORY_SEPARATOR, md5($class), $format))) {
83+
return;
84+
}
85+
86+
try {
87+
$context = ['cache_dir' => $this->templateCacheDir];
88+
89+
foreach ($this->contextBuilders as $contextBuilder) {
90+
$context = $contextBuilder->buildSerializeContext($context, true);
91+
}
92+
93+
file_put_contents($path, serialize_generate($class, $format, $context));
94+
} catch (ExceptionInterface $e) {
95+
$this->logger->debug('Cannot generate template for "{class}": {exception}', ['class' => $class, 'exception' => $e]);
96+
}
97+
}
98+
99+
/**
100+
* @param class-string $class
101+
*/
102+
private function warmClassLazyObject(string $class): void
103+
{
104+
if (file_exists($path = sprintf('%s%s%s.php', $this->lazyObjectCacheDir, \DIRECTORY_SEPARATOR, md5($class)))) {
105+
return;
106+
}
107+
108+
file_put_contents($path, sprintf(
109+
'class %s%s',
110+
sprintf('%sGhost', preg_replace('/\\\\/', '', $class)),
111+
ProxyHelper::generateLazyGhost(new \ReflectionClass($class)),
112+
));
113+
}
114+
}

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/Compiler/UnusedTagsPass.php

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,9 @@ class UnusedTagsPass implements CompilerPassInterface
8888
'security.voter',
8989
'serializer.encoder',
9090
'serializer.normalizer',
91+
'ser_des.context_builder',
92+
'ser_des.hook.serialize',
93+
'ser_des.hook.deserialize',
9194
'texter.transport_factory',
9295
'translation.dumper',
9396
'translation.extractor',

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

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
use Symfony\Component\Lock\Lock;
3232
use Symfony\Component\Lock\Store\SemaphoreStore;
3333
use Symfony\Component\Mailer\Mailer;
34+
use Symfony\Component\SerDes\Serializer as SerDesSerializer;
3435
use Symfony\Component\Messenger\MessageBusInterface;
3536
use Symfony\Component\Notifier\Notifier;
3637
use Symfony\Component\PropertyAccess\PropertyAccessor;
@@ -185,6 +186,7 @@ public function getConfigTreeBuilder(): TreeBuilder
185186
$this->addHtmlSanitizerSection($rootNode, $enableIfStandalone);
186187
$this->addWebhookSection($rootNode, $enableIfStandalone);
187188
$this->addRemoteEventSection($rootNode, $enableIfStandalone);
189+
$this->addSerDesSection($rootNode, $enableIfStandalone);
188190

189191
return $treeBuilder;
190192
}
@@ -2388,4 +2390,39 @@ private function addHtmlSanitizerSection(ArrayNodeDefinition $rootNode, callable
23882390
->end()
23892391
;
23902392
}
2393+
2394+
private function addSerDesSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void
2395+
{
2396+
$rootNode
2397+
->children()
2398+
->arrayNode('ser_des')
2399+
->info('SerDes configuration')
2400+
->{$enableIfStandalone('symfony/ser-des', SerDesSerializer::class)}()
2401+
->fixXmlConfig('serializable_path')
2402+
->children()
2403+
->arrayNode('serializable_paths')
2404+
->info('Defines where to find serializable classes.')
2405+
->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()
2406+
->prototype('scalar')->end()
2407+
->end()
2408+
->arrayNode('template_warm_up')
2409+
->addDefaultsIfNotSet()
2410+
->fixXmlConfig('format')
2411+
->children()
2412+
->arrayNode('formats')
2413+
->info('Defines the formats that will be handled.')
2414+
->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()
2415+
->prototype('scalar')->end()
2416+
->end()
2417+
->booleanNode('nullable_data')
2418+
->info('Defines if nullable data should be handled by default.')
2419+
->defaultFalse()
2420+
->end()
2421+
->end()
2422+
->end()
2423+
->end()
2424+
->end()
2425+
->end()
2426+
;
2427+
}
23912428
}

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

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
use Doctrine\Common\Annotations\Reader;
1616
use Http\Client\HttpAsyncClient;
1717
use Http\Client\HttpClient;
18+
use Symfony\Component\SerDes\Context\ContextBuilderInterface;
19+
use Symfony\Component\SerDes\SerializerInterface as SerDesSerializerInterface;
1820
use phpDocumentor\Reflection\DocBlockFactoryInterface;
1921
use phpDocumentor\Reflection\Types\ContextFactory;
2022
use PhpParser\Parser;
@@ -597,6 +599,10 @@ public function load(array $configs, ContainerBuilder $container)
597599
$this->registerHtmlSanitizerConfiguration($config['html_sanitizer'], $container, $loader);
598600
}
599601

602+
if ($this->readConfigEnabled('ser_des', $container, $config['ser_des'])) {
603+
$this->registerSerDesConfiguration($config['ser_des'], $container, $loader);
604+
}
605+
600606
$this->addAnnotatedClassesToCompile([
601607
'**\\Controller\\',
602608
'**\\Entity\\',
@@ -689,6 +695,8 @@ public function load(array $configs, ContainerBuilder $container)
689695
->addTag('mime.mime_type_guesser');
690696
$container->registerForAutoconfiguration(LoggerAwareInterface::class)
691697
->addMethodCall('setLogger', [new Reference('logger')]);
698+
$container->registerForAutoconfiguration(ContextBuilderInterface::class)
699+
->addTag('ser_des.context_builder');
692700

693701
$container->registerAttributeForAutoconfiguration(AsEventListener::class, static function (ChildDefinition $definition, AsEventListener $attribute, \ReflectionClass|\ReflectionMethod $reflector) {
694702
$tagAttributes = get_object_vars($attribute);
@@ -2992,6 +3000,19 @@ private function registerHtmlSanitizerConfiguration(array $config, ContainerBuil
29923000
}
29933001
}
29943002

3003+
private function registerSerDesConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
3004+
{
3005+
if (!interface_exists(SerDesSerializerInterface::class)) {
3006+
throw new LogicException('SerDes serializer support cannot be enabled as the SerDes component is not installed. Try running "composer require symfony/ser-des');
3007+
}
3008+
3009+
$container->setParameter('ser_des.serializable_paths', $config['serializable_paths']);
3010+
$container->setParameter('ser_des.template_warm_up.formats', $config['template_warm_up']['formats']);
3011+
$container->setParameter('ser_des.template_warm_up.nullable_data', $config['template_warm_up']['nullable_data']);
3012+
3013+
$loader->load('ser_des.php');
3014+
}
3015+
29953016
private function resolveTrustedHeaders(array $headers): int
29963017
{
29973018
$trustedHeaders = 0;

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,7 @@
5656
use Symfony\Component\HttpKernel\DependencyInjection\RemoveEmptyControllerArgumentLocatorsPass;
5757
use Symfony\Component\HttpKernel\DependencyInjection\ResettableServicePass;
5858
use Symfony\Component\HttpKernel\KernelEvents;
59+
use Symfony\Component\SerDes\DependencyInjection\SerDesPass;
5960
use Symfony\Component\Messenger\DependencyInjection\MessengerPass;
6061
use Symfony\Component\Mime\DependencyInjection\AddMimeTypeGuesserPass;
6162
use Symfony\Component\PropertyInfo\DependencyInjection\PropertyInfoPass;
@@ -173,6 +174,7 @@ public function build(ContainerBuilder $container)
173174
$container->addCompilerPass(new RegisterReverseContainerPass(true));
174175
$container->addCompilerPass(new RegisterReverseContainerPass(false), PassConfig::TYPE_AFTER_REMOVING);
175176
$container->addCompilerPass(new RemoveUnusedSessionMarshallingHandlerPass());
177+
$this->addCompilerPassIfExists($container, SerDesPass::class);
176178

177179
if ($container->getParameter('kernel.debug')) {
178180
$container->addCompilerPass(new EnableLoggerDebugModePass(), PassConfig::TYPE_BEFORE_OPTIMIZATION, -33);

src/Symfony/Bundle/FrameworkBundle/Resources/config/cache.php

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,11 @@
7676
->private()
7777
->tag('cache.pool')
7878

79+
->set('cache.ser_des')
80+
->parent('cache.system')
81+
->private()
82+
->tag('cache.pool')
83+
7984
->set('cache.adapter.system', AdapterInterface::class)
8085
->abstract()
8186
->factory([AbstractAdapter::class, 'createSystemCache'])

0 commit comments

Comments
 (0)