-
-
Notifications
You must be signed in to change notification settings - Fork 9.7k
[Serializer] add WebDebugToolbar collector for normalize/denormalize actions inside the serializer. #39325
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
[Serializer] add WebDebugToolbar collector for normalize/denormalize actions inside the serializer. #39325
Changes from all commits
1c730b9
27ce406
c9e2961
b896d64
7368cfb
a9ab284
52ed5d0
e5afaff
532235c
d7539d4
92e49b1
9038316
c9c453e
0c5a2d8
2c815cc
6992af5
f861ffe
6de10f0
18b55e6
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,76 @@ | ||
<?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\Bundle\FrameworkBundle\DependencyInjection\Compiler; | ||
|
||
use Symfony\Component\DependencyInjection\Compiler\CompilerPassInterface; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\DependencyInjection\Definition; | ||
use Symfony\Component\DependencyInjection\Exception\InvalidArgumentException; | ||
use Symfony\Component\DependencyInjection\Exception\RuntimeException; | ||
use Symfony\Component\Serializer\Debug\Normalizer\TraceableDenormalizer; | ||
use Symfony\Component\Serializer\Debug\Normalizer\TraceableHybridNormalizer; | ||
use Symfony\Component\Serializer\Debug\Normalizer\TraceableNormalizer; | ||
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface; | ||
use Symfony\Component\Serializer\Normalizer\NormalizerInterface; | ||
|
||
class SerializerDebugPass implements CompilerPassInterface | ||
{ | ||
public function process(ContainerBuilder $container): void | ||
{ | ||
if (!$container->hasDefinition('serializer')) { | ||
return; | ||
} | ||
|
||
foreach ($container->findTaggedServiceIds('serializer.normalizer') as $id => $tags) { | ||
$this->decorateNormalizer($id, $container); | ||
} | ||
} | ||
|
||
private function decorateNormalizer(string $id, ContainerBuilder $container): void | ||
{ | ||
$aliasName = 'debug.'.$id; | ||
|
||
$normalizerDef = $container->getDefinition($id); | ||
$normalizerClass = $normalizerDef->getClass(); | ||
|
||
if (!$normalizerRef = $container->getReflectionClass($normalizerClass)) { | ||
throw new InvalidArgumentException(sprintf('Class "%s" used for service "%s" cannot be found.', $normalizerClass, $id)); | ||
} | ||
|
||
$isNormalizer = $normalizerRef->implementsInterface(NormalizerInterface::class); | ||
$isDenormalizer = $normalizerRef->implementsInterface(DenormalizerInterface::class); | ||
|
||
/* | ||
* We must decorate each type of normalizer with a specific decorator, since the serializer behaves | ||
* differently depending of instanceof checks against the used normalizer. | ||
* Therefore we cannot decorate all normalizers equally. | ||
*/ | ||
if ($isNormalizer && $isDenormalizer) { | ||
$decoratorClass = TraceableHybridNormalizer::class; | ||
} elseif ($isNormalizer) { | ||
$decoratorClass = TraceableNormalizer::class; | ||
} elseif ($isDenormalizer) { | ||
$decoratorClass = TraceableDenormalizer::class; | ||
} else { | ||
throw new RuntimeException(sprintf('Normalizer with id "%s" neither implements NormalizerInterface nor DenormalizerInterface!', $id)); | ||
} | ||
|
||
$decoratorDef = (new Definition($decoratorClass)) | ||
->setArguments([$normalizerDef]) | ||
->addTag('debug.normalizer') | ||
->setDecoratedService($id) | ||
->setAutowired(true) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is it needed? same for autoconfigure There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think it's required indeed? |
||
; | ||
|
||
$container->setDefinition($aliasName, $decoratorDef); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
<?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\Serializer\DataCollector\SerializerDataCollector; | ||
use Symfony\Component\Serializer\Debug\SerializerActionFactory; | ||
use Symfony\Component\Serializer\Debug\SerializerActionFactoryInterface; | ||
use Symfony\Component\Serializer\Debug\TraceableSerializer; | ||
|
||
return static function (ContainerConfigurator $container) { | ||
$container->services() | ||
->set('debug.serializer', TraceableSerializer::class) | ||
->decorate('serializer', null, 255) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The Serializer class also implements normalizers and encoders interfaces, hence the places in userland were the
and the DI aliases on the respective interfaces won't work anymore. So decorating this service directly might not be a good idea? We could decorate each alias instead, but then no data would be collected where the |
||
->args([service('debug.serializer.inner'), service('debug.serializer.action_factory')]) | ||
->tag('kernel.reset', ['method' => 'reset']) | ||
|
||
->set('data_collector.serializer', SerializerDataCollector::class) | ||
->args([service('debug.serializer'), tagged_iterator('debug.normalizer')]) | ||
->tag('data_collector', ['template' => '@WebProfiler/Collector/serializer.html.twig', 'id' => 'serializer']) | ||
|
||
->set('debug.serializer.action_factory', SerializerActionFactory::class) | ||
|
||
->alias(SerializerActionFactoryInterface::class, 'debug.serializer.action_factory') | ||
; | ||
}; |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,63 @@ | ||
<?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\Bundle\FrameworkBundle\Tests\DependencyInjection\Compiler; | ||
|
||
use PHPUnit\Framework\TestCase; | ||
use Symfony\Bundle\FrameworkBundle\DependencyInjection\Compiler\SerializerDebugPass; | ||
use Symfony\Component\DependencyInjection\ContainerBuilder; | ||
use Symfony\Component\Serializer\Debug\SerializerActionFactory; | ||
use Symfony\Component\Serializer\Debug\SerializerActionFactoryInterface; | ||
use Symfony\Component\Serializer\SerializerInterface; | ||
use Symfony\Component\Serializer\Tests\Normalizer\TestDenormalizer; | ||
use Symfony\Component\Serializer\Tests\Normalizer\TestHybridNormalizer; | ||
use Symfony\Component\Serializer\Tests\Normalizer\TestNormalizer; | ||
|
||
class SerializerDebugPassTest extends TestCase | ||
{ | ||
private const NORMALIZER_TAG = 'serializer.normalizer'; | ||
|
||
public function testProcess() | ||
{ | ||
$serializerDebugPass = new SerializerDebugPass(); | ||
$container = new ContainerBuilder(); | ||
$container->register('serializer', SerializerInterface::class); | ||
$container->register('debug.serializer.action_factory', SerializerActionFactory::class); | ||
$container->setAlias(SerializerActionFactoryInterface::class, 'debug.serializer.action_factory'); | ||
|
||
$container->register('Test\normalizer', TestNormalizer::class) | ||
->addTag(self::NORMALIZER_TAG); | ||
|
||
$container->register('Test\denormalizer', TestDenormalizer::class) | ||
->addTag(self::NORMALIZER_TAG); | ||
|
||
$container->register('Test\hybridNormalizer', TestHybridNormalizer::class) | ||
->addTag(self::NORMALIZER_TAG); | ||
|
||
$container->addCompilerPass($serializerDebugPass); | ||
|
||
$serializerDebugPass->process($container); | ||
|
||
$debugDefinitions = [ | ||
'Test\normalizer' => 'debug.Test\normalizer', | ||
'Test\denormalizer' => 'debug.Test\denormalizer', | ||
'Test\hybridNormalizer' => 'debug.Test\hybridNormalizer', | ||
]; | ||
|
||
foreach ($debugDefinitions as $originalName => $decoratorName) { | ||
self::assertTrue($container->hasDefinition($decoratorName), 'Container should have definition: '.$decoratorName); | ||
$definition = $container->getDefinition($decoratorName); | ||
self::assertTrue($definition->hasTag('debug.normalizer')); | ||
$decoratedService = $definition->getDecoratedService(); | ||
self::assertSame($originalName, $decoratedService[0]); | ||
} | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it should rather be a LogicException, since it' a user misconfiguration/code issue.
But I also wonder why this pass would throw while the
SerializerPass
doesn't?If it's interesting DX-wise, it should be in the
SerializerPass
(not necessarily in this PR).