Merge branch '4.4'

* 4.4:
  [FrameworkBundle] added type-hints on private methods
  add parameter type declarations to private methods
  add missing changelog entry
  Avoid using huge amount of memory when formatting long exception
  [VarDumper] added support for Imagine/Image
  clarify error handler restoring process
  [Messenger][Profiler] Attempt to give more useful source info when using HandleTrait
  [VarDumper] cs fix
  [DI] fix perf issue with lazy autowire error messages
  [Messenger][Profiler] Collect the stamps at the end of dispatch
  Fix pluralizing "season"
This commit is contained in:
Fabien Potencier 2019-07-27 08:44:55 +02:00
commit f815bce56e
18 changed files with 269 additions and 43 deletions

View File

@ -51,6 +51,7 @@ CHANGELOG
4.1.0
-----
* The `switch_user.stateless` firewall option is deprecated, use the `stateless` option instead.
* The `logout_on_user_change` firewall option is deprecated.
* deprecated `SecurityUserValueResolver`, use
`Symfony\Component\Security\Http\Controller\UserValueResolver` instead.

View File

@ -10,11 +10,12 @@ CHANGELOG
4.4.0
-----
* Added button to clear the ajax request tab
* Deprecated the `ExceptionController::templateExists()` method
* Deprecated the `TemplateManager::templateExists()` method
* Deprecated the `ExceptionController` in favor of `ExceptionPanelController`
* Marked all classes of the WebProfilerBundle as internal
* added button to clear the ajax request tab
* deprecated the `ExceptionController::templateExists()` method
* deprecated the `TemplateManager::templateExists()` method
* deprecated the `ExceptionController` in favor of `ExceptionPanelController`
* marked all classes of the WebProfilerBundle as internal
* added a section with the stamps of a message after it is dispatched in the Messenger panel
4.3.0
-----

View File

@ -45,7 +45,7 @@
{{ parent() }}
<style>
.message-item thead th { position: relative; cursor: pointer; user-select: none; padding-right: 35px; }
.message-item tbody tr td:first-child { width: 115px; }
.message-item tbody tr td:first-child { width: 170px; }
.message-item .label { float: right; padding: 1px 5px; opacity: .75; margin-left: 5px; }
.message-item .toggle-button { position: absolute; right: 6px; top: 6px; opacity: .5; pointer-events: none }
@ -166,7 +166,7 @@
<td>{{ profiler_dump(dispatchCall.message.value, maxDepth=2) }}</td>
</tr>
<tr>
<td class="text-bold">Envelope stamps</td>
<td class="text-bold">Envelope stamps <span class="text-muted">when dispatching</span></td>
<td>
{% for item in dispatchCall.stamps %}
{{ profiler_dump(item) }}
@ -175,6 +175,18 @@
{% endfor %}
</td>
</tr>
{% if dispatchCall.stamps_after_dispatch is defined %}
<tr>
<td class="text-bold">Envelope stamps <span class="text-muted">after dispatch</span></td>
<td>
{% for item in dispatchCall.stamps_after_dispatch %}
{{ profiler_dump(item) }}
{% else %}
<span class="text-muted">No items</span>
{% endfor %}
</td>
</tr>
{% endif %}
{% if dispatchCall.exception is defined %}
<tr>
<td class="text-bold">Exception</td>

View File

