Skip to content

Commit 5a31b97

Browse files
committed
[Marshaller] Introduce component
1 parent 38b5992 commit 5a31b97

File tree

489 files changed

+13471
-5
lines changed

Some content is hidden

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

489 files changed

+13471
-5
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/marshaller": "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/Marshaller/Resources/functions.php"
184186
],
185187
"classmap": [
186188
"src/Symfony/Component/Cache/Traits/ValueWrapper.php"
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
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\Marshaller\Attribute\Marshallable;
18+
use Symfony\Component\Marshaller\Context\ContextBuilderInterface;
19+
use Symfony\Component\Marshaller\Exception\ExceptionInterface;
20+
use Symfony\Component\Marshaller\MarshallableResolverInterface;
21+
use Symfony\Component\VarExporter\ProxyHelper;
22+
23+
use function Symfony\Component\Marshaller\marshal_generate;
24+
25+
/**
26+
* @author Mathias Arlaud <[email protected]>
27+
*/
28+
final class MarshallerCacheWarmer implements CacheWarmerInterface
29+
{
30+
/**
31+
* @param iterable<ContextBuilderInterface> $contextBuilders
32+
* @param list<string> $formats
33+
*/
34+
public function __construct(
35+
private readonly MarshallableResolverInterface $marshallableResolver,
36+
private readonly iterable $contextBuilders,
37+
private readonly string $templateCacheDir,
38+
private readonly string $lazyObjectCacheDir,
39+
private readonly array $formats,
40+
private readonly bool $nullableData,
41+
private readonly LoggerInterface $logger = new NullLogger(),
42+
) {
43+
}
44+
45+
public function warmUp(string $cacheDir): array
46+
{
47+
if (!file_exists($this->templateCacheDir)) {
48+
mkdir($this->templateCacheDir, recursive: true);
49+
}
50+
51+
if (!file_exists($this->lazyObjectCacheDir)) {
52+
mkdir($this->lazyObjectCacheDir, recursive: true);
53+
}
54+
55+
foreach ($this->marshallableResolver->resolve() as $class => $attribute) {
56+
foreach ($this->formats as $format) {
57+
$this->warmClassTemplate($class, $attribute, $format);
58+
}
59+
60+
$this->warmClassLazyObject($class);
61+
}
62+
63+
return [];
64+
}
65+
66+
public function isOptional(): bool
67+
{
68+
return true;
69+
}
70+
71+
/**
72+
* @param class-string $class
73+
*/
74+
private function warmClassTemplate(string $class, Marshallable $attribute, string $format): void
75+
{
76+
if ($attribute->nullable ?? $this->nullableData) {
77+
$class = '?'.$class;
78+
}
79+
80+
if (file_exists($path = sprintf('%s%s%s.%s.php', $this->templateCacheDir, \DIRECTORY_SEPARATOR, md5($class), $format))) {
81+
return;
82+
}
83+
84+
try {
85+
$context = ['cache_dir' => $this->templateCacheDir];
86+
87+
foreach ($this->contextBuilders as $contextBuilder) {
88+
$context = $contextBuilder->buildMarshalContext($context, true);
89+
}
90+
91+
file_put_contents($path, marshal_generate($class, $format, $context));
92+
} catch (ExceptionInterface $e) {
93+
$this->logger->debug('Cannot generate template for "{class}": {exception}', ['class' => $class, 'exception' => $e]);
94+
}
95+
}
96+
97+
/**
98+
* @param class-string $class
99+
*/
100+
private function warmClassLazyObject(string $class): void
101+
{
102+
if (file_exists($path = sprintf('%s%s%s.php', $this->lazyObjectCacheDir, \DIRECTORY_SEPARATOR, md5($class)))) {
103+
return;
104+
}
105+
106+
file_put_contents($path, sprintf(
107+
'class %s%s',
108+
sprintf('%sGhost', preg_replace('/\\\\/', '', $class)),
109+
ProxyHelper::generateLazyGhost(new \ReflectionClass($class)),
110+
));
111+
}
112+
}

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

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ class UnusedTagsPass implements CompilerPassInterface
6262
'kernel.reset',
6363
'ldap',
6464
'mailer.transport_factory',
65+
'marshaller.context_builder',
66+
'marshaller.hook.marshal',
67+
'marshaller.hook.unmarshal',
6568
'messenger.bus',
6669
'messenger.message_handler',
6770
'messenger.receiver',

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\Marshaller\Marshaller;
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->addMarshallerSection($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 addMarshallerSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void
2395+
{
2396+
$rootNode
2397+
->children()
2398+
->arrayNode('marshaller')
2399+
->info('Marshaller configuration')
2400+
->{$enableIfStandalone('symfony/marshaller', Marshaller::class)}()
2401+
->fixXmlConfig('marshallable_path')
2402+
->children()
2403+
->arrayNode('marshallable_paths')
2404+
->info('Defines where the marshaller should look to find marshallable 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: 25 additions & 4 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\Marshaller\Context\ContextBuilderInterface;
19+
use Symfony\Component\Marshaller\MarshallerInterface;
1820
use phpDocumentor\Reflection\DocBlockFactoryInterface;
1921
use phpDocumentor\Reflection\Types\ContextFactory;
2022
use PhpParser\Parser;
@@ -37,8 +39,8 @@
3739
use Symfony\Component\Cache\Adapter\ChainAdapter;
3840
use Symfony\Component\Cache\Adapter\TagAwareAdapter;
3941
use Symfony\Component\Cache\DependencyInjection\CachePoolPass;
40-
use Symfony\Component\Cache\Marshaller\DefaultMarshaller;
41-
use Symfony\Component\Cache\Marshaller\MarshallerInterface;
42+
use Symfony\Component\Cache\Marshaller\DefaultMarshaller as CacheDefaultMarshaller;
43+
use Symfony\Component\Cache\Marshaller\MarshallerInterface as CacheMarshallerInterface;
4244
use Symfony\Component\Cache\ResettableInterface;
4345
use Symfony\Component\Clock\ClockInterface;
4446
use Symfony\Component\Config\Definition\ConfigurationInterface;
@@ -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('marshaller', $container, $config['marshaller'])) {
603+
$this->registerMarshallerConfiguration($config['marshaller'], $container, $loader);
604+
}
605+
600606
$this->addAnnotatedClassesToCompile([
601607
'**\\Controller\\',
602608
'**\\Entity\\',
@@ -652,7 +658,7 @@ public function load(array $configs, ContainerBuilder $container)
652658
$container->registerForAutoconfiguration(ResetInterface::class)
653659
->addTag('kernel.reset', ['method' => 'reset']);
654660

655-
if (!interface_exists(MarshallerInterface::class)) {
661+
if (!interface_exists(CacheMarshallerInterface::class)) {
656662
$container->registerForAutoconfiguration(ResettableInterface::class)
657663
->addTag('kernel.reset', ['method' => 'reset']);
658664
}
@@ -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('marshaller.context_builder');
692700

693701
$container->registerAttributeForAutoconfiguration(AsEventListener::class, static function (ChildDefinition $definition, AsEventListener $attribute, \ReflectionClass|\ReflectionMethod $reflector) {
694702
$tagAttributes = get_object_vars($attribute);
@@ -2265,7 +2273,7 @@ private function registerMessengerConfiguration(array $config, ContainerBuilder
22652273

22662274
private function registerCacheConfiguration(array $config, ContainerBuilder $container): void
22672275
{
2268-
if (!class_exists(DefaultMarshaller::class)) {
2276+
if (!class_exists(CacheDefaultMarshaller::class)) {
22692277
$container->removeDefinition('cache.default_marshaller');
22702278
}
22712279

@@ -2992,6 +3000,19 @@ private function registerHtmlSanitizerConfiguration(array $config, ContainerBuil
29923000
}
29933001
}
29943002

3003+
private function registerMarshallerConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
3004+
{
3005+
if (!interface_exists(MarshallerInterface::class)) {
3006+
throw new LogicException('Marshaller support cannot be enabled as the Marshaller component is not installed. Try running "composer require symfony/marshaller');
3007+
}
3008+
3009+
$container->setParameter('marshaller.marshallable_paths', $config['marshallable_paths']);
3010+
$container->setParameter('marshaller.template_warm_up.formats', $config['template_warm_up']['formats']);
3011+
$container->setParameter('marshaller.template_warm_up.nullable_data', $config['template_warm_up']['nullable_data']);
3012+
3013+
$loader->load('marshaller.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\Marshaller\DependencyInjection\MarshallerPass;
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, MarshallerPass::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.marshaller')
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)