Skip to content

[JsonEncoder] Wire services #58515

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

Merged
merged 1 commit into from
Dec 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 1 addition & 0 deletions src/Symfony/Bundle/FrameworkBundle/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ CHANGELOG

* Add support for assets pre-compression
* Rename `TranslationUpdateCommand` to `TranslationExtractCommand`
* Add JsonEncoder services and configuration

7.2
---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,9 @@ class UnusedTagsPass implements CompilerPassInterface
'form.type_guesser',
'html_sanitizer',
'http_client.client',
'json_encoder.denormalizer',
'json_encoder.encodable',
'json_encoder.normalizer',
'kernel.cache_clearer',
'kernel.cache_warmer',
'kernel.event_listener',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
use Symfony\Component\HttpClient\HttpClient;
use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\HttpFoundation\IpUtils;
use Symfony\Component\JsonEncoder\EncoderInterface;
use Symfony\Component\Lock\Lock;
use Symfony\Component\Lock\Store\SemaphoreStore;
use Symfony\Component\Mailer\Mailer;
Expand Down Expand Up @@ -181,6 +182,7 @@ public function getConfigTreeBuilder(): TreeBuilder
$this->addHtmlSanitizerSection($rootNode, $enableIfStandalone);
$this->addWebhookSection($rootNode, $enableIfStandalone);
$this->addRemoteEventSection($rootNode, $enableIfStandalone);
$this->addJsonEncoderSection($rootNode, $enableIfStandalone);

return $treeBuilder;
}
Expand Down Expand Up @@ -2570,4 +2572,26 @@ private function addHtmlSanitizerSection(ArrayNodeDefinition $rootNode, callable
->end()
;
}

private function addJsonEncoderSection(ArrayNodeDefinition $rootNode, callable $enableIfStandalone): void
{
$rootNode
->children()
->arrayNode('json_encoder')
->info('JSON encoder configuration')
->{$enableIfStandalone('symfony/json-encoder', EncoderInterface::class)}()
->fixXmlConfig('path')
->children()
->arrayNode('paths')
->info('Namespaces and paths of encodable/decodable classes.')
->normalizeKeys(false)
->useAttributeAsKey('namespace')
->scalarPrototype()->end()
->defaultValue([])
->end()
->end()
->end()
->end()
;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,11 @@
use Symfony\Component\HttpKernel\DataCollector\DataCollectorInterface;
use Symfony\Component\HttpKernel\DependencyInjection\Extension;
use Symfony\Component\HttpKernel\Log\DebugLoggerConfigurator;
use Symfony\Component\JsonEncoder\Decode\Denormalizer\DenormalizerInterface as JsonEncoderDenormalizerInterface;
use Symfony\Component\JsonEncoder\DecoderInterface as JsonEncoderDecoderInterface;
use Symfony\Component\JsonEncoder\Encode\Normalizer\NormalizerInterface as JsonEncoderNormalizerInterface;
use Symfony\Component\JsonEncoder\EncoderInterface as JsonEncoderEncoderInterface;
use Symfony\Component\JsonEncoder\JsonEncoder;
use Symfony\Component\Lock\LockFactory;
use Symfony\Component\Lock\LockInterface;
use Symfony\Component\Lock\PersistingStoreInterface;
Expand Down Expand Up @@ -176,6 +181,7 @@
use Symfony\Component\TypeInfo\Type;
use Symfony\Component\TypeInfo\TypeResolver\PhpDocAwareReflectionTypeResolver;
use Symfony\Component\TypeInfo\TypeResolver\StringTypeResolver;
use Symfony\Component\TypeInfo\TypeResolver\TypeResolverInterface;
use Symfony\Component\Uid\Factory\UuidFactory;
use Symfony\Component\Uid\UuidV4;
use Symfony\Component\Validator\Constraints\ExpressionLanguageProvider;
Expand Down Expand Up @@ -414,14 +420,22 @@ public function load(array $configs, ContainerBuilder $container): void
$container->removeDefinition('console.command.serializer_debug');
}

