Skip to content

Serializer revamp #6

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 42 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
42cc163
[SerDes] Introduce component
mtarld Mar 20, 2023
b013f64
add enum support
mtarld Mar 24, 2023
27cc000
updates to select 7.0 branch
mtarld Mar 24, 2023
f6aec4b
set eager read and instantiator by default
mtarld Mar 24, 2023
3c872be
split context builders and remove cache trait
mtarld Mar 24, 2023
fdb4a66
use callable in formatter signature
mtarld Mar 26, 2023
5497e69
minor fixes
mtarld Mar 26, 2023
f12ec50
unify symfony context
mtarld Mar 26, 2023
7371213
remove support of non backed enums
mtarld Mar 26, 2023
e885240
static data providers
mtarld Mar 26, 2023
a0a7af9
update xxh128 hash instead of md5
mtarld Mar 26, 2023
972b2af
simplify object instantiation
mtarld Mar 27, 2023
a05dd46
remove serializable attribute
mtarld Mar 27, 2023
bed00e3
split deserializer
mtarld Mar 27, 2023
1a3870c
use null coalescing assignment operator
mtarld Mar 27, 2023
f62e6d6
fix attribute context builders
mtarld Mar 27, 2023
a6425e5
remove nullable and improve cache
mtarld Mar 27, 2023
560ebb1
improve tagged iterator arguments
mtarld Mar 29, 2023
838b68e
add force_generate_template option
mtarld Mar 29, 2023
2ddc742
allow to ignore properties during serialization
mtarld Mar 29, 2023
3bb892b
better deserializers split
mtarld Mar 29, 2023
1f575f0
add proper defaults
mtarld Mar 29, 2023
1640817
rename attributes
mtarld Mar 29, 2023
62ab9c3
improve lazy instantiator
mtarld Mar 30, 2023
f58de4d
refactor template generator
mtarld Mar 31, 2023
d46de84
remove useless serialize union selector
mtarld Apr 5, 2023
418f968
make context builders internal
mtarld Apr 5, 2023
d7c4c08
handle mixed, object and raw array
mtarld May 17, 2023
260c991
handle groups
mtarld May 17, 2023
394407c
remove initial closure nodes
mtarld May 24, 2023
59a74d8
handle csv serialization
mtarld May 24, 2023
d5c5e40
handle csv deserialization
mtarld Jun 2, 2023
227eab3
remove stream validation
mtarld Jun 5, 2023
74026d9
add readonly classes
mtarld Jun 5, 2023
7a1d404
fix missing internal
mtarld Jun 6, 2023
0f57cab
move types out of internal
mtarld Jun 6, 2023
c41cd4c
fix csv serialization
mtarld Jun 6, 2023
d9a05c8
serialize hooks
mtarld Jun 7, 2023
65738fc
deserialize hooks
mtarld Jun 8, 2023
68691be
remove union type
mtarld Jun 8, 2023
1f52369
remove isGeneric
mtarld Jun 9, 2023
20c0095
merge in serializer
mtarld Jun 9, 2023
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -186,7 +186,8 @@
"Symfony\\Component\\": "src/Symfony/Component/"
},
"files": [
"src/Symfony/Component/String/Resources/functions.php"
"src/Symfony/Component/String/Resources/functions.php",
"src/Symfony/Component/Serializer/Resources/functions.php"
],
"classmap": [
"src/Symfony/Component/Cache/Traits/ValueWrapper.php"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
<?php

/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <[email protected]>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/

namespace Symfony\Bundle\FrameworkBundle\CacheWarmer;

use Psr\Log\LoggerInterface;
use Psr\Log\NullLogger;
use Symfony\Component\HttpKernel\CacheWarmer\CacheWarmer;
use Symfony\Component\Serializer\Context\ContextBuilder;
use Symfony\Component\Serializer\Exception\ExceptionInterface;
use Symfony\Component\Serializer\SerializableResolver\SerializableResolverInterface;
use Symfony\Component\Serializer\Serialize\Template\TemplateHelper;
use Symfony\Component\Serializer\Serialize\Template\TemplateVariation;
use Symfony\Component\Serializer\Type\TypeFactory;
use Symfony\Component\VarExporter\ProxyHelper;

use function Symfony\Component\Serializer\serialize_generate;

/**
* @author Mathias Arlaud <[email protected]>
*
* @experimental in 7.0
*/
final class SerializeDeserializeCacheWarmer extends CacheWarmer
{
private readonly TemplateHelper $templateHelper;

/**
* @param list<string> $formats
*/
public function __construct(
private readonly ContextBuilder $contextBuilder,
private readonly SerializableResolverInterface $serializableResolver,
private readonly string $templateCacheDir,
private readonly string $lazyObjectCacheDir,
private readonly array $formats,
private readonly int $maxVariants,
private readonly LoggerInterface $logger = new NullLogger(),
) {
$this->templateHelper = new TemplateHelper();
}

public function warmUp(string $cacheDir): array
{
if (!file_exists($this->templateCacheDir)) {
mkdir($this->templateCacheDir, recursive: true);
}

if (!file_exists($this->lazyObjectCacheDir)) {
mkdir($this->lazyObjectCacheDir, recursive: true);
}

foreach ($this->serializableResolver->resolve() as $className) {
$variants = $this->templateHelper->classTemplateVariants($className);

if (\count($variants) > $this->maxVariants) {
$this->logger->debug('Too many variants for "{className}", keeping only the first {maxVariants}.', ['className' => $className, 'maxVariants' => $this->maxVariants]);
$variants = \array_slice($variants, offset: 0, length: $this->maxVariants);
}

foreach ($this->formats as $format) {
$this->warmClassTemplate($className, $variants, $format);
}

$this->warmClassLazyObject($className);
}

return [];
}

public function isOptional(): bool
{
return false;
}

/**
* @param class-string $className
* @param list<list<TemplateVariation>> $variants
*/
private function warmClassTemplate(string $className, array $variants, string $format): void
{
try {
$context = [
'cache_dir' => $this->templateCacheDir,
];

$context = $this->contextBuilder->build($context, isSerialization: true);

foreach ($variants as $variant) {
$variantContext = $context;

$groupVariations = array_filter($variant, fn (TemplateVariation $v): bool => 'group' === $v->type);
if ([] !== $groupVariations) {
$variantContext['groups'] = array_map(fn (TemplateVariation $v): string => $v->value, $groupVariations);
}

$variantFilename = $this->templateHelper->templateFilename($className, $format, $variantContext);

$this->writeCacheFile(
$this->templateCacheDir.\DIRECTORY_SEPARATOR.$variantFilename,
serialize_generate(TypeFactory::createFromString($className), $format, $variantContext),
);
}
} catch (ExceptionInterface $e) {
$this->logger->debug('Cannot generate template for "{className}": {exception}', ['className' => $className, 'exception' => $e]);
}
}

/**
* @param class-string $className
*/
private function warmClassLazyObject(string $className): void
{
$path = sprintf('%s%s%s.php', $this->lazyObjectCacheDir, \DIRECTORY_SEPARATOR, hash('xxh128', $className));

$this->writeCacheFile($path, sprintf(
'class %s%s',
sprintf('%sGhost', preg_replace('/\\\\/', '', $className)),
ProxyHelper::generateLazyGhost(new \ReflectionClass($className)),
));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1098,6 +1098,7 @@ private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $e
->arrayNode('serializer')
->info('serializer configuration')
->{$enableIfStandalone('symfony/serializer', Serializer::class)}()
->fixXmlConfig('serializable_path')
->children()
->booleanNode('enable_annotations')->{!class_exists(FullStack::class) ? 'defaultTrue' : 'defaultFalse'}()->end()
->scalarNode('name_converter')->end()
Expand All @@ -1118,6 +1119,29 @@ private function addSerializerSection(ArrayNodeDefinition $rootNode, callable $e
->defaultValue([])
->prototype('variable')->end()
->end()
->arrayNode('serializable_paths')
->info('Defines where to find serializable classes.')
->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()
->prototype('scalar')->end()
->defaultValue([])
->end()
->arrayNode('template_warm_up')
->addDefaultsIfNotSet()
->fixXmlConfig('format')
->children()
->arrayNode('formats')
->info('Defines the formats that will be handled.')
->beforeNormalization()->ifString()->then(function ($v) { return [$v]; })->end()
->prototype('scalar')->end()
->defaultValue(['json'])
->end()
->integerNode('max_variants')
->info('Defines the maximum variants allowed for each class.')
->defaultValue(32)
->min(0)
->end()
->end()
->end()
->end()
->end()
->end()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1943,6 +1943,18 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
if (isset($config['default_context']) && $config['default_context']) {
$container->setParameter('serializer.default_context', $config['default_context']);
}

$container->setParameter('serializer.serializable_paths', $config['serializable_paths']);
$container->setParameter('serializer.template_warm_up.formats', $config['template_warm_up']['formats']);
$container->setParameter('serializer.template_warm_up.max_variants', $config['template_warm_up']['max_variants']);

foreach ($config['serializable_paths'] as $path) {
if (!is_dir($path)) {
continue;
}

$container->fileExists($path, '/\.php$/');
}
}

private function registerPropertyInfoConfiguration(ContainerBuilder $container, PhpFileLoader $loader): void
Expand Down
113 changes: 113 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Resources/config/serializer.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,18 @@
namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Psr\Cache\CacheItemPoolInterface;
use Symfony\Bundle\FrameworkBundle\CacheWarmer\SerializeDeserializeCacheWarmer;
use Symfony\Bundle\FrameworkBundle\CacheWarmer\SerializerCacheWarmer;
use Symfony\Component\Cache\Adapter\PhpArrayAdapter;
use Symfony\Component\ErrorHandler\ErrorRenderer\HtmlErrorRenderer;
use Symfony\Component\ErrorHandler\ErrorRenderer\SerializerErrorRenderer;
use Symfony\Component\PropertyInfo\Extractor\SerializerExtractor;
use Symfony\Component\Serializer\Context\ContextBuilder;
use Symfony\Component\Serializer\Deserialize;
use Symfony\Component\Serializer\Deserialize\Hook\ObjectHook as DeserializeObjectHook;
use Symfony\Component\Serializer\Deserialize\Hook\ObjectHookInterface as DeserializeObjectHookInterface;
use Symfony\Component\Serializer\Deserialize\Instantiator\LazyInstantiator;
use Symfony\Component\Serializer\DeserializeInterface;
use Symfony\Component\Serializer\Encoder\CsvEncoder;
use Symfony\Component\Serializer\Encoder\DecoderInterface;
use Symfony\Component\Serializer\Encoder\EncoderInterface;
Expand Down Expand Up @@ -49,8 +56,18 @@
use Symfony\Component\Serializer\Normalizer\PropertyNormalizer;
use Symfony\Component\Serializer\Normalizer\UidNormalizer;
use Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer;
use Symfony\Component\Serializer\SerializableResolver\CachedSerializableResolver;
use Symfony\Component\Serializer\SerializableResolver\PathSerializableResolver;
use Symfony\Component\Serializer\SerializableResolver\SerializableResolverInterface;
use Symfony\Component\Serializer\Serialize;
use Symfony\Component\Serializer\Serialize\Hook\ObjectHook as SerializeObjectHook;
use Symfony\Component\Serializer\Serialize\Hook\ObjectHookInterface as SerializeObjectHookInterface;
use Symfony\Component\Serializer\SerializeInterface;
use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\SerializerInterface;
use Symfony\Component\Serializer\Type\PhpstanTypeExtractor;
use Symfony\Component\Serializer\Type\ReflectionTypeExtractor;
use Symfony\Component\Serializer\Type\TypeExtractorInterface;

return static function (ContainerConfigurator $container) {
$container->parameters()
Expand Down Expand Up @@ -220,4 +237,100 @@
->tag('serializer.normalizer', ['priority' => -915])
;
}

//
// Experimental serializer
//

$container->parameters()
->set('.serializer.cache_dir.template', '%kernel.cache_dir%/serializer/template')
->set('.serializer.cache_dir.lazy_object', '%kernel.cache_dir%/serializer/lazy_object')
;

$container->services()
// Serialize
->set('serializer.serialize', Serialize::class)
->args([
service('.serializer.context_builder'),
param('.serializer.cache_dir.template'),
])
->alias(SerializeInterface::class, 'serializer.serialize')

// Deserialize
->set('serializer.deserialize', Deserialize::class)
->args([
service('.serializer.context_builder'),
service('serializer.hook.deserialize.object'),
])
->alias(DeserializeInterface::class, 'serializer.deserialize')

// Context
->set('.serializer.context_builder', ContextBuilder::class)
->args([
service('serializer.serializable_resolver'),
service('serializer.instantiator.lazy'),
service('serializer.hook.serialize.object'),
service('serializer.hook.deserialize.object'),
])

// Type extractors
->set('serializer.type_extractor.reflection', ReflectionTypeExtractor::class)
->lazy()
->tag('proxy', ['interface' => TypeExtractorInterface::class])

->set('serializer.type_extractor.phpstan', PhpstanTypeExtractor::class)
->decorate('serializer.type_extractor.reflection')
->args([
service('serializer.type_extractor.phpstan.inner'),
])
->lazy()
->tag('proxy', ['interface' => TypeExtractorInterface::class])
->alias('serializer.type_extractor', 'serializer.type_extractor.reflection')

// Hooks
->set('serializer.hook.serialize.object', SerializeObjectHook::class)
->args([
service('serializer.type_extractor'),
])
->alias(SerializeObjectHookInterface::class, 'serializer.hook.serialize.object')

->set('serializer.hook.deserialize.object', DeserializeObjectHook::class)
->args([
service('serializer.type_extractor'),
])
->alias(DeserializeObjectHookInterface::class, 'serializer.hook.deserialize.object')

// Serializable resolvers
->set('serializer.serializable_resolver', PathSerializableResolver::class)
->args([
param('serializer.serializable_paths'),
])

->set('serializer.serializable_resolver.cached', CachedSerializableResolver::class)
->decorate('serializer.serializable_resolver', priority: -1024)
->args([
service('serializer.serializable_resolver.cached.inner'),
service('cache.serializer')->ignoreOnInvalid(),
])
->alias(SerializableResolverInterface::class, 'serializer.serializable_resolver')

// Object instantiators
->set('serializer.instantiator.lazy', LazyInstantiator::class)
->args([
param('.serializer.cache_dir.lazy_object'),
])

// Cache
->set('serializer.cache_warmer.serialize_deserialize', SerializeDeserializeCacheWarmer::class)
->args([
service('.serializer.context_builder'),
service('serializer.serializable_resolver'),
param('.serializer.cache_dir.template'),
param('.serializer.cache_dir.lazy_object'),
param('serializer.template_warm_up.formats'),
param('serializer.template_warm_up.max_variants'),
service('logger')->ignoreOnInvalid(),
])
->tag('kernel.cache_warmer')
;
};
Loading
Loading