@ -1111,15 +1111,21 @@ class Application implements ResetInterface
$utf8String = mb_convert_encoding($string, 'utf8', $encoding);
$lines = [];
$line = '';
foreach (preg_split('//u', $utf8String) as $char) {
// test if $char could be appended to current line
if (mb_strwidth($line.$char, 'utf8') <= $width) {
$line .= $char;
continue;
$offset = 0;
while (preg_match('/.{1,10000}/u', $utf8String, $m, 0, $offset)) {
$offset += \strlen($m[0]);
foreach (preg_split('//u', $m[0]) as $char) {
// test if $char could be appended to current line
if (mb_strwidth($line.$char, 'utf8') <= $width) {
$line .= $char;
continue;
}
// if not, push current line to array and make new line
$lines[] = str_pad($line, $width);
$line = $char;
}
// if not, push current line to array and make new line
$lines[] = str_pad($line, $width);
$line = $char;
}
$lines[] = \count($lines) ? str_pad($line, $width) : $line;

View File

@ -37,6 +37,7 @@ class AutowirePass extends AbstractRecursivePass
private $getPreviousValue;
private $decoratedMethodIndex;
private $decoratedMethodArgumentIndex;
private $typesClone;
public function __construct(bool $throwOnAutowireException = true)
{
@ -49,16 +50,16 @@ class AutowirePass extends AbstractRecursivePass
public function process(ContainerBuilder $container)
{
try {
$this->typesClone = clone $this;
parent::process($container);
} finally {
$this->types = null;
$this->ambiguousServiceTypes = null;
$this->decoratedClass = null;
$this->decoratedId = null;
$this->methodCalls = null;
$this->getPreviousValue = null;
$this->decoratedMethodIndex = null;
$this->decoratedMethodArgumentIndex = null;
$this->typesClone = null;
}
}
@ -375,20 +376,22 @@ class AutowirePass extends AbstractRecursivePass
private function createTypeNotFoundMessageCallback(TypedReference $reference, string $label)
{
$container = new ContainerBuilder($this->container->getParameterBag());
$container->setAliases($this->container->getAliases());
$container->setDefinitions($this->container->getDefinitions());
$container->setResourceTracking(false);
if (null === $this->typesClone->container) {
$this->typesClone->container = new ContainerBuilder($this->container->getParameterBag());
$this->typesClone->container->setAliases($this->container->getAliases());
$this->typesClone->container->setDefinitions($this->container->getDefinitions());
$this->typesClone->container->setResourceTracking(false);
}
$currentId = $this->currentId;
return function () use ($container, $reference, $label, $currentId) {
return $this->createTypeNotFoundMessage($container, $reference, $label, $currentId);
};
return (function () use ($reference, $label, $currentId) {
return $this->createTypeNotFoundMessage($reference, $label, $currentId);
})->bindTo($this->typesClone);
}
private function createTypeNotFoundMessage(ContainerBuilder $container, TypedReference $reference, string $label, string $currentId)
private function createTypeNotFoundMessage(TypedReference $reference, string $label, string $currentId)
{
if (!$r = $container->getReflectionClass($type = $reference->getType(), false)) {
if (!$r = $this->container->getReflectionClass($type = $reference->getType(), false)) {
// either $type does not exist or a parent class does not exist
try {
$resource = new ClassExistenceResource($type, false);
@ -401,8 +404,8 @@ class AutowirePass extends AbstractRecursivePass
$message = sprintf('has type "%s" but this class %s.', $type, $parentMsg ? sprintf('is missing a parent class (%s)', $parentMsg) : 'was not found');
} else {
$alternatives = $this->createTypeAlternatives($container, $reference);
$message = $container->has($type) ? 'this service is abstract' : 'no such service exists';
$alternatives = $this->createTypeAlternatives($this->container, $reference);
$message = $this->container->has($type) ? 'this service is abstract' : 'no such service exists';
$message = sprintf('references %s "%s" but %s.%s', $r->isInterface() ? 'interface' : 'class', $type, $message, $alternatives);
if ($r->isInterface() && !$alternatives) {

View File

@ -50,10 +50,9 @@ class CacheWarmerAggregate implements CacheWarmerInterface
*/
public function warmUp($cacheDir)
{
if ($this->debug) {
if ($collectDeprecations = $this->debug && !\defined('PHPUNIT_COMPOSER_INSTALL')) {
$collectedLogs = [];
$previousHandler = \defined('PHPUNIT_COMPOSER_INSTALL');
$previousHandler = $previousHandler ?: set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) {
$previousHandler = set_error_handler(function ($type, $message, $file, $line) use (&$collectedLogs, &$previousHandler) {
if (E_USER_DEPRECATED !== $type && E_DEPRECATED !== $type) {
return $previousHandler ? $previousHandler($type, $message, $file, $line) : false;
}
@ -96,7 +95,7 @@ class CacheWarmerAggregate implements CacheWarmerInterface
$warmer->warmUp($cacheDir);
}
} finally {
if ($this->debug && true !== $previousHandler) {
if ($collectDeprecations) {
restore_error_handler();
if (file_exists($this->deprecationLogsFilepath)) {

View File

@ -228,6 +228,9 @@ final class Inflector
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
['noi', 3, true, true, 'ions'],
// seasons (season), treasons (treason), poisons (poison), lessons (lesson)
['nos', 3, true, true, 'sons'],
// bacteria (bacterium), criteria (criterion), phenomena (phenomenon)
['no', 2, true, true, 'a'],

View File

@ -93,6 +93,7 @@ class InflectorTest extends TestCase
['kisses', 'kiss'],
['knives', 'knife'],
['lamps', 'lamp'],
['lessons', 'lesson'],
['leaves', ['leaf', 'leave', 'leaff']],
['lice', 'louse'],
['lives', 'life'],
@ -115,6 +116,7 @@ class InflectorTest extends TestCase
['photos', 'photo'],
['pianos', 'piano'],
['plateaux', 'plateau'],
['poisons', 'poison'],
['poppies', 'poppy'],
['prices', ['prex', 'prix', 'price']],
['quizzes', 'quiz'],
@ -124,6 +126,7 @@ class InflectorTest extends TestCase
['sandwiches', ['sandwich', 'sandwiche']],
['scarves', ['scarf', 'scarve', 'scarff']],
['schemas', 'schema'], //schemata
['seasons', 'season'],
['selfies', 'selfie'],
['series', 'series'],
['services', 'service'],
@ -139,6 +142,7 @@ class InflectorTest extends TestCase
['teeth', 'tooth'],
['theses', ['thes', 'these', 'thesis']],
['thieves', ['thief', 'thieve', 'thieff']],
['treasons', 'treason'],
['trees', ['tre', 'tree']],
['waltzes', ['waltz', 'waltze']],
['wives', 'wife'],
@ -226,6 +230,7 @@ class InflectorTest extends TestCase
['knife', 'knives'],
['lamp', 'lamps'],
['leaf', ['leafs', 'leaves']],
['lesson', 'lessons'],
['life', 'lives'],
['louse', 'lice'],
['man', 'men'],
@ -245,6 +250,7 @@ class InflectorTest extends TestCase
['photo', 'photos'],
['piano', 'pianos'],
['plateau', ['plateaus', 'plateaux']],
['poison', 'poisons'],
['poppy', 'poppies'],
['price', 'prices'],
['quiz', 'quizzes'],
@ -254,6 +260,7 @@ class InflectorTest extends TestCase
['sandwich', 'sandwiches'],
['scarf', ['scarfs', 'scarves']],
['schema', 'schemas'], //schemata
['season', 'seasons'],
['selfie', 'selfies'],
['series', 'series'],
['service', 'services'],
@ -268,6 +275,7 @@ class InflectorTest extends TestCase
['tag', 'tags'],
['thief', ['thiefs', 'thieves']],
['tooth', 'teeth'],
['treason', 'treasons'],
['tree', 'trees'],
['waltz', 'waltzes'],
['wife', 'wives'],

View File

@ -99,6 +99,7 @@ class MessengerDataCollector extends DataCollector implements LateDataCollectorI
$debugRepresentation = [
'bus' => $busName,
'stamps' => $tracedMessage['stamps'] ?? null,
'stamps_after_dispatch' => $tracedMessage['stamps_after_dispatch'] ?? null,
'message' => [
'type' => new ClassStub(\get_class($message)),
'value' => $message,

View File

@ -55,9 +55,10 @@ class MessengerDataCollectorTest extends TestCase
$file = __FILE__;
$expected = <<<DUMP
array:4 [
array:5 [
"bus" => "default"
"stamps" => []
"stamps_after_dispatch" => []
"message" => array:2 [
"type" => "Symfony\Component\Messenger\Tests\Fixtures\DummyMessage"
"value" => Symfony\Component\Messenger\Tests\Fixtures\DummyMessage %A
@ -100,9 +101,10 @@ DUMP;
$file = __FILE__;
$this->assertStringMatchesFormat(<<<DUMP
array:5 [
array:6 [
"bus" => "default"
"stamps" => []
"stamps_after_dispatch" => []
"message" => array:2 [
"type" => "Symfony\Component\Messenger\Tests\Fixtures\DummyMessage"
"value" => Symfony\Component\Messenger\Tests\Fixtures\DummyMessage %A

View File

@ -0,0 +1,33 @@
<?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\Messenger\Tests\Fixtures;
use Symfony\Component\Messenger\HandleTrait;
use Symfony\Component\Messenger\MessageBusInterface;
/**
* @see \Symfony\Component\Messenger\Tests\TraceableMessageBusTest::testItTracesDispatchWhenHandleTraitIsUsed
*/
class TestTracesWithHandleTraitAction
{
use HandleTrait;
public function __construct(MessageBusInterface $messageBus)
{
$this->messageBus = $messageBus;
}
public function __invoke($message)
{
$this->handle($message);
}
}

View File

@ -15,8 +15,10 @@ use PHPUnit\Framework\TestCase;
use Symfony\Component\Messenger\Envelope;
use Symfony\Component\Messenger\MessageBusInterface;
use Symfony\Component\Messenger\Stamp\DelayStamp;
use Symfony\Component\Messenger\Stamp\HandledStamp;
use Symfony\Component\Messenger\Tests\Fixtures\AnEnvelopeStamp;
use Symfony\Component\Messenger\Tests\Fixtures\DummyMessage;
use Symfony\Component\Messenger\Tests\Fixtures\TestTracesWithHandleTraitAction;
use Symfony\Component\Messenger\TraceableMessageBus;
class TraceableMessageBusTest extends TestCase
@ -27,7 +29,7 @@ class TraceableMessageBusTest extends TestCase
$stamp = new DelayStamp(5);
$bus = $this->getMockBuilder(MessageBusInterface::class)->getMock();
$bus->expects($this->once())->method('dispatch')->with($message, [$stamp])->willReturn(new Envelope($message));
$bus->expects($this->once())->method('dispatch')->with($message, [$stamp])->willReturn(new Envelope($message, [$stamp]));
$traceableBus = new TraceableMessageBus($bus);
$line = __LINE__ + 1;
@ -38,6 +40,7 @@ class TraceableMessageBusTest extends TestCase
$this->assertEquals([
'message' => $message,
'stamps' => [$stamp],
'stamps_after_dispatch' => [$stamp],
'caller' => [
'name' => 'TraceableMessageBusTest.php',
'file' => __FILE__,
@ -46,6 +49,29 @@ class TraceableMessageBusTest extends TestCase
], $actualTracedMessage);
}
public function testItTracesDispatchWhenHandleTraitIsUsed()
{
$message = new DummyMessage('Hello');
$bus = $this->getMockBuilder(MessageBusInterface::class)->getMock();
$bus->expects($this->once())->method('dispatch')->with($message)->willReturn((new Envelope($message))->with(new HandledStamp('result', 'handlerName')));
$traceableBus = new TraceableMessageBus($bus);
(new TestTracesWithHandleTraitAction($traceableBus))($message);
$this->assertCount(1, $tracedMessages = $traceableBus->getDispatchedMessages());
$actualTracedMessage = $tracedMessages[0];
unset($actualTracedMessage['callTime']); // don't check, too variable
$this->assertEquals([
'message' => $message,
'stamps' => [],
'caller' => [
'name' => 'TestTracesWithHandleTraitAction.php',
'file' => (new \ReflectionClass(TestTracesWithHandleTraitAction::class))->getFileName(),
'line' => (new \ReflectionMethod(TestTracesWithHandleTraitAction::class, '__invoke'))->getStartLine() + 2,
],
], $actualTracedMessage);
}
public function testItTracesDispatchWithEnvelope()
{
$message = new DummyMessage('Hello');
@ -63,6 +89,33 @@ class TraceableMessageBusTest extends TestCase
$this->assertEquals([
'message' => $message,
'stamps' => [$stamp],
'stamps_after_dispatch' => [$stamp],
'caller' => [
'name' => 'TraceableMessageBusTest.php',
'file' => __FILE__,
'line' => $line,
],
], $actualTracedMessage);
}
public function testItCollectsStampsAddedDuringDispatch()
{
$message = new DummyMessage('Hello');
$envelope = (new Envelope($message))->with($stamp = new AnEnvelopeStamp());
$bus = $this->getMockBuilder(MessageBusInterface::class)->getMock();
$bus->expects($this->once())->method('dispatch')->with($envelope)->willReturn($envelope->with($anotherStamp = new AnEnvelopeStamp()));
$traceableBus = new TraceableMessageBus($bus);
$line = __LINE__ + 1;
$traceableBus->dispatch($envelope);
$this->assertCount(1, $tracedMessages = $traceableBus->getDispatchedMessages());
$actualTracedMessage = $tracedMessages[0];
unset($actualTracedMessage['callTime']); // don't check, too variable
$this->assertEquals([
'message' => $message,
'stamps' => [$stamp],
'stamps_after_dispatch' => [$stamp, $anotherStamp],
'caller' => [
'name' => 'TraceableMessageBusTest.php',
'file' => __FILE__,
@ -94,6 +147,7 @@ class TraceableMessageBusTest extends TestCase
'message' => $message,
'exception' => $exception,
'stamps' => [],
'stamps_after_dispatch' => [],
'caller' => [
'name' => 'TraceableMessageBusTest.php',
'file' => __FILE__,

View File

@ -38,13 +38,13 @@ class TraceableMessageBus implements MessageBusInterface
];
try {
return $this->decoratedBus->dispatch($message, $stamps);
return $envelope = $this->decoratedBus->dispatch($message, $stamps);
} catch (\Throwable $e) {
$context['exception'] = $e;
throw $e;
} finally {
$this->dispatchedMessages[] = $context;
$this->dispatchedMessages[] = $context + ['stamps_after_dispatch' => array_merge([], ...array_values($envelope->all()))];
}
}
@ -65,7 +65,19 @@ class TraceableMessageBus implements MessageBusInterface
$file = $trace[1]['file'];
$line = $trace[1]['line'];
for ($i = 2; $i < 8; ++$i) {
$handleTraitFile = (new \ReflectionClass(HandleTrait::class))->getFileName();
$found = false;
for ($i = 1; $i < 8; ++$i) {
if (isset($trace[$i]['file'], $trace[$i + 1]['file'], $trace[$i + 1]['line']) && $trace[$i]['file'] === $handleTraitFile) {
$file = $trace[$i + 1]['file'];
$line = $trace[$i + 1]['line'];
$found = true;
break;
}
}
for ($i = 2; $i < 8 && !$found; ++$i) {
if (isset($trace[$i]['class'], $trace[$i]['function'])
&& 'dispatch' === $trace[$i]['function']
&& is_a($trace[$i]['class'], MessageBusInterface::class, true)

View File

@ -4,8 +4,10 @@ CHANGELOG
4.4.0
-----
* added `VarDumperTestTrait::setUpVarDumper()` and `VarDumperTestTrait::tearDownVarDumper()`
* added `VarDumperTestTrait::setUpVarDumper()` and `VarDumperTestTrait::tearDownVarDumper()`
to configure casters & flags to use in tests
* added `ImagineCaster` and infrastructure to dump images
* added the stamps of a message after it is dispatched in `TraceableMessageBus` and `MessengerDataCollector` collected data
4.3.0
-----

View File

@ -0,0 +1,37 @@
<?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\VarDumper\Caster;
use Imagine\Image\ImageInterface;
use Symfony\Component\VarDumper\Cloner\Stub;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class ImagineCaster
{
public static function castImage(ImageInterface $c, array $a, Stub $stub, $isNested)
{
$imgData = $c->get('png');
if (\strlen($imgData) > 1 * 1000 * 1000) {
$a += [
Caster::PREFIX_VIRTUAL.'image' => new ConstStub($c->getSize()),
];
} else {
$a += [
Caster::PREFIX_VIRTUAL.'image' => new ImgStub($imgData, 'image/png', $c->getSize()),
];
}
return $a;
}
}

View File

@ -0,0 +1,26 @@
<?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\VarDumper\Caster;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class ImgStub extends ConstStub
{
public function __construct(string $data, string $contentType, string $size)
{
$this->value = '';
$this->attr['img-data'] = $data;
$this->attr['img-size'] = $size;
$this->attr['content-type'] = $contentType;
}
}

View File

@ -87,6 +87,8 @@ abstract class AbstractCloner implements ClonerInterface
'Symfony\Component\VarDumper\Cloner\AbstractCloner' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
'Symfony\Component\ErrorHandler\Exception\SilencedErrorContext' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castSilencedErrorContext'],
'Imagine\Image\ImageInterface' => ['Symfony\Component\VarDumper\Caster\ImagineCaster', 'castImage'],
'ProxyManager\Proxy\ProxyInterface' => ['Symfony\Component\VarDumper\Caster\ProxyManagerCaster', 'castProxy'],
'PHPUnit_Framework_MockObject_MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
'Prophecy\Prophecy\ProphecySubjectInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],

View File

@ -473,7 +473,7 @@ return function (root, x) {
return this.current();
}
this.idx = this.idx < (this.nodes.length - 1) ? this.idx + 1 : 0;
return this.current();
},
previous: function () {
@ -481,7 +481,7 @@ return function (root, x) {
return this.current();
}
this.idx = this.idx > 0 ? this.idx - 1 : (this.nodes.length - 1);
return this.current();
},
isEmpty: function () {
@ -565,11 +565,11 @@ return function (root, x) {
"sf-dump-protected",
"sf-dump-private",
].map(xpathHasClass).join(' or ');
var xpathResult = doc.evaluate('.//span[' + classMatches + '][contains(translate(child::text(), ' + xpathString(searchQuery.toUpperCase()) + ', ' + xpathString(searchQuery.toLowerCase()) + '), ' + xpathString(searchQuery.toLowerCase()) + ')]', root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
while (node = xpathResult.iterateNext()) state.nodes.push(node);
showCurrent(state);
}, 400);
});
@ -682,6 +682,13 @@ pre.sf-dump a {
outline: none;
color: inherit;
}
pre.sf-dump img {
max-width: 50em;
max-height: 50em;
margin: .5em 0 0 0;
padding: 0;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAAHUlEQVQY02O8zAABilCaiQEN0EeA8QuUcX9g3QEAAjcC5piyhyEAAAAASUVORK5CYII=) #D3D3D3;
}
pre.sf-dump .sf-dump-ellipsis {
display: inline-block;
overflow: visible;
@ -792,6 +799,23 @@ EOHTML
return $this->dumpHeader = preg_replace('/\s+/', ' ', $line).'</style>'.$this->dumpHeader;
}
/**
* {@inheritdoc}
*/
public function dumpString(Cursor $cursor, $str, $bin, $cut)
{
if ('' === $str && isset($cursor->attr['img-data'], $cursor->attr['content-type'])) {
$this->dumpKey($cursor);
$this->line .= $this->style('default', $cursor->attr['img-size'] ?? '', []).' <samp>';
$this->endValue($cursor);
$this->line .= $this->indentPad;
$this->line .= sprintf('<img src="data:%s;base64,%s" /></samp>', $cursor->attr['content-type'], base64_encode($cursor->attr['img-data']));
$this->endValue($cursor);
} else {
parent::dumpString($cursor, $str, $bin, $cut);
}
}
/**
* {@inheritdoc}
*/