if ($this->readConfigEnabled('type_info', $container, $config['type_info'])) {
if ($typeInfoEnabled = $this->readConfigEnabled('type_info', $container, $config['type_info'])) {
$this->registerTypeInfoConfiguration($container, $loader);
}

if ($propertyInfoEnabled) {
$this->registerPropertyInfoConfiguration($container, $loader);
}

if ($this->readConfigEnabled('json_encoder', $container, $config['json_encoder'])) {
if (!$typeInfoEnabled) {
throw new LogicException('JsonEncoder support cannot be enabled as the TypeInfo component is not '.(interface_exists(TypeResolverInterface::class) ? 'enabled.' : 'installed. Try running "composer require symfony/type-info".'));
}

$this->registerJsonEncoderConfiguration($config['json_encoder'], $container, $loader);
}

if ($this->readConfigEnabled('lock', $container, $config['lock'])) {
$this->registerLockConfiguration($config['lock'], $container, $loader);
}
Expand Down Expand Up @@ -1990,6 +2004,36 @@ private function registerSerializerConfiguration(array $config, ContainerBuilder
$container->setParameter('.serializer.named_serializers', $config['named_serializers'] ?? []);
}

private function registerJsonEncoderConfiguration(array $config, ContainerBuilder $container, PhpFileLoader $loader): void
{
if (!class_exists(JsonEncoder::class)) {
throw new LogicException('JsonEncoder support cannot be enabled as the JsonEncoder component is not installed. Try running "composer require symfony/json-encoder".');
}

$container->registerForAutoconfiguration(JsonEncoderNormalizerInterface::class)
->addTag('json_encoder.normalizer');
$container->registerForAutoconfiguration(JsonEncoderDenormalizerInterface::class)
->addTag('json_encoder.denormalizer');

$loader->load('json_encoder.php');

$container->registerAliasForArgument('json_encoder.encoder', JsonEncoderEncoderInterface::class, 'json.encoder');
$container->registerAliasForArgument('json_encoder.decoder', JsonEncoderDecoderInterface::class, 'json.decoder');

$container->setParameter('.json_encoder.encoders_dir', '%kernel.cache_dir%/json_encoder/encoder');
$container->setParameter('.json_encoder.decoders_dir', '%kernel.cache_dir%/json_encoder/decoder');
$container->setParameter('.json_encoder.lazy_ghosts_dir', '%kernel.cache_dir%/json_encoder/lazy_ghost');

$encodableDefinition = (new Definition())
->setAbstract(true)
->addTag('container.excluded')
->addTag('json_encoder.encodable');

foreach ($config['paths'] as $namespace => $path) {
$loader->registerClasses($encodableDefinition, $namespace, $path);
}
}

private function registerPropertyInfoConfiguration(ContainerBuilder $container, PhpFileLoader $loader): void
{
if (!interface_exists(PropertyInfoExtractorInterface::class)) {
Expand Down
126 changes: 126 additions & 0 deletions src/Symfony/Bundle/FrameworkBundle/Resources/config/json_encoder.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
<?php

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

namespace Symfony\Component\DependencyInjection\Loader\Configurator;

use Symfony\Component\JsonEncoder\CacheWarmer\EncoderDecoderCacheWarmer;
use Symfony\Component\JsonEncoder\CacheWarmer\LazyGhostCacheWarmer;
use Symfony\Component\JsonEncoder\Decode\Denormalizer\DateTimeDenormalizer;
use Symfony\Component\JsonEncoder\Encode\Normalizer\DateTimeNormalizer;
use Symfony\Component\JsonEncoder\JsonDecoder;
use Symfony\Component\JsonEncoder\JsonEncoder;
use Symfony\Component\JsonEncoder\Mapping\Decode\AttributePropertyMetadataLoader as DecodeAttributePropertyMetadataLoader;
use Symfony\Component\JsonEncoder\Mapping\Decode\DateTimeTypePropertyMetadataLoader as DecodeDateTimeTypePropertyMetadataLoader;
use Symfony\Component\JsonEncoder\Mapping\Encode\AttributePropertyMetadataLoader as EncodeAttributePropertyMetadataLoader;
use Symfony\Component\JsonEncoder\Mapping\Encode\DateTimeTypePropertyMetadataLoader as EncodeDateTimeTypePropertyMetadataLoader;
use Symfony\Component\JsonEncoder\Mapping\GenericTypePropertyMetadataLoader;
use Symfony\Component\JsonEncoder\Mapping\PropertyMetadataLoader;

return static function (ContainerConfigurator $container) {
$container->services()
// encoder/decoder
->set('json_encoder.encoder', JsonEncoder::class)
->args([
tagged_locator('json_encoder.normalizer'),
service('json_encoder.encode.property_metadata_loader'),
param('.json_encoder.encoders_dir'),
false,
])
->set('json_encoder.decoder', JsonDecoder::class)
->args([
tagged_locator('json_encoder.denormalizer'),
service('json_encoder.decode.property_metadata_loader'),
param('.json_encoder.decoders_dir'),
param('.json_encoder.lazy_ghosts_dir'),
])
->alias(JsonEncoder::class, 'json_encoder.encoder')
->alias(JsonDecoder::class, 'json_encoder.decoder')

// metadata
->stack('json_encoder.encode.property_metadata_loader', [
inline_service(EncodeAttributePropertyMetadataLoader::class)
->args([
service('.inner'),
tagged_locator('json_encoder.normalizer'),
service('type_info.resolver'),
]),
inline_service(EncodeDateTimeTypePropertyMetadataLoader::class)
->args([
service('.inner'),
]),
inline_service(GenericTypePropertyMetadataLoader::class)
->args([
service('.inner'),
service('type_info.type_context_factory'),
]),
inline_service(PropertyMetadataLoader::class)
->args([
service('type_info.resolver'),
]),
])

->stack('json_encoder.decode.property_metadata_loader', [
inline_service(DecodeAttributePropertyMetadataLoader::class)
->args([
service('.inner'),
tagged_locator('json_encoder.denormalizer'),
service('type_info.resolver'),
]),
inline_service(DecodeDateTimeTypePropertyMetadataLoader::class)
->args([
service('.inner'),
]),
inline_service(GenericTypePropertyMetadataLoader::class)
->args([
service('.inner'),
service('type_info.type_context_factory'),
]),
inline_service(PropertyMetadataLoader::class)
->args([
service('type_info.resolver'),
]),
])

// normalizers/denormalizers
->set('json_encoder.normalizer.date_time', DateTimeNormalizer::class)
->tag('json_encoder.normalizer')
->set('json_encoder.denormalizer.date_time', DateTimeDenormalizer::class)
->args([
false,
])
->tag('json_encoder.denormalizer')
->set('json_encoder.denormalizer.date_time_immutable', DateTimeDenormalizer::class)
->args([
true,
])
->tag('json_encoder.denormalizer')

// cache
->set('.json_encoder.cache_warmer.encoder_decoder', EncoderDecoderCacheWarmer::class)
->args([
tagged_iterator('json_encoder.encodable'),
service('json_encoder.encode.property_metadata_loader'),
service('json_encoder.decode.property_metadata_loader'),
param('.json_encoder.encoders_dir'),
param('.json_encoder.decoders_dir'),
false,
service('logger')->ignoreOnInvalid(),
])
->tag('kernel.cache_warmer')

->set('.json_encoder.cache_warmer.lazy_ghost', LazyGhostCacheWarmer::class)
->args([
tagged_iterator('json_encoder.encodable'),
param('.json_encoder.lazy_ghosts_dir'),
])
->tag('kernel.cache_warmer')
;
};
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
<xsd:element name="enabled-locale" type="xsd:string" minOccurs="0" maxOccurs="unbounded" />
<xsd:element name="webhook" type="webhook" minOccurs="0" maxOccurs="1" />
<xsd:element name="remote-event" type="remote-event" minOccurs="0" maxOccurs="1" />
<xsd:element name="json-encoder" type="json-encoder" minOccurs="0" maxOccurs="1" />
</xsd:choice>

<xsd:attribute name="http-method-override" type="xsd:boolean" />
Expand Down Expand Up @@ -1003,4 +1004,13 @@
<xsd:complexType name="remote-event">
<xsd:attribute name="enabled" type="xsd:boolean" />
</xsd:complexType>

<xsd:complexType name="json-encoder">
<xsd:choice minOccurs="0" maxOccurs="unbounded">
<xsd:sequence>
<xsd:element name="default-context" type="metadata" minOccurs="0" maxOccurs="1" />
</xsd:sequence>
</xsd:choice>
<xsd:attribute name="enabled" type="xsd:boolean" />
</xsd:complexType>
</xsd:schema>
Original file line number Diff line number Diff line change
Expand Up @@ -970,6 +970,10 @@ class_exists(SemaphoreStore::class) && SemaphoreStore::isSupported() ? 'semaphor
'remote-event' => [
'enabled' => !class_exists(FullStack::class) && class_exists(RemoteEvent::class),
],
'json_encoder' => [
'enabled' => false,
'paths' => [],
],
];
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?php

$container->loadFromExtension('framework', [
'annotations' => false,
'http_method_override' => false,
'handle_all_throwables' => true,
'php_errors' => ['log' => true],
'type_info' => [
'enabled' => true,
],
'json_encoder' => [
'enabled' => true,
],
]);
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,6 @@
</framework:serializer>
<framework:property-info />
<framework:type-info />
<framework:json-encoder />
</framework:config>
</container>
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8" ?>
<container xmlns="http://symfony.com/schema/dic/services"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:framework="http://symfony.com/schema/dic/symfony"
xsi:schemaLocation="http://symfony.com/schema/dic/services https://symfony.com/schema/dic/services/services-1.0.xsd
http://symfony.com/schema/dic/symfony https://symfony.com/schema/dic/symfony/symfony-1.0.xsd">

<framework:config http-method-override="false" handle-all-throwables="true">
<framework:annotations enabled="false" />
<framework:php-errors log="true" />
<framework:type-info enabled="true" />
<framework:json-encoder enabled="true" />
</framework:config>
</container>
Original file line number Diff line number Diff line change
Expand Up @@ -70,3 +70,4 @@ framework:
formats:
csv: ['text/csv', 'text/plain']
pdf: 'application/pdf'
json_encoder: ~
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
framework:
annotations: false
http_method_override: false
handle_all_throwables: true
php_errors:
log: true
type_info:
enabled: true
json_encoder:
enabled: true
Original file line number Diff line number Diff line change
Expand Up @@ -2497,6 +2497,12 @@ public function testSemaphoreWithService()
self::assertEquals(new Reference('my_service'), $storeDef->getArgument(0));
}

public function testJsonEncoderEnabled()
{
$container = $this->createContainerFromFile('json_encoder');
$this->assertTrue($container->has('json_encoder.encoder'));
}

protected function createContainer(array $data = [])
{
return new ContainerBuilder(new EnvPlaceholderParameterBag(array_merge([
Expand Down
Loading
Loading