Skip to content

Commit

Permalink
symfony#39102: migrated all the collector stuff from my PoC.
Browse files Browse the repository at this point in the history
  • Loading branch information
Basster committed Dec 21, 2020
1 parent 88e18ce commit 810e8b9
Show file tree
Hide file tree
Showing 19 changed files with 1,033 additions and 16 deletions.
@@ -0,0 +1,83 @@
<?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 ReflectionException;
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
{
/**
* @throws ReflectionException
*/
public function process(ContainerBuilder $container): void
{
foreach ($container->findTaggedServiceIds('serializer.normalizer') as $id => $tags) {
$this->decorateNormalizer($id, $container);
}
}

/**
* @throws ReflectionException
*/
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)
->setAutoconfigured(true);

$container->setDefinition($aliasName, $decoratorDef);
}
}
@@ -0,0 +1,57 @@
<?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\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(): void
{
$serializerDebugPass = new SerializerDebugPass();
$container = new ContainerBuilder();

$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));
$definition = $container->getDefinition($decoratorName);
self::assertTrue($definition->hasTag('debug.normalizer'));
$decoratedService = $definition->getDecoratedService();
self::assertSame($originalName, $decoratedService[0]);
}
}
}
@@ -0,0 +1,184 @@
{% extends '@WebProfiler/Profiler/layout.html.twig' %}

{% block toolbar %}
{% set icon %}
{{ include('@WebProfiler/Icon/serializer.svg') }}
{% endset %}

{% set text %}
<div class="sf-toolbar-info-piece">
Serializer
</div>
{% endset %}

{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { 'link': true }) }}
{% endblock %}

{% block menu %}
{% set disable = collector.serializations | length == 0 and collector.deserializations | length == 0 %}
<span class="label {{ disable ? 'disabled' }}">
<span class="icon">{{ include('data_collector/exchange-alt-solid.svg') }}</span>
<strong>Serializer</strong>
</span>
{% endblock %}

{% block panel %}
<h2>Serializer</h2>

<div class="metrics">
<div class="metric">
<span class="value">{{ collector.serializations | length }}</span>
<span class="label">Serializations</span>
</div>
<div class="metric">
<span class="value">{{ collector.deserializations | length }}</span>
<span class="label">Deserializations</span>
</div>
<div class="metric">
<span class="value">{{ collector.normalizations | length }}</span>
<span class="label">Normalizations</span>
</div>
<div class="metric">
<span class="value">{{ collector.denormalizations | length }}</span>
<span class="label">Denormalizations</span>
</div>
</div>

<div class="sf-tabs">
<div class="tab {{ collector.serializations|length == 0 ? 'disabled' }}">
<h3 class="tab-title">Serializations <span class="badge">{{ collector.serializations | length }}</span></h3>

<div class="tab-content">
<h4>Serializations</h4>
<table>
<thead>
<tr>
<th>#</th>
<th>Format</th>
<th>From</th>
<th>To</th>
<th>Context</th>
</tr>
</thead>
<tbody>
{% for serialization in collector.serializations %}
<tr>
<td class="font-normal text-small text-muted nowrap">{{ loop.index }}</td>
<td class="nowrap">{{ serialization.format }}</td>
<td>{{ profiler_dump(serialization.data, maxDepth=2) }}</td>
<td>{{ serialization.result }}</td>
<td>{{ profiler_dump(serialization.context, maxDepth=2) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="tab {{ collector.deserializations|length == 0 ? 'disabled' }}">
<h3 class="tab-title">Deserializations <span class="badge">{{ collector.deserializations | length }}</span>
</h3>

<div class="tab-content">
<h4>Deserializations</h4>
<table>
<thead>
<tr>
<th>#</th>
<th>Format</th>
<th>Type</th>
<th>From</th>
<th>To</th>
<th>Context</th>
</tr>
</thead>
<tbody>
{% for deserialization in collector.deserializations %}
<tr>
<td class="font-normal text-small text-muted nowrap">{{ loop.index }}</td>
<td class="nowrap">{{ deserialization.format }}</td>
<td class="nowrap">{{ deserialization.type }}</td>
<td>{{ deserialization.data }}</td>
<td>{{ profiler_dump(deserialization.result, maxDepth=2) }}</td>
<td>{{ profiler_dump(deserialization.context, maxDepth=2) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="tab {{ collector.normalizations|length == 0 ? 'disabled' }}">
<h3 class="tab-title">Normalizations <span class="badge">{{ collector.normalizations | length }}</span>
</h3>

<div class="tab-content">
<h4>Normalizations</h4>
<table>
<thead>
<tr>
<th>#</th>
<th>Format</th>
<th>Normalizer</th>
<th>From</th>
<th>To</th>
<th>Context</th>
</tr>
</thead>
<tbody>
{% for normalization in collector.normalizations %}
<tr>
<td class="font-normal text-small text-muted nowrap">{{ loop.index }}</td>
<td class="nowrap">{{ normalization.format }}</td>
<td>{{ profiler_dump(normalization.normalizer, maxDepth=2) }}</td>
<td>{{ profiler_dump(normalization.data, maxDepth=2) }}</td>
<td>{{ normalization.result }}</td>
<td>{{ profiler_dump(normalization.context, maxDepth=2) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
<div class="tab {{ collector.denormalizations|length == 0 ? 'disabled' }}">
<h3 class="tab-title">Denormalizations <span class="badge">{{ collector.denormalizations | length }}</span>
</h3>

<div class="tab-content">
<h4>Denormalizations</h4>
<table>
<thead>
<tr>
<th>#</th>
<th>Format</th>
<th>Denormalizer</th>
<th>Type</th>
<th>From</th>
<th>To</th>
<th>Context</th>
</tr>
</thead>
<tbody>
{% for denormalization in collector.denormalizations %}
<tr>
<td class="font-normal text-small text-muted nowrap">{{ loop.index }}</td>
<td class="nowrap">{{ denormalization.format }}</td>
<td class="nowrap">{{ denormalization.denormalizer }}</td>
<td class="nowrap">{{ denormalization.type }}</td>
<td>{{ denormalization.data }}</td>
<td>{{ profiler_dump(denormalization.result, maxDepth=2) }}</td>
<td>{{ profiler_dump(denormalization.context, maxDepth=2) }}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>

<footer>
<small>
The icon is taken from <a href="https://fontawesome.com/icons/exchange-alt?style=solid">fontawesome</a> and
is under <a href="https://fontawesome.com/license/free">Creative Commons Attribution 4.0 International
license</a>.
</small>
</footer>
{% endblock %}
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.

0 comments on commit 810e8b9

Please sign in to comment.