Skip to content

Commit

Permalink
symfony#39102: avoid storing huge (de)serializing objects/strings in …
Browse files Browse the repository at this point in the history
…memory by replacing too large source-objects/results as replacement (LargeContent).
  • Loading branch information
Basster committed Dec 21, 2020
1 parent e31bd8b commit cfe328a
Show file tree
Hide file tree
Showing 11 changed files with 342 additions and 39 deletions.
Expand Up @@ -67,7 +67,9 @@ private function decorateNormalizer(string $id, ContainerBuilder $container): vo
$decoratorDef = (new Definition($decoratorClass))
->setArguments([$normalizerDef])
->addTag('debug.normalizer')
->setDecoratedService($id);
->setDecoratedService($id)
->setAutowired(true)
;

$container->setDefinition($aliasName, $decoratorDef);
}
Expand Down
Expand Up @@ -12,17 +12,23 @@
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)
->args([service('debug.serializer.inner')])
->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')])
->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')
;
};
Expand Up @@ -44,6 +44,11 @@
</div>
</div>

<p class="help">To avoid storing huge amounts of serialized/deserialized data in memory, all content
bigger than {{ constant('Symfony\\Component\\Serializer\\Debug\\LargeContent::LIMIT_BYTES') / 1024 }} kB
will be displayed as an instance of {{ 'Symfony\\Component\\Serializer\\Debug\\LargeContent' | abbr_class }}
or a string with a representative message on the tabs below.</p>

<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>
Expand Down Expand Up @@ -96,7 +101,7 @@
<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 class="nowrap">{{ deserialization.type | abbr_class }}</td>
<td>{{ deserialization.data }}</td>
<td>{{ profiler_dump(deserialization.result, maxDepth=2) }}</td>
<td>{{ profiler_dump(deserialization.context, maxDepth=2) }}</td>
Expand Down Expand Up @@ -128,9 +133,9 @@
<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 class="nowrap">{{ normalization.normalizer | abbr_class }}</td>
<td>{{ profiler_dump(normalization.data, maxDepth=2) }}</td>
<td>{{ normalization.result }}</td>
<td>{{ profiler_dump(normalization.result, maxDepth=2) }}</td>
<td>{{ profiler_dump(normalization.context, maxDepth=2) }}</td>
</tr>
{% endfor %}
Expand Down Expand Up @@ -161,9 +166,9 @@
<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 class="nowrap">{{ denormalization.denormalizer | abbr_class }}</td>
<td class="nowrap">{{ denormalization.type | abbr_class }}</td>
<td>{{ profiler_dump(denormalization.data, maxDepth=2) }}</td>
<td>{{ profiler_dump(denormalization.result, maxDepth=2) }}</td>
<td>{{ profiler_dump(denormalization.context, maxDepth=2) }}</td>
</tr>
Expand All @@ -175,8 +180,9 @@
</div>

<footer>
<small>
The icon is taken from <a href="https://fontawesome.com/icons/exchange-alt?style=solid">fontawesome</a> and
<small class="text-muted">
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>
Expand Down
30 changes: 30 additions & 0 deletions src/Symfony/Component/Serializer/Debug/LargeContent.php
@@ -0,0 +1,30 @@
<?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\Serializer\Debug;

final class LargeContent
{
public const LIMIT_BYTES = 1024 * 1024;
public $message;

public function __construct()
{
$this->message = sprintf(
"The content of the serialized/deserialized data exceeded %d kB.",
self::LIMIT_BYTES / 1024
);
}

public function __toString(): string
{
return $this->message;
}
}
Expand Up @@ -11,6 +11,7 @@

namespace Symfony\Component\Serializer\Debug\Normalizer;

use Symfony\Component\Serializer\Debug\SerializerActionFactoryInterface;
use Symfony\Component\Serializer\Normalizer\CacheableSupportsMethodInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerAwareInterface;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
Expand All @@ -25,21 +26,23 @@ abstract class AbstractTraceableNormalizer implements SerializerAwareInterface,
* @var DenormalizerInterface|NormalizerInterface
*/
protected $delegate;
private $serializerActionFactory;
private $normalizations = [];
private $denormalizations = [];

/**
* @param DenormalizerInterface|NormalizerInterface $delegate
*/
public function __construct(object $delegate)
public function __construct(object $delegate, SerializerActionFactoryInterface $serializerActionFactory)
{
$this->delegate = $delegate;
$this->serializerActionFactory = $serializerActionFactory;
}

public function denormalize($data, string $type, string $format = null, array $context = [])
{
$result = $this->delegate->denormalize($data, $type, $format, $context);
$this->denormalizations[] = new Denormalization($this->delegate, $result, $data, $type, $format, $context);
$this->denormalizations[] = $this->serializerActionFactory->createDenormalization($this->delegate, $data, $result, $type, $format, $context);

return $result;
}
Expand All @@ -52,7 +55,7 @@ public function supportsDenormalization($data, string $type, string $format = nu
public function normalize($object, string $format = null, array $context = [])
{
$result = $this->delegate->normalize($object, $format, $context);
$this->normalizations[] = new Normalization($this->delegate, $object, $result, $format, $context);
$this->normalizations[] = $this->serializerActionFactory->createNormalization($this->delegate, $object, $result, $format, $context);

return $result;
}
Expand Down
95 changes: 95 additions & 0 deletions src/Symfony/Component/Serializer/Debug/SerializerActionFactory.php
@@ -0,0 +1,95 @@
<?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\Serializer\Debug;

use Symfony\Component\Serializer\Debug\Normalizer\Denormalization;
use Symfony\Component\Serializer\Debug\Normalizer\Normalization;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

final class SerializerActionFactory implements SerializerActionFactoryInterface
{
public function createDenormalization(DenormalizerInterface $denormalizer, $data, $result, string $type, string $format, array $context = []): Denormalization
{
return new Denormalization(
$denormalizer,
$this->sanitize($data),
$this->sanitize($result),
$type,
$format,
$context
);
}

public function createNormalization(NormalizerInterface $normalizer, $object, $result, string $format, array $context = []): Normalization
{
return new Normalization(
$normalizer,
$this->sanitize($object),
$this->sanitize($result),
$format,
$context
);
}

public function createSerialization($data, string $result, string $format, array $context = []): Serialization
{
return new Serialization(
$this->sanitize($data),
$this->sanitize($result),
$format,
$context
);
}

public function createDeserialization(string $data, $result, string $type, string $format, array $context = [] ): Deserialization
{
return new Deserialization(
$this->sanitize($data),
$this->sanitize($result),
$type,
$format,
$context
);
}

/**
* @param mixed $data
* @return mixed|string|LargeContent
*/
private function sanitize($data)
{
if ($this->getVarSize($data) > LargeContent::LIMIT_BYTES) {
$data = $this->markAsHugeContent($data);
}

return $data;
}

/**
* @param mixed $data
* @return string|LargeContent
*/
private function markAsHugeContent($data)
{
$output = new LargeContent();
if (is_string($data)) {
return (string) $output;
}

return $output;
}

private function getVarSize($data): int
{
return \mb_strlen(serialize($data), 'UTF-8') ?? 0;
}
}
@@ -0,0 +1,39 @@
<?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\Serializer\Debug;

use Symfony\Component\Serializer\Debug\Normalizer\Denormalization;
use Symfony\Component\Serializer\Debug\Normalizer\Normalization;
use Symfony\Component\Serializer\Normalizer\DenormalizerInterface;
use Symfony\Component\Serializer\Normalizer\NormalizerInterface;

interface SerializerActionFactoryInterface
{
/**
* @param mixed $result
*/
public function createDenormalization(DenormalizerInterface $denormalizer, string $data, $result, string $type, string $format, array $context = []): Denormalization;

/**
* @param mixed $object
*/
public function createNormalization(NormalizerInterface $normalizer, $object, string $result, string $format, array $context = []): Normalization;

/**
* @param mixed $result
*/
public function createDeserialization(string $data, $result, string $type, string $format, array $context = []): Deserialization;

/**
* @param mixed $data
*/
public function createSerialization($data, string $result, string $format, array $context = []): Serialization;
}
19 changes: 9 additions & 10 deletions src/Symfony/Component/Serializer/Debug/TraceableSerializer.php
Expand Up @@ -25,28 +25,27 @@ final class TraceableSerializer implements SerializerInterface, ResetInterface
*/
private $deserializations = [];

/**
* @var SerializerInterface
*/
private $delegate;
private $serializerDelegate;
private $serializerActionFactory;

public function __construct(SerializerInterface $delegate)
public function __construct(SerializerInterface $serializerDelegate, SerializerActionFactoryInterface $serializerActionFactory)
{
$this->delegate = $delegate;
$this->serializerDelegate = $serializerDelegate;
$this->serializerActionFactory = $serializerActionFactory;
}

public function serialize($data, string $format, array $context = []): string
{
$result = $this->delegate->serialize($data, $format, $context);
$this->serializations[] = new Serialization($data, $result, $format, $context);
$result = $this->serializerDelegate->serialize($data, $format, $context);
$this->serializations[] = $this->serializerActionFactory->createSerialization($data, $result, $format, $context);

return $result;
}

public function deserialize($data, string $type, string $format, array $context = [])
{
$result = $this->delegate->deserialize($data, $type, $format, $context);
$this->deserializations[] = new Deserialization($data, $result, $type, $format, $context);
$result = $this->serializerDelegate->deserialize($data, $type, $format, $context);
$this->deserializations[] = $this->serializerActionFactory->createDeserialization($data, $result, $type, $format, $context);

return $result;
}
Expand Down

0 comments on commit cfe328a

Please sign in to comment.