From ab716c64de1fdfc74bb07653666733e3f9406b62 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 15 Feb 2017 11:50:57 +0100 Subject: [PATCH] [VarDumper] Allow seamless use of Data clones --- .../DataCollector/SecurityDataCollector.php | 24 +- .../SecurityDataCollectorTest.php | 12 +- .../Bundle/SecurityBundle/composer.json | 7 +- .../Resources/views/Collector/cache.html.twig | 2 +- .../views/Collector/config.html.twig | 2 +- .../views/Collector/events.html.twig | 2 +- .../views/Collector/logger.html.twig | 2 +- .../Twig/WebProfilerExtension.php | 6 +- .../Bundle/WebProfilerBundle/composer.json | 5 +- .../DataCollector/CacheDataCollector.php | 15 +- src/Symfony/Component/Cache/composer.json | 3 + .../EventDispatcher/Debug/WrappedListener.php | 8 +- .../Debug/TraceableEventDispatcherTest.php | 8 +- .../DataCollector/FormDataCollector.php | 82 ++---- src/Symfony/Component/Form/composer.json | 5 +- .../DataCollector/ConfigDataCollector.php | 6 +- .../DataCollector/DataCollector.php | 54 +--- .../DataCollector/EventDataCollector.php | 1 + .../DataCollector/LoggerDataCollector.php | 4 +- .../DataCollector/RequestDataCollector.php | 44 +--- .../Tests/DataCollector/DataCollectorTest.php | 16 +- .../DataCollector/LoggerDataCollectorTest.php | 39 ++- .../RequestDataCollectorTest.php | 8 +- .../Tests/EventListener/DumpListenerTest.php | 6 +- .../Tests/Profiler/ProfilerTest.php | 3 +- .../Component/HttpKernel/composer.json | 5 +- .../TranslationDataCollector.php | 6 +- .../TranslationDataCollectorTest.php | 14 +- .../Component/VarDumper/Caster/Caster.php | 39 ++- .../VarDumper/Caster/ExceptionCaster.php | 2 +- .../VarDumper/Caster/ReflectionCaster.php | 13 +- .../Component/VarDumper/Caster/SplCaster.php | 2 +- .../VarDumper/Cloner/AbstractCloner.php | 244 +++++++++--------- .../Component/VarDumper/Cloner/Data.php | 141 +++++++++- .../VarDumper/Tests/Cloner/DataTest.php | 115 +++++++++ .../Tests/{ => Cloner}/VarClonerTest.php | 2 +- .../Tests/{ => Dumper}/CliDumperTest.php | 10 +- .../Tests/{ => Dumper}/HtmlDumperTest.php | 10 +- 38 files changed, 558 insertions(+), 409 deletions(-) create mode 100644 src/Symfony/Component/VarDumper/Tests/Cloner/DataTest.php rename src/Symfony/Component/VarDumper/Tests/{ => Cloner}/VarClonerTest.php (99%) rename src/Symfony/Component/VarDumper/Tests/{ => Dumper}/CliDumperTest.php (97%) rename src/Symfony/Component/VarDumper/Tests/{ => Dumper}/HtmlDumperTest.php (95%) diff --git a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php index f0fa6790c3..bb17d401d1 100644 --- a/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php +++ b/src/Symfony/Bundle/SecurityBundle/DataCollector/SecurityDataCollector.php @@ -20,6 +20,7 @@ use Symfony\Component\Security\Core\Role\RoleInterface; use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Authorization\TraceableAccessDecisionManager; +use Symfony\Component\VarDumper\Caster\ClassStub; use Symfony\Component\VarDumper\Cloner\Data; use Symfony\Component\Security\Http\FirewallMapInterface; use Symfony\Bundle\SecurityBundle\Security\FirewallMap; @@ -36,6 +37,7 @@ class SecurityDataCollector extends DataCollector private $logoutUrlGenerator; private $accessDecisionManager; private $firewallMap; + private $hasVarDumper; /** * Constructor. @@ -53,6 +55,7 @@ class SecurityDataCollector extends DataCollector $this->logoutUrlGenerator = $logoutUrlGenerator; $this->accessDecisionManager = $accessDecisionManager; $this->firewallMap = $firewallMap; + $this->hasVarDumper = class_exists(ClassStub::class); } /** @@ -109,28 +112,23 @@ class SecurityDataCollector extends DataCollector $this->data = array( 'enabled' => true, 'authenticated' => $token->isAuthenticated(), - 'token' => $this->cloneVar($token), - 'token_class' => get_class($token), + 'token' => $token, + 'token_class' => $this->hasVarDumper ? new ClassStub(get_class($token)) : get_class($token), 'logout_url' => $logoutUrl, 'user' => $token->getUsername(), - 'roles' => $this->cloneVar(array_map(function (RoleInterface $role) { return $role->getRole(); }, $assignedRoles)), - 'inherited_roles' => $this->cloneVar(array_map(function (RoleInterface $role) { return $role->getRole(); }, $inheritedRoles)), + 'roles' => array_map(function (RoleInterface $role) { return $role->getRole(); }, $assignedRoles), + 'inherited_roles' => array_map(function (RoleInterface $role) { return $role->getRole(); }, $inheritedRoles), 'supports_role_hierarchy' => null !== $this->roleHierarchy, ); } // collect voters and access decision manager information if ($this->accessDecisionManager instanceof TraceableAccessDecisionManager) { - $this->data['access_decision_log'] = array_map(function ($decision) { - $decision['object'] = $this->cloneVar($decision['object']); - - return $decision; - }, $this->accessDecisionManager->getDecisionLog()); - + $this->data['access_decision_log'] = $this->accessDecisionManager->getDecisionLog(); $this->data['voter_strategy'] = $this->accessDecisionManager->getStrategy(); foreach ($this->accessDecisionManager->getVoters() as $voter) { - $this->data['voters'][] = get_class($voter); + $this->data['voters'][] = $this->hasVarDumper ? new ClassStub(get_class($voter)) : get_class($voter); } } else { $this->data['access_decision_log'] = array(); @@ -155,10 +153,12 @@ class SecurityDataCollector extends DataCollector 'access_denied_handler' => $firewallConfig->getAccessDeniedHandler(), 'access_denied_url' => $firewallConfig->getAccessDeniedUrl(), 'user_checker' => $firewallConfig->getUserChecker(), - 'listeners' => $this->cloneVar($firewallConfig->getListeners()), + 'listeners' => $firewallConfig->getListeners(), ); } } + + $this->data = $this->cloneVar($this->data); } /** diff --git a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php index a5cd0fd1fd..b6f1f980e4 100644 --- a/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php +++ b/src/Symfony/Bundle/SecurityBundle/Tests/DataCollector/SecurityDataCollectorTest.php @@ -66,14 +66,10 @@ class SecurityDataCollectorTest extends TestCase $this->assertTrue($collector->isEnabled()); $this->assertTrue($collector->isAuthenticated()); - $this->assertSame('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', $collector->getTokenClass()); + $this->assertSame('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', $collector->getTokenClass()->getValue()); $this->assertTrue($collector->supportsRoleHierarchy()); - $this->assertSame($normalizedRoles, $collector->getRoles()->getRawData()[1]); - if ($inheritedRoles) { - $this->assertSame($inheritedRoles, $collector->getInheritedRoles()->getRawData()[1]); - } else { - $this->assertSame($inheritedRoles, $collector->getInheritedRoles()->getRawData()[0][0]); - } + $this->assertSame($normalizedRoles, $collector->getRoles()->getValue(true)); + $this->assertSame($inheritedRoles, $collector->getInheritedRoles()->getValue(true)); $this->assertSame('hhamon', $collector->getUser()); } @@ -107,7 +103,7 @@ class SecurityDataCollectorTest extends TestCase $this->assertSame($firewallConfig->getAccessDeniedHandler(), $collected['access_denied_handler']); $this->assertSame($firewallConfig->getAccessDeniedUrl(), $collected['access_denied_url']); $this->assertSame($firewallConfig->getUserChecker(), $collected['user_checker']); - $this->assertSame($firewallConfig->getListeners(), $collected['listeners']->getRawData()[0][0]); + $this->assertSame($firewallConfig->getListeners(), $collected['listeners']->getValue()); } public function testGetFirewallReturnsNull() diff --git a/src/Symfony/Bundle/SecurityBundle/composer.json b/src/Symfony/Bundle/SecurityBundle/composer.json index 337d5fb826..ae1bb02f72 100644 --- a/src/Symfony/Bundle/SecurityBundle/composer.json +++ b/src/Symfony/Bundle/SecurityBundle/composer.json @@ -19,7 +19,7 @@ "php": ">=5.5.9", "symfony/security": "~3.3", "symfony/dependency-injection": "~3.3", - "symfony/http-kernel": "~3.2", + "symfony/http-kernel": "~3.3", "symfony/polyfill-php70": "~1.0" }, "require-dev": { @@ -37,12 +37,15 @@ "symfony/twig-bridge": "~2.8|~3.0", "symfony/process": "~2.8|~3.0", "symfony/validator": "^3.2.5", - "symfony/var-dumper": "~3.2", + "symfony/var-dumper": "~3.3", "symfony/yaml": "~2.8|~3.0", "symfony/expression-language": "~2.8|~3.0", "doctrine/doctrine-bundle": "~1.4", "twig/twig": "~1.28|~2.0" }, + "conflict": { + "symfony/var-dumper": "<3.3" + }, "suggest": { "symfony/security-acl": "For using the ACL functionality of this bundle" }, diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/cache.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/cache.html.twig index 3f52d643a2..c71a691817 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/cache.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/cache.html.twig @@ -90,7 +90,7 @@
{% if key == 'time' %} - {{ '%0.2f'|format(1000*value) }} ms + {{ '%0.2f'|format(1000*value.value) }} ms {% else %} {{ value }} {% endif %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig index 217f5e8a84..99e12f0f00 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/config.html.twig @@ -267,7 +267,7 @@ {% for name in collector.bundles|keys|sort %} {{ name }} - {{ collector.bundles[name] }} + {{ profiler_dump(collector.bundles[name]) }} {% endfor %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig index 401410f41c..238096157a 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/events.html.twig @@ -75,7 +75,7 @@ {{ listener.priority|default('-') }} - {{ profiler_dump(listener.data) }} + {{ profiler_dump(listener.stub) }} {% if loop.last %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig index e03b5fff31..554e1e61dd 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig +++ b/src/Symfony/Bundle/WebProfilerBundle/Resources/views/Collector/logger.html.twig @@ -182,7 +182,7 @@ Show trace
- {{ profiler_dump(log.context.seek('exception').seek('\0Exception\0trace'), maxDepth=2) }} + {{ profiler_dump(log.context.exception['\0Exception\0trace'], maxDepth=2) }}
{% elseif log.context is defined and log.context is not empty %} diff --git a/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php b/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php index 032d6f08bb..664763ea67 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php +++ b/src/Symfony/Bundle/WebProfilerBundle/Twig/WebProfilerExtension.php @@ -98,9 +98,9 @@ class WebProfilerExtension extends \Twig_Extension_Profiler } $replacements = array(); - foreach ($context->getRawData()[1] as $k => $v) { - $v = '{'.twig_escape_filter($env, $k).'}'; - $replacements['"'.$v.'"'] = $replacements[$v] = $this->dumpData($env, $context->seek($k)); + foreach ($context as $k => $v) { + $k = '{'.twig_escape_filter($env, $k).'}'; + $replacements['"'.$k.'"'] = $replacements[$k] = $this->dumpData($env, $v); } return ''.strtr($message, $replacements).''; diff --git a/src/Symfony/Bundle/WebProfilerBundle/composer.json b/src/Symfony/Bundle/WebProfilerBundle/composer.json index 57656312bc..22ad5c074c 100644 --- a/src/Symfony/Bundle/WebProfilerBundle/composer.json +++ b/src/Symfony/Bundle/WebProfilerBundle/composer.json @@ -22,7 +22,7 @@ "symfony/routing": "~2.8|~3.0", "symfony/twig-bridge": "~2.8|~3.0", "twig/twig": "~1.28|~2.0", - "symfony/var-dumper": "~3.2" + "symfony/var-dumper": "~3.3" }, "require-dev": { "symfony/config": "~2.8|~3.0", @@ -31,7 +31,8 @@ "symfony/stopwatch": "~2.8|~3.0" }, "conflict": { - "symfony/event-dispatcher": "<3.2" + "symfony/event-dispatcher": "<3.2", + "symfony/var-dumper": "<3.3" }, "autoload": { "psr-4": { "Symfony\\Bundle\\WebProfilerBundle\\": "" }, diff --git a/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php b/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php index 200867c0ec..edbd726351 100644 --- a/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php +++ b/src/Symfony/Component/Cache/DataCollector/CacheDataCollector.php @@ -45,20 +45,13 @@ class CacheDataCollector extends DataCollector $empty = array('calls' => array(), 'config' => array(), 'options' => array(), 'statistics' => array()); $this->data = array('instances' => $empty, 'total' => $empty); foreach ($this->instances as $name => $instance) { - $calls = $instance->getCalls(); - foreach ($calls as $call) { - if (isset($call->result)) { - $call->result = $this->cloneVar($call->result); - } - if (isset($call->argument)) { - $call->argument = $this->cloneVar($call->argument); - } - } - $this->data['instances']['calls'][$name] = $calls; + $this->data['instances']['calls'][$name] = $instance->getCalls(); } $this->data['instances']['statistics'] = $this->calculateStatistics(); $this->data['total']['statistics'] = $this->calculateTotalStatistics(); + + $this->data = $this->cloneVar($this->data); } /** @@ -133,7 +126,7 @@ class CacheDataCollector extends DataCollector $statistics[$name]['misses'] += $count - $call->misses; } elseif ('hasItem' === $call->name) { $statistics[$name]['reads'] += 1; - if (false === $call->result->getRawData()[0][0]) { + if (false === $call->result) { $statistics[$name]['misses'] += 1; } else { $statistics[$name]['hits'] += 1; diff --git a/src/Symfony/Component/Cache/composer.json b/src/Symfony/Component/Cache/composer.json index f3bc3988fa..a132099ca1 100644 --- a/src/Symfony/Component/Cache/composer.json +++ b/src/Symfony/Component/Cache/composer.json @@ -31,6 +31,9 @@ "doctrine/dbal": "~2.4", "predis/predis": "~1.0" }, + "conflict": { + "symfony/var-dumper": "<3.3" + }, "suggest": { "symfony/polyfill-apcu": "For using ApcuAdapter on HHVM" }, diff --git a/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php b/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php index 5e580806e0..130cc54d34 100644 --- a/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php +++ b/src/Symfony/Component/EventDispatcher/Debug/WrappedListener.php @@ -29,7 +29,7 @@ class WrappedListener private $stopwatch; private $dispatcher; private $pretty; - private $data; + private $stub; private static $cloner; @@ -91,15 +91,15 @@ class WrappedListener public function getInfo($eventName) { - if (null === $this->data) { - $this->data = false !== self::$cloner ? self::$cloner->cloneVar(array(new ClassStub($this->pretty.'()', $this->listener)))->seek(0) : $this->pretty; + if (null === $this->stub) { + $this->stub = false === self::$cloner ? $this->pretty.'()' : new ClassStub($this->pretty.'()', $this->listener); } return array( 'event' => $eventName, 'priority' => null !== $this->dispatcher ? $this->dispatcher->getListenerPriority($eventName, $this->listener) : null, 'pretty' => $this->pretty, - 'data' => $this->data, + 'stub' => $this->stub, ); } diff --git a/src/Symfony/Component/EventDispatcher/Tests/Debug/TraceableEventDispatcherTest.php b/src/Symfony/Component/EventDispatcher/Tests/Debug/TraceableEventDispatcherTest.php index 8af193a1cb..8f805bcfc6 100644 --- a/src/Symfony/Component/EventDispatcher/Tests/Debug/TraceableEventDispatcherTest.php +++ b/src/Symfony/Component/EventDispatcher/Tests/Debug/TraceableEventDispatcherTest.php @@ -97,16 +97,16 @@ class TraceableEventDispatcherTest extends TestCase $tdispatcher->addListener('foo', $listener = function () {}); $listeners = $tdispatcher->getNotCalledListeners(); - $this->assertArrayHasKey('data', $listeners['foo.closure']); - unset($listeners['foo.closure']['data']); + $this->assertArrayHasKey('stub', $listeners['foo.closure']); + unset($listeners['foo.closure']['stub']); $this->assertEquals(array(), $tdispatcher->getCalledListeners()); $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'pretty' => 'closure', 'priority' => 0)), $listeners); $tdispatcher->dispatch('foo'); $listeners = $tdispatcher->getCalledListeners(); - $this->assertArrayHasKey('data', $listeners['foo.closure']); - unset($listeners['foo.closure']['data']); + $this->assertArrayHasKey('stub', $listeners['foo.closure']); + unset($listeners['foo.closure']['stub']); $this->assertEquals(array('foo.closure' => array('event' => 'foo', 'pretty' => 'closure', 'priority' => null)), $listeners); $this->assertEquals(array(), $tdispatcher->getNotCalledListeners()); } diff --git a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php index a4b4dcfcc8..b088abc796 100644 --- a/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php +++ b/src/Symfony/Component/Form/Extension/DataCollector/FormDataCollector.php @@ -20,6 +20,7 @@ use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; use Symfony\Component\Validator\ConstraintViolationInterface; use Symfony\Component\VarDumper\Caster\Caster; use Symfony\Component\VarDumper\Caster\ClassStub; +use Symfony\Component\VarDumper\Caster\CutStub; use Symfony\Component\VarDumper\Cloner\ClonerInterface; use Symfony\Component\VarDumper\Cloner\Data; use Symfony\Component\VarDumper\Cloner\Stub; @@ -80,7 +81,8 @@ class FormDataCollector extends DataCollector implements FormDataCollectorInterf * @var ClonerInterface */ private $cloner; - private $clonerCache = array(); + + private $hasVarDumper; public function __construct(FormDataExtractorInterface $dataExtractor) { @@ -90,6 +92,7 @@ class FormDataCollector extends DataCollector implements FormDataCollectorInterf 'forms_by_hash' => array(), 'nb_errors' => 0, ); + $this->hasVarDumper = class_exists(ClassStub::class); } /** @@ -238,38 +241,15 @@ class FormDataCollector extends DataCollector implements FormDataCollectorInterf public function serialize() { - $cloneVar = array($this, 'cloneVar'); - - foreach ($this->data['forms_by_hash'] as &$form) { - foreach ($form as $k => $v) { - switch ($k) { - case 'type_class': - $form[$k] = $cloneVar($v, true); - break; - case 'synchronized': - $form[$k] = $cloneVar($v); - break; - case 'view_vars': - case 'passed_options': - case 'resolved_options': - case 'default_data': - case 'submitted_data': - if ($v && is_array($v)) { - $form[$k] = array_map($cloneVar, $v); - } - break; - case 'errors': - foreach ($v as $i => $e) { - if (!empty($e['trace'])) { - $form['errors'][$i]['trace'] = array_map($cloneVar, $e['trace']); - } - } - break; + if ($this->hasVarDumper) { + foreach ($this->data['forms_by_hash'] as &$form) { + if (isset($form['type_class']) && !$form['type_class'] instanceof ClassStub) { + $form['type_class'] = new ClassStub($form['type_class']); } } } - return serialize($this->data); + return serialize($this->cloneVar($this->data)); } /** @@ -281,14 +261,15 @@ class FormDataCollector extends DataCollector implements FormDataCollectorInterf return $var; } if (null === $this->cloner) { - if (class_exists(ClassStub::class)) { + if ($this->hasVarDumper) { $this->cloner = new VarCloner(); - $this->cloner->setMaxItems(25); + $this->cloner->setMaxItems(-1); $this->cloner->addCasters(array( '*' => function ($v, array $a, Stub $s, $isNested) { - if ($isNested && !$v instanceof \DateTimeInterface) { - $s->cut = -1; - $a = array(); + foreach ($a as &$v) { + if (is_object($v) && !$v instanceof \DateTimeInterface) { + $v = new CutStub($v); + } } return $a; @@ -320,34 +301,15 @@ class FormDataCollector extends DataCollector implements FormDataCollectorInterf $this->cloner = false; } } - if (false === $this->cloner) { - if (null === $this->valueExporter) { - $this->valueExporter = new ValueExporter(); - } - - return $this->valueExporter->exportValue($var); - } - if (null === $var) { - $type = $hash = 'null'; - } elseif (array() === $var) { - $type = $hash = 'array'; - } elseif ('object' === $type = gettype($var)) { - $hash = spl_object_hash($var); - } elseif ('double' === $type) { - $hash = (string) $var; - } elseif ('integer' === $type || 'string' === $type) { - $hash = $var; - } else { - $type = null; - } - if (null !== $type && null !== $cache = &$this->clonerCache[$type][$hash]) { - return $cache; - } - if ($isClass) { - return $cache = $this->cloner->cloneVar(array(new ClassStub($var)))->seek(0); + if (false !== $this->cloner) { + return $this->cloner->cloneVar($var, Caster::EXCLUDE_VERBOSE); } - return $cache = $this->cloner->cloneVar($var); + if (null === $this->valueExporter) { + $this->valueExporter = new ValueExporter(); + } + + return $this->valueExporter->exportValue($var); } private function &recursiveBuildPreliminaryFormTree(FormInterface $form, array &$outputByHash) diff --git a/src/Symfony/Component/Form/composer.json b/src/Symfony/Component/Form/composer.json index 1febae6c88..c15ca0e8e4 100644 --- a/src/Symfony/Component/Form/composer.json +++ b/src/Symfony/Component/Form/composer.json @@ -32,13 +32,14 @@ "symfony/http-kernel": "~2.8|~3.0", "symfony/security-csrf": "~2.8|~3.0", "symfony/translation": "~2.8|~3.0", - "symfony/var-dumper": "~3.2" + "symfony/var-dumper": "~3.3" }, "conflict": { "phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", "symfony/doctrine-bridge": "<2.7", "symfony/framework-bundle": "<2.7", - "symfony/twig-bridge": "<2.7" + "symfony/twig-bridge": "<2.7", + "symfony/var-dumper": "~3.3" }, "suggest": { "symfony/validator": "For form validation.", diff --git a/src/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php index 908aacd9a2..b626c2e5ca 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/ConfigDataCollector.php @@ -15,6 +15,7 @@ use Symfony\Component\HttpKernel\KernelInterface; use Symfony\Component\HttpKernel\Kernel; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\VarDumper\Caster\LinkStub; /** * ConfigDataCollector. @@ -29,6 +30,7 @@ class ConfigDataCollector extends DataCollector private $kernel; private $name; private $version; + private $hasVarDumper; /** * Constructor. @@ -40,6 +42,7 @@ class ConfigDataCollector extends DataCollector { $this->name = $name; $this->version = $version; + $this->hasVarDumper = class_exists(LinkStub::class); } /** @@ -79,7 +82,7 @@ class ConfigDataCollector extends DataCollector if (isset($this->kernel)) { foreach ($this->kernel->getBundles() as $name => $bundle) { - $this->data['bundles'][$name] = $bundle->getPath(); + $this->data['bundles'][$name] = $this->hasVarDumper ? new LinkStub($bundle->getPath()) : $bundle->getPath(); } $this->data['symfony_state'] = $this->determineSymfonyState(); @@ -94,6 +97,7 @@ class ConfigDataCollector extends DataCollector $this->data['php_version'] = $matches[1]; $this->data['php_version_extra'] = $matches[2]; } + $this->data = $this->cloneVar($this->data); } public function getApplicationName() diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php index 98711e00d2..2980808f95 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/DataCollector.php @@ -13,11 +13,8 @@ namespace Symfony\Component\HttpKernel\DataCollector; use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; use Symfony\Component\VarDumper\Caster\ClassStub; -use Symfony\Component\VarDumper\Caster\LinkStub; -use Symfony\Component\VarDumper\Caster\StubCaster; use Symfony\Component\VarDumper\Cloner\ClonerInterface; use Symfony\Component\VarDumper\Cloner\Data; -use Symfony\Component\VarDumper\Cloner\Stub; use Symfony\Component\VarDumper\Cloner\VarCloner; /** @@ -40,7 +37,7 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable /** * @var ClonerInterface */ - private $cloner; + private static $cloner; private static $stubsCache = array(); @@ -66,21 +63,16 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable */ protected function cloneVar($var) { - if (null === $this->cloner) { + if (null === self::$cloner) { if (class_exists(ClassStub::class)) { - $this->cloner = new VarCloner(); - $this->cloner->setMaxItems(250); - $this->cloner->addCasters(array( - Stub::class => function (Stub $v, array $a, Stub $s, $isNested) { - return $isNested ? $a : StubCaster::castStub($v, $a, $s, true); - }, - )); + self::$cloner = new VarCloner(); + self::$cloner->setMaxItems(-1); } else { @trigger_error(sprintf('Using the %s() method without the VarDumper component is deprecated since version 3.2 and won\'t be supported in 4.0. Install symfony/var-dumper version 3.2 or above.', __METHOD__), E_USER_DEPRECATED); - $this->cloner = false; + self::$cloner = false; } } - if (false === $this->cloner) { + if (false === self::$cloner) { if (null === $this->valueExporter) { $this->valueExporter = new ValueExporter(); } @@ -88,7 +80,7 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable return $this->valueExporter->exportValue($var); } - return $this->cloner->cloneVar($this->decorateVar($var)); + return self::$cloner->cloneVar($var); } /** @@ -110,36 +102,4 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable return $this->valueExporter->exportValue($var); } - - private function decorateVar($var) - { - if (is_array($var)) { - if (isset($var[0], $var[1]) && is_callable($var)) { - return ClassStub::wrapCallable($var); - } - foreach ($var as $k => $v) { - if ($v !== $d = $this->decorateVar($v)) { - $var[$k] = $d; - } - } - - return $var; - } - if (is_string($var)) { - if (isset(self::$stubsCache[$var])) { - return self::$stubsCache[$var]; - } - if (false !== strpos($var, '\\')) { - $c = (false !== $i = strpos($var, '::')) ? substr($var, 0, $i) : $var; - if (class_exists($c, false) || interface_exists($c, false) || trait_exists($c, false)) { - return self::$stubsCache[$var] = new ClassStub($var); - } - } - if (false !== strpos($var, DIRECTORY_SEPARATOR) && false === strpos($var, '://') && false === strpos($var, "\0") && @is_file($var)) { - return self::$stubsCache[$var] = new LinkStub($var); - } - } - - return $var; - } } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php index 0a87bc3892..3d75f322d8 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/EventDataCollector.php @@ -47,6 +47,7 @@ class EventDataCollector extends DataCollector implements LateDataCollectorInter $this->setCalledListeners($this->dispatcher->getCalledListeners()); $this->setNotCalledListeners($this->dispatcher->getNotCalledListeners()); } + $this->data = $this->cloneVar($this->data); } /** diff --git a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php index 358a411ab0..217290aa10 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/LoggerDataCollector.php @@ -48,6 +48,7 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte if (null !== $this->logger) { $this->data = $this->computeErrorsCount(); $this->data['logs'] = $this->sanitizeLogs($this->logger->getLogs()); + $this->data = $this->cloneVar($this->data); } } @@ -100,7 +101,6 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte foreach ($logs as $log) { if (!$this->isSilencedOrDeprecationErrorLog($log)) { - $log['context'] = $log['context'] ? $this->cloneVar($log['context']) : $log['context']; $sanitizedLogs[] = $log; continue; @@ -112,8 +112,6 @@ class LoggerDataCollector extends DataCollector implements LateDataCollectorInte if (isset($sanitizedLogs[$errorId])) { ++$sanitizedLogs[$errorId]['errorCount']; } else { - $log['context'] = $log['context'] ? $this->cloneVar($log['context']) : $log['context']; - $log += array( 'errorCount' => 1, 'scream' => $exception instanceof SilencedErrorContext, diff --git a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php index bfdabaf558..9574d5c757 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/RequestDataCollector.php @@ -116,10 +116,7 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter continue; } if ('request_headers' === $key || 'response_headers' === $key) { - $value = array_map(function ($v) { return isset($v[0]) && !isset($v[1]) ? $v[0] : $v; }, $value); - } - if ('request_server' !== $key && 'request_cookies' !== $key) { - $this->data[$key] = array_map(array($this, 'cloneVar'), $value); + $this->data[$key] = array_map(function ($v) { return isset($v[0]) && !isset($v[1]) ? $v[0] : $v; }, $value); } } @@ -144,6 +141,8 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter )); } } + + $this->data = $this->cloneVar($this->data); } public function getMethod() @@ -158,52 +157,52 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter public function getRequestRequest() { - return new ParameterBag($this->data['request_request']); + return new ParameterBag($this->data['request_request']->getValue()); } public function getRequestQuery() { - return new ParameterBag($this->data['request_query']); + return new ParameterBag($this->data['request_query']->getValue()); } public function getRequestHeaders() { - return new ParameterBag($this->data['request_headers']); + return new ParameterBag($this->data['request_headers']->getValue()); } public function getRequestServer($raw = false) { - return new ParameterBag($raw ? $this->data['request_server'] : array_map(array($this, 'cloneVar'), $this->data['request_server'])); + return new ParameterBag($this->data['request_server']->getValue($raw)); } public function getRequestCookies($raw = false) { - return new ParameterBag($raw ? $this->data['request_cookies'] : array_map(array($this, 'cloneVar'), $this->data['request_cookies'])); + return new ParameterBag($this->data['request_cookies']->getValue($raw)); } public function getRequestAttributes() { - return new ParameterBag($this->data['request_attributes']); + return new ParameterBag($this->data['request_attributes']->getValue()); } public function getResponseHeaders() { - return new ParameterBag($this->data['response_headers']); + return new ParameterBag($this->data['response_headers']->getValue()); } public function getSessionMetadata() { - return $this->data['session_metadata']; + return $this->data['session_metadata']->getValue(); } public function getSessionAttributes() { - return $this->data['session_attributes']; + return $this->data['session_attributes']->getValue(); } public function getFlashes() { - return $this->data['flashes']; + return $this->data['flashes']->getValue(); } public function getContent() @@ -262,22 +261,7 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter */ public function getRouteParams() { - if (!isset($this->data['request_attributes']['_route_params'])) { - return array(); - } - - $data = $this->data['request_attributes']['_route_params']; - $rawData = $data->getRawData(); - if (!isset($rawData[1])) { - return array(); - } - - $params = array(); - foreach ($rawData[1] as $k => $v) { - $params[$k] = $data->seek($k); - } - - return $params; + return isset($this->data['request_attributes']['_route_params']) ? $this->data['request_attributes']['_route_params']->getValue() : array(); } /** diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/DataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/DataCollectorTest.php index 0af51db6ad..54fd39e0d8 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/DataCollectorTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/DataCollectorTest.php @@ -15,9 +15,7 @@ use PHPUnit\Framework\TestCase; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpKernel\Tests\Fixtures\DataCollector\CloneVarDataCollector; -use Symfony\Component\VarDumper\Cloner\Stub; use Symfony\Component\VarDumper\Cloner\VarCloner; -use Symfony\Component\VarDumper\Dumper\CliDumper; class DataCollectorTest extends TestCase { @@ -32,19 +30,9 @@ class DataCollectorTest extends TestCase public function testCloneVarExistingFilePath() { - $c = new CloneVarDataCollector($filePath = tempnam(sys_get_temp_dir(), 'clone_var_data_collector_')); + $c = new CloneVarDataCollector(array($filePath = tempnam(sys_get_temp_dir(), 'clone_var_data_collector_'))); $c->collect(new Request(), new Response()); - $data = $c->getData(); - $this->assertInstanceOf(Stub::class, $data->getRawData()[0][0]); - $this->assertDumpEquals("\"$filePath\"", $data); - } - - private function assertDumpEquals($dump, $data, $message = '') - { - $dumper = new CliDumper(); - $dumper->setColors(false); - - $this->assertSame(rtrim($dump), rtrim($dumper->dump($data, true)), $message); + $this->assertSame($filePath, $c->getData()[0]); } } diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php index 7a5833d4e9..5a01ee8816 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/LoggerDataCollectorTest.php @@ -14,12 +14,9 @@ namespace Symfony\Component\HttpKernel\Tests\DataCollector; use PHPUnit\Framework\TestCase; use Symfony\Component\Debug\Exception\SilencedErrorContext; use Symfony\Component\HttpKernel\DataCollector\LoggerDataCollector; -use Symfony\Component\VarDumper\Cloner\Data; class LoggerDataCollectorTest extends TestCase { - private static $data; - /** * @dataProvider getCollectTestData */ @@ -29,31 +26,31 @@ class LoggerDataCollectorTest extends TestCase $logger->expects($this->once())->method('countErrors')->will($this->returnValue($nb)); $logger->expects($this->exactly(2))->method('getLogs')->will($this->returnValue($logs)); - // disable cloning the context, to ease fixtures creation. - $c = $this->getMockBuilder(LoggerDataCollector::class) - ->setMethods(array('cloneVar')) - ->setConstructorArgs(array($logger)) - ->getMock(); - $c->expects($this->any())->method('cloneVar')->willReturn(self::$data); + $c = new LoggerDataCollector($logger); $c->lateCollect(); $this->assertEquals('logger', $c->getName()); $this->assertEquals($nb, $c->countErrors()); - $this->assertEquals($expectedLogs, $c->getLogs()); + + $logs = array_map(function ($v) { + if (isset($v['context']['exception'])) { + $e = &$v['context']['exception']; + $e = isset($e["\0*\0message"]) ? array($e["\0*\0message"], $e["\0*\0severity"]) : array($e["\0Symfony\Component\Debug\Exception\SilencedErrorContext\0severity"]); + } + + return $v; + }, $c->getLogs()->getValue(true)); + $this->assertEquals($expectedLogs, $logs); $this->assertEquals($expectedDeprecationCount, $c->countDeprecations()); $this->assertEquals($expectedScreamCount, $c->countScreams()); if (isset($expectedPriorities)) { - $this->assertSame($expectedPriorities, $c->getPriorities()); + $this->assertSame($expectedPriorities, $c->getPriorities()->getValue(true)); } } public function getCollectTestData() { - if (null === self::$data) { - self::$data = new Data(array()); - } - yield 'simple log' => array( 1, array(array('message' => 'foo', 'context' => array(), 'priority' => 100, 'priorityName' => 'DEBUG')), @@ -65,7 +62,7 @@ class LoggerDataCollectorTest extends TestCase yield 'log with a context' => array( 1, array(array('message' => 'foo', 'context' => array('foo' => 'bar'), 'priority' => 100, 'priorityName' => 'DEBUG')), - array(array('message' => 'foo', 'context' => self::$data, 'priority' => 100, 'priorityName' => 'DEBUG')), + array(array('message' => 'foo', 'context' => array('foo' => 'bar'), 'priority' => 100, 'priorityName' => 'DEBUG')), 0, 0, ); @@ -82,9 +79,9 @@ class LoggerDataCollectorTest extends TestCase array('message' => 'foo2', 'context' => array('exception' => new \ErrorException('deprecated', 0, E_USER_DEPRECATED)), 'priority' => 100, 'priorityName' => 'DEBUG'), ), array( - array('message' => 'foo3', 'context' => self::$data, 'priority' => 100, 'priorityName' => 'DEBUG'), - array('message' => 'foo', 'context' => self::$data, 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => false), - array('message' => 'foo2', 'context' => self::$data, 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => false), + array('message' => 'foo3', 'context' => array('exception' => array('warning', E_USER_WARNING)), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo', 'context' => array('exception' => array('deprecated', E_DEPRECATED)), 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => false), + array('message' => 'foo2', 'context' => array('exception' => array('deprecated', E_USER_DEPRECATED)), 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => false), ), 2, 0, @@ -98,8 +95,8 @@ class LoggerDataCollectorTest extends TestCase array('message' => 'foo3', 'context' => array('exception' => new SilencedErrorContext(E_USER_WARNING, __FILE__, __LINE__)), 'priority' => 100, 'priorityName' => 'DEBUG'), ), array( - array('message' => 'foo3', 'context' => self::$data, 'priority' => 100, 'priorityName' => 'DEBUG'), - array('message' => 'foo3', 'context' => self::$data, 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => true), + array('message' => 'foo3', 'context' => array('exception' => array('warning', E_USER_WARNING)), 'priority' => 100, 'priorityName' => 'DEBUG'), + array('message' => 'foo3', 'context' => array('exception' => array(E_USER_WARNING)), 'priority' => 100, 'priorityName' => 'DEBUG', 'errorCount' => 1, 'scream' => true), ), 0, 1, diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php index cc8b4c6500..3a95c6b9b6 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/RequestDataCollectorTest.php @@ -48,11 +48,11 @@ class RequestDataCollectorTest extends TestCase $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestQuery()); $this->assertSame('html', $c->getFormat()); $this->assertEquals('foobar', $c->getRoute()); - $this->assertEquals(array('name' => $cloner->cloneVar(array('name' => 'foo'))->seek('name')), $c->getRouteParams()); + $this->assertEquals(array('name' => 'foo'), $c->getRouteParams()); $this->assertSame(array(), $c->getSessionAttributes()); $this->assertSame('en', $c->getLocale()); - $this->assertEquals($cloner->cloneVar($request->attributes->get('resource')), $attributes->get('resource')); - $this->assertEquals($cloner->cloneVar($request->attributes->get('object')), $attributes->get('object')); + $this->assertContains(__FILE__, $attributes->get('resource')); + $this->assertSame('stdClass', $attributes->get('object')->getType()); $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getResponseHeaders()); $this->assertSame('OK', $c->getStatusText()); @@ -95,7 +95,7 @@ class RequestDataCollectorTest extends TestCase $this->injectController($c, $callable, $request); $c->collect($request, $response); - $this->assertSame($expected, $c->getController(), sprintf('Testing: %s', $name)); + $this->assertSame($expected, $c->getController()->getValue(true), sprintf('Testing: %s', $name)); } public function provideControllerCallables() diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/DumpListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/DumpListenerTest.php index 332ec55bce..509f443087 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/DumpListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/DumpListenerTest.php @@ -68,7 +68,7 @@ class MockCloner implements ClonerInterface { public function cloneVar($var) { - return new Data(array($var.'-')); + return new Data(array(array($var.'-'))); } } @@ -76,8 +76,6 @@ class MockDumper implements DataDumperInterface { public function dump(Data $data) { - $rawData = $data->getRawData(); - - echo '+'.$rawData[0]; + echo '+'.$data->getValue(); } } diff --git a/src/Symfony/Component/HttpKernel/Tests/Profiler/ProfilerTest.php b/src/Symfony/Component/HttpKernel/Tests/Profiler/ProfilerTest.php index b24873d4b2..cd5daf1128 100644 --- a/src/Symfony/Component/HttpKernel/Tests/Profiler/ProfilerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/Profiler/ProfilerTest.php @@ -17,7 +17,6 @@ use Symfony\Component\HttpKernel\Profiler\FileProfilerStorage; use Symfony\Component\HttpKernel\Profiler\Profiler; use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Response; -use Symfony\Component\VarDumper\Cloner\Data; class ProfilerTest extends TestCase { @@ -37,7 +36,7 @@ class ProfilerTest extends TestCase $this->assertSame(204, $profile->getStatusCode()); $this->assertSame('GET', $profile->getMethod()); - $this->assertInstanceOf(Data::class, $profiler->get('request')->getRequestQuery()->all()['foo']); + $this->assertSame('bar', $profiler->get('request')->getRequestQuery()->all()['foo']->getValue()); } public function testFindWorksWithDates() diff --git a/src/Symfony/Component/HttpKernel/composer.json b/src/Symfony/Component/HttpKernel/composer.json index 4df383ea2c..a680eb05e8 100644 --- a/src/Symfony/Component/HttpKernel/composer.json +++ b/src/Symfony/Component/HttpKernel/composer.json @@ -37,12 +37,13 @@ "symfony/stopwatch": "~2.8|~3.0", "symfony/templating": "~2.8|~3.0", "symfony/translation": "~2.8|~3.0", - "symfony/var-dumper": "~3.2", + "symfony/var-dumper": "~3.3", "psr/cache": "~1.0" }, "conflict": { "symfony/config": "<2.8", - "symfony/dependency-injection": "<3.3" + "symfony/dependency-injection": "<3.3", + "symfony/var-dumper": "<3.3" }, "suggest": { "symfony/browser-kit": "", diff --git a/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php b/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php index 6b5e165aad..b99a49f7ff 100644 --- a/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php +++ b/src/Symfony/Component/Translation/DataCollector/TranslationDataCollector.php @@ -44,6 +44,8 @@ class TranslationDataCollector extends DataCollector implements LateDataCollecto $this->data = $this->computeCount($messages); $this->data['messages'] = $messages; + + $this->data = $this->cloneVar($this->data); } /** @@ -101,12 +103,12 @@ class TranslationDataCollector extends DataCollector implements LateDataCollecto if (!isset($result[$messageId])) { $message['count'] = 1; - $message['parameters'] = !empty($message['parameters']) ? array($this->cloneVar($message['parameters'])) : array(); + $message['parameters'] = !empty($message['parameters']) ? array($message['parameters']) : array(); $messages[$key]['translation'] = $this->sanitizeString($message['translation']); $result[$messageId] = $message; } else { if (!empty($message['parameters'])) { - $result[$messageId]['parameters'][] = $this->cloneVar($message['parameters']); + $result[$messageId]['parameters'][] = $message['parameters']; } ++$result[$messageId]['count']; diff --git a/src/Symfony/Component/Translation/Tests/DataCollector/TranslationDataCollectorTest.php b/src/Symfony/Component/Translation/Tests/DataCollector/TranslationDataCollectorTest.php index 8dea492153..5611c877ca 100644 --- a/src/Symfony/Component/Translation/Tests/DataCollector/TranslationDataCollectorTest.php +++ b/src/Symfony/Component/Translation/Tests/DataCollector/TranslationDataCollectorTest.php @@ -14,7 +14,6 @@ namespace Symfony\Component\Translation\Tests\DataCollector; use PHPUnit\Framework\TestCase; use Symfony\Component\Translation\DataCollectorTranslator; use Symfony\Component\Translation\DataCollector\TranslationDataCollector; -use Symfony\Component\VarDumper\Cloner\VarCloner; class TranslationDataCollectorTest extends TestCase { @@ -36,13 +35,11 @@ class TranslationDataCollectorTest extends TestCase $this->assertEquals(0, $dataCollector->getCountMissings()); $this->assertEquals(0, $dataCollector->getCountFallbacks()); $this->assertEquals(0, $dataCollector->getCountDefines()); - $this->assertEquals(array(), $dataCollector->getMessages()); + $this->assertEquals(array(), $dataCollector->getMessages()->getValue()); } public function testCollect() { - $cloner = new VarCloner(); - $collectedMessages = array( array( 'id' => 'foo', @@ -119,9 +116,9 @@ class TranslationDataCollectorTest extends TestCase 'state' => DataCollectorTranslator::MESSAGE_MISSING, 'count' => 3, 'parameters' => array( - $cloner->cloneVar(array('%count%' => 3)), - $cloner->cloneVar(array('%count%' => 3)), - $cloner->cloneVar(array('%count%' => 4, '%foo%' => 'bar')), + array('%count%' => 3), + array('%count%' => 3), + array('%count%' => 4, '%foo%' => 'bar'), ), 'transChoiceNumber' => 3, ), @@ -136,7 +133,8 @@ class TranslationDataCollectorTest extends TestCase $this->assertEquals(1, $dataCollector->getCountMissings()); $this->assertEquals(1, $dataCollector->getCountFallbacks()); $this->assertEquals(1, $dataCollector->getCountDefines()); - $this->assertEquals($expectedMessages, array_values($dataCollector->getMessages())); + + $this->assertEquals($expectedMessages, array_values($dataCollector->getMessages()->getValue(true))); } private function getTranslator() diff --git a/src/Symfony/Component/VarDumper/Caster/Caster.php b/src/Symfony/Component/VarDumper/Caster/Caster.php index 5428fecc2b..fd8a115605 100644 --- a/src/Symfony/Component/VarDumper/Caster/Caster.php +++ b/src/Symfony/Component/VarDumper/Caster/Caster.php @@ -17,6 +17,8 @@ use Symfony\Component\VarDumper\Cloner\Stub; * Helper for filtering out properties in casters. * * @author Nicolas Grekas + * + * @final */ class Caster { @@ -38,14 +40,20 @@ class Caster /** * Casts objects to arrays and adds the dynamic property prefix. * - * @param object $obj The object to cast - * @param \ReflectionClass $reflector The class reflector to use for inspecting the object definition + * @param object $obj The object to cast + * @param string $class The class of the object + * @param bool $hasDebugInfo Whether the __debugInfo method exists on $obj or not * * @return array The array-cast of the object, with prefixed dynamic properties */ - public static function castObject($obj, \ReflectionClass $reflector) + public static function castObject($obj, $class, $hasDebugInfo = false) { - if ($reflector->hasMethod('__debugInfo')) { + if ($class instanceof \ReflectionClass) { + @trigger_error(sprintf('Passing a ReflectionClass to %s() is deprecated since version 3.3 and will be unsupported in 4.0. Pass the class name as string instead.', __METHOD__), E_USER_DEPRECATED); + $hasDebugInfo = $class->hasMethod('__debugInfo'); + $class = $class->name; + } + if ($hasDebugInfo) { $a = $obj->__debugInfo(); } elseif ($obj instanceof \Closure) { $a = array(); @@ -57,19 +65,22 @@ class Caster } if ($a) { - $combine = false; - $p = array_keys($a); - foreach ($p as $i => $k) { - if (isset($k[0]) && "\0" !== $k[0] && !$reflector->hasProperty($k)) { - $combine = true; - $p[$i] = self::PREFIX_DYNAMIC.$k; + $i = 0; + $prefixedKeys = array(); + foreach ($a as $k => $v) { + if (isset($k[0]) && "\0" !== $k[0] && !property_exists($class, $k)) { + $prefixedKeys[$i] = self::PREFIX_DYNAMIC.$k; } elseif (isset($k[16]) && "\0" === $k[16] && 0 === strpos($k, "\0class@anonymous\0")) { - $combine = true; - $p[$i] = "\0".$reflector->getParentClass().'@anonymous'.strrchr($k, "\0"); + $prefixedKeys[$i] = "\0".get_parent_class($class).'@anonymous'.strrchr($k, "\0"); } + ++$i; } - if ($combine) { - $a = array_combine($p, $a); + if ($prefixedKeys) { + $keys = array_keys($a); + foreach ($prefixedKeys as $i => $k) { + $keys[$i] = $k; + } + $a = array_combine($keys, $a); } } diff --git a/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php index aa32d8b90b..8069d88454 100644 --- a/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php @@ -70,7 +70,7 @@ class ExceptionCaster if (isset($a[$xPrefix.'previous'], $a[$xPrefix.'trace'])) { $b = (array) $a[$xPrefix.'previous']; self::traceUnshift($b[$xPrefix.'trace'], get_class($a[$xPrefix.'previous']), $b[$prefix.'file'], $b[$prefix.'line']); - $a[$xPrefix.'trace'] = new TraceStub($b[$xPrefix.'trace'], false, 0, -1 - count($a[$xPrefix.'trace']->value)); + $a[$xPrefix.'trace'] = new TraceStub($b[$xPrefix.'trace'], false, 0, -count($a[$xPrefix.'trace']->value)); } unset($a[$xPrefix.'previous'], $a[$prefix.'code'], $a[$prefix.'file'], $a[$prefix.'line']); diff --git a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php index dcb5a2f29c..9811eab5e7 100644 --- a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php @@ -31,13 +31,13 @@ class ReflectionCaster 'isVariadic' => 'isVariadic', ); - public static function castClosure(\Closure $c, array $a, Stub $stub, $isNested) + public static function castClosure(\Closure $c, array $a, Stub $stub, $isNested, $filter = 0) { $prefix = Caster::PREFIX_VIRTUAL; $c = new \ReflectionFunction($c); $stub->class = 'Closure'; // HHVM generates unique class names for closures - $a = static::castFunctionAbstract($c, $a, $stub, $isNested); + $a = static::castFunctionAbstract($c, $a, $stub, $isNested, $filter); if (isset($a[$prefix.'parameters'])) { foreach ($a[$prefix.'parameters']->value as &$v) { @@ -51,8 +51,9 @@ class ReflectionCaster unset($v->value['position'], $v->value['isVariadic'], $v->value['byReference'], $v); } } + ; - if ($f = $c->getFileName()) { + if (!($filter & Caster::EXCLUDE_VERBOSE) && $f = $c->getFileName()) { $a[$prefix.'file'] = new LinkStub($f, $c->getStartLine()); $a[$prefix.'line'] = $c->getStartLine().' to '.$c->getEndLine(); } @@ -199,7 +200,11 @@ class ReflectionCaster if ($v = $c->getStaticVariables()) { foreach ($v as $k => &$v) { - $a[$prefix.'use']['$'.$k] = &$v; + if (is_object($v)) { + $a[$prefix.'use']['$'.$k] = new CutStub($v); + } else { + $a[$prefix.'use']['$'.$k] = &$v; + } } unset($v); $a[$prefix.'use'] = new EnumStub($a[$prefix.'use']); diff --git a/src/Symfony/Component/VarDumper/Caster/SplCaster.php b/src/Symfony/Component/VarDumper/Caster/SplCaster.php index 2c67695758..cbeb679543 100644 --- a/src/Symfony/Component/VarDumper/Caster/SplCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/SplCaster.php @@ -45,7 +45,7 @@ class SplCaster } else { if (!($flags & \ArrayObject::STD_PROP_LIST)) { $c->setFlags(\ArrayObject::STD_PROP_LIST); - $a = Caster::castObject($c, new \ReflectionClass($class)); + $a = Caster::castObject($c, $class); $c->setFlags($flags); } diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index b185c96fab..57285717f3 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -22,107 +22,107 @@ use Symfony\Component\VarDumper\Exception\ThrowingCasterException; abstract class AbstractCloner implements ClonerInterface { public static $defaultCasters = array( - '__PHP_Incomplete_Class' => 'Symfony\Component\VarDumper\Caster\Caster::castPhpIncompleteClass', + '__PHP_Incomplete_Class' => array('Symfony\Component\VarDumper\Caster\Caster', 'castPhpIncompleteClass'), - 'Symfony\Component\VarDumper\Caster\CutStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castStub', - 'Symfony\Component\VarDumper\Caster\CutArrayStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castCutArray', - 'Symfony\Component\VarDumper\Caster\ConstStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castStub', - 'Symfony\Component\VarDumper\Caster\EnumStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castEnum', + 'Symfony\Component\VarDumper\Caster\CutStub' => array('Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'), + 'Symfony\Component\VarDumper\Caster\CutArrayStub' => array('Symfony\Component\VarDumper\Caster\StubCaster', 'castCutArray'), + 'Symfony\Component\VarDumper\Caster\ConstStub' => array('Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'), + 'Symfony\Component\VarDumper\Caster\EnumStub' => array('Symfony\Component\VarDumper\Caster\StubCaster', 'castEnum'), - 'Closure' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castClosure', - 'Generator' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castGenerator', - 'ReflectionType' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castType', - 'ReflectionGenerator' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castReflectionGenerator', - 'ReflectionClass' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castClass', - 'ReflectionFunctionAbstract' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castFunctionAbstract', - 'ReflectionMethod' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castMethod', - 'ReflectionParameter' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castParameter', - 'ReflectionProperty' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castProperty', - 'ReflectionExtension' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castExtension', - 'ReflectionZendExtension' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castZendExtension', + 'Closure' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClosure'), + 'Generator' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castGenerator'), + 'ReflectionType' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castType'), + 'ReflectionGenerator' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReflectionGenerator'), + 'ReflectionClass' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClass'), + 'ReflectionFunctionAbstract' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castFunctionAbstract'), + 'ReflectionMethod' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castMethod'), + 'ReflectionParameter' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castParameter'), + 'ReflectionProperty' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castProperty'), + 'ReflectionExtension' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castExtension'), + 'ReflectionZendExtension' => array('Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castZendExtension'), - 'Doctrine\Common\Persistence\ObjectManager' => 'Symfony\Component\VarDumper\Caster\StubCaster::cutInternals', - 'Doctrine\Common\Proxy\Proxy' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castCommonProxy', - 'Doctrine\ORM\Proxy\Proxy' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castOrmProxy', - 'Doctrine\ORM\PersistentCollection' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castPersistentCollection', + 'Doctrine\Common\Persistence\ObjectManager' => array('Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'), + 'Doctrine\Common\Proxy\Proxy' => array('Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castCommonProxy'), + 'Doctrine\ORM\Proxy\Proxy' => array('Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castOrmProxy'), + 'Doctrine\ORM\PersistentCollection' => array('Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castPersistentCollection'), - 'DOMException' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castException', - 'DOMStringList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength', - 'DOMNameList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength', - 'DOMImplementation' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castImplementation', - 'DOMImplementationList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength', - 'DOMNode' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNode', - 'DOMNameSpaceNode' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNameSpaceNode', - 'DOMDocument' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDocument', - 'DOMNodeList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength', - 'DOMNamedNodeMap' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength', - 'DOMCharacterData' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castCharacterData', - 'DOMAttr' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castAttr', - 'DOMElement' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castElement', - 'DOMText' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castText', - 'DOMTypeinfo' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castTypeinfo', - 'DOMDomError' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDomError', - 'DOMLocator' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLocator', - 'DOMDocumentType' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDocumentType', - 'DOMNotation' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNotation', - 'DOMEntity' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castEntity', - 'DOMProcessingInstruction' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castProcessingInstruction', - 'DOMXPath' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castXPath', + 'DOMException' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castException'), + 'DOMStringList' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'), + 'DOMNameList' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'), + 'DOMImplementation' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castImplementation'), + 'DOMImplementationList' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'), + 'DOMNode' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castNode'), + 'DOMNameSpaceNode' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castNameSpaceNode'), + 'DOMDocument' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocument'), + 'DOMNodeList' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'), + 'DOMNamedNodeMap' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'), + 'DOMCharacterData' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castCharacterData'), + 'DOMAttr' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castAttr'), + 'DOMElement' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castElement'), + 'DOMText' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castText'), + 'DOMTypeinfo' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castTypeinfo'), + 'DOMDomError' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castDomError'), + 'DOMLocator' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castLocator'), + 'DOMDocumentType' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocumentType'), + 'DOMNotation' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castNotation'), + 'DOMEntity' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castEntity'), + 'DOMProcessingInstruction' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castProcessingInstruction'), + 'DOMXPath' => array('Symfony\Component\VarDumper\Caster\DOMCaster', 'castXPath'), - 'XmlReader' => 'Symfony\Component\VarDumper\Caster\XmlReaderCaster::castXmlReader', + 'XmlReader' => array('Symfony\Component\VarDumper\Caster\XmlReaderCaster', 'castXmlReader'), - 'ErrorException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castErrorException', - 'Exception' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castException', - 'Error' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castError', - 'Symfony\Component\DependencyInjection\ContainerInterface' => 'Symfony\Component\VarDumper\Caster\StubCaster::cutInternals', - 'Symfony\Component\DependencyInjection\LazyProxy\InheritanceProxyInterface' => 'Symfony\Component\VarDumper\Caster\SymfonyCaster::castInheritanceProxy', - 'Symfony\Component\HttpFoundation\Request' => 'Symfony\Component\VarDumper\Caster\SymfonyCaster::castRequest', - 'Symfony\Component\VarDumper\Exception\ThrowingCasterException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castThrowingCasterException', - 'Symfony\Component\VarDumper\Caster\TraceStub' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castTraceStub', - 'Symfony\Component\VarDumper\Caster\FrameStub' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castFrameStub', + 'ErrorException' => array('Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castErrorException'), + 'Exception' => array('Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castException'), + 'Error' => array('Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castError'), + 'Symfony\Component\DependencyInjection\ContainerInterface' => array('Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'), + 'Symfony\Component\DependencyInjection\LazyProxy\InheritanceProxyInterface' => array('Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castInheritanceProxy'), + 'Symfony\Component\HttpFoundation\Request' => array('Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castRequest'), + 'Symfony\Component\VarDumper\Exception\ThrowingCasterException' => array('Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castThrowingCasterException'), + 'Symfony\Component\VarDumper\Caster\TraceStub' => array('Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castTraceStub'), + 'Symfony\Component\VarDumper\Caster\FrameStub' => array('Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castFrameStub'), - 'PHPUnit_Framework_MockObject_MockObject' => 'Symfony\Component\VarDumper\Caster\StubCaster::cutInternals', - 'Prophecy\Prophecy\ProphecySubjectInterface' => 'Symfony\Component\VarDumper\Caster\StubCaster::cutInternals', - 'Mockery\MockInterface' => 'Symfony\Component\VarDumper\Caster\StubCaster::cutInternals', + 'PHPUnit_Framework_MockObject_MockObject' => array('Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'), + 'Prophecy\Prophecy\ProphecySubjectInterface' => array('Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'), + 'Mockery\MockInterface' => array('Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'), - 'PDO' => 'Symfony\Component\VarDumper\Caster\PdoCaster::castPdo', - 'PDOStatement' => 'Symfony\Component\VarDumper\Caster\PdoCaster::castPdoStatement', + 'PDO' => array('Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdo'), + 'PDOStatement' => array('Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdoStatement'), - 'AMQPConnection' => 'Symfony\Component\VarDumper\Caster\AmqpCaster::castConnection', - 'AMQPChannel' => 'Symfony\Component\VarDumper\Caster\AmqpCaster::castChannel', - 'AMQPQueue' => 'Symfony\Component\VarDumper\Caster\AmqpCaster::castQueue', - 'AMQPExchange' => 'Symfony\Component\VarDumper\Caster\AmqpCaster::castExchange', - 'AMQPEnvelope' => 'Symfony\Component\VarDumper\Caster\AmqpCaster::castEnvelope', + 'AMQPConnection' => array('Symfony\Component\VarDumper\Caster\AmqpCaster', 'castConnection'), + 'AMQPChannel' => array('Symfony\Component\VarDumper\Caster\AmqpCaster', 'castChannel'), + 'AMQPQueue' => array('Symfony\Component\VarDumper\Caster\AmqpCaster', 'castQueue'), + 'AMQPExchange' => array('Symfony\Component\VarDumper\Caster\AmqpCaster', 'castExchange'), + 'AMQPEnvelope' => array('Symfony\Component\VarDumper\Caster\AmqpCaster', 'castEnvelope'), - 'ArrayObject' => 'Symfony\Component\VarDumper\Caster\SplCaster::castArrayObject', - 'SplDoublyLinkedList' => 'Symfony\Component\VarDumper\Caster\SplCaster::castDoublyLinkedList', - 'SplFileInfo' => 'Symfony\Component\VarDumper\Caster\SplCaster::castFileInfo', - 'SplFileObject' => 'Symfony\Component\VarDumper\Caster\SplCaster::castFileObject', - 'SplFixedArray' => 'Symfony\Component\VarDumper\Caster\SplCaster::castFixedArray', - 'SplHeap' => 'Symfony\Component\VarDumper\Caster\SplCaster::castHeap', - 'SplObjectStorage' => 'Symfony\Component\VarDumper\Caster\SplCaster::castObjectStorage', - 'SplPriorityQueue' => 'Symfony\Component\VarDumper\Caster\SplCaster::castHeap', - 'OuterIterator' => 'Symfony\Component\VarDumper\Caster\SplCaster::castOuterIterator', + 'ArrayObject' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayObject'), + 'SplDoublyLinkedList' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castDoublyLinkedList'), + 'SplFileInfo' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castFileInfo'), + 'SplFileObject' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castFileObject'), + 'SplFixedArray' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castFixedArray'), + 'SplHeap' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'), + 'SplObjectStorage' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castObjectStorage'), + 'SplPriorityQueue' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'), + 'OuterIterator' => array('Symfony\Component\VarDumper\Caster\SplCaster', 'castOuterIterator'), - 'MongoCursorInterface' => 'Symfony\Component\VarDumper\Caster\MongoCaster::castCursor', + 'MongoCursorInterface' => array('Symfony\Component\VarDumper\Caster\MongoCaster', 'castCursor'), - 'Redis' => 'Symfony\Component\VarDumper\Caster\RedisCaster::castRedis', - 'RedisArray' => 'Symfony\Component\VarDumper\Caster\RedisCaster::castRedisArray', + 'Redis' => array('Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'), + 'RedisArray' => array('Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisArray'), - ':curl' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castCurl', - ':dba' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba', - ':dba persistent' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba', - ':gd' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castGd', - ':mysql link' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castMysqlLink', - ':pgsql large object' => 'Symfony\Component\VarDumper\Caster\PgSqlCaster::castLargeObject', - ':pgsql link' => 'Symfony\Component\VarDumper\Caster\PgSqlCaster::castLink', - ':pgsql link persistent' => 'Symfony\Component\VarDumper\Caster\PgSqlCaster::castLink', - ':pgsql result' => 'Symfony\Component\VarDumper\Caster\PgSqlCaster::castResult', - ':process' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castProcess', - ':stream' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStream', - ':persistent stream' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStream', - ':stream-context' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStreamContext', - ':xml' => 'Symfony\Component\VarDumper\Caster\XmlResourceCaster::castXml', + ':curl' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'), + ':dba' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'), + ':dba persistent' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'), + ':gd' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'), + ':mysql link' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castMysqlLink'), + ':pgsql large object' => array('Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLargeObject'), + ':pgsql link' => array('Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'), + ':pgsql link persistent' => array('Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'), + ':pgsql result' => array('Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castResult'), + ':process' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castProcess'), + ':stream' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'), + ':persistent stream' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'), + ':stream-context' => array('Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStreamContext'), + ':xml' => array('Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'), ); protected $maxItems = 2500; @@ -161,7 +161,7 @@ abstract class AbstractCloner implements ClonerInterface public function addCasters(array $casters) { foreach ($casters as $type => $callback) { - $this->casters[strtolower($type)][] = $callback; + $this->casters[strtolower($type)][] = is_string($callback) && false !== strpos($callback, '::') ? explode('::', $callback, 2) : $callback; } } @@ -249,25 +249,37 @@ abstract class AbstractCloner implements ClonerInterface $stub->class = get_parent_class($class).'@anonymous'; } if (isset($this->classInfo[$class])) { - $classInfo = $this->classInfo[$class]; + list($i, $parents, $hasDebugInfo) = $this->classInfo[$class]; } else { - $classInfo = array( - new \ReflectionClass($class), - array_reverse(array($class => $class) + class_parents($class) + class_implements($class) + array('*' => '*')), - ); - $classInfo[1] = array_map('strtolower', $classInfo[1]); + $i = 2; + $parents = array(strtolower($class)); + $hasDebugInfo = method_exists($class, '__debugInfo'); - $this->classInfo[$class] = $classInfo; + foreach (class_parents($class) as $p) { + $parents[] = strtolower($p); + ++$i; + } + foreach (class_implements($class) as $p) { + $parents[] = strtolower($p); + ++$i; + } + $parents[] = '*'; + + $this->classInfo[$class] = array($i, $parents, $hasDebugInfo); } - $a = Caster::castObject($obj, $classInfo[0]); + $a = Caster::castObject($obj, $class, $hasDebugInfo); - foreach ($classInfo[1] as $p) { - if (!empty($this->casters[$p])) { - foreach ($this->casters[$p] as $p) { - $a = $this->callCaster($p, $obj, $a, $stub, $isNested); + try { + while ($i--) { + if (!empty($this->casters[$p = $parents[$i]])) { + foreach ($this->casters[$p] as $callback) { + $a = $callback($obj, $a, $stub, $isNested, $this->filter); + } } } + } catch (\Exception $e) { + $a = array((Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)) + $a; } return $a; @@ -287,33 +299,11 @@ abstract class AbstractCloner implements ClonerInterface $res = $stub->value; $type = $stub->class; - if (!empty($this->casters[':'.$type])) { - foreach ($this->casters[':'.$type] as $c) { - $a = $this->callCaster($c, $res, $a, $stub, $isNested); - } - } - - return $a; - } - - /** - * Calls a custom caster. - * - * @param callable $callback The caster - * @param object|resource $obj The object/resource being casted - * @param array $a The result of the previous cast for chained casters - * @param Stub $stub The Stub for the casted object/resource - * @param bool $isNested True if $obj is nested in the dumped structure - * - * @return array The casted object/resource - */ - private function callCaster($callback, $obj, $a, $stub, $isNested) - { try { - $cast = call_user_func($callback, $obj, $a, $stub, $isNested, $this->filter); - - if (is_array($cast)) { - $a = $cast; + if (!empty($this->casters[':'.$type])) { + foreach ($this->casters[':'.$type] as $callback) { + $a = $callback($res, $a, $stub, $isNested, $this->filter); + } } } catch (\Exception $e) { $a = array((Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)) + $a; diff --git a/src/Symfony/Component/VarDumper/Cloner/Data.php b/src/Symfony/Component/VarDumper/Cloner/Data.php index 22fd2fd1a4..5a6997cf25 100644 --- a/src/Symfony/Component/VarDumper/Cloner/Data.php +++ b/src/Symfony/Component/VarDumper/Cloner/Data.php @@ -16,7 +16,7 @@ use Symfony\Component\VarDumper\Caster\Caster; /** * @author Nicolas Grekas */ -class Data +class Data implements \ArrayAccess, \Countable, \IteratorAggregate { private $data; private $position = 0; @@ -33,11 +33,147 @@ class Data $this->data = $data; } + /** + * @return string The type of the value. + */ + public function getType() + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!$item instanceof Stub) { + return gettype($item); + } + if (Stub::TYPE_STRING === $item->type) { + return 'string'; + } + if (Stub::TYPE_ARRAY === $item->type) { + return 'array'; + } + if (Stub::TYPE_OBJECT === $item->type) { + return $item->class; + } + if (Stub::TYPE_RESOURCE === $item->type) { + return $item->class.' resource'; + } + } + + /** + * @param bool $recursive Whether values should be resolved recursively or not. + * + * @return scalar|array|null|Data[] A native representation of the original value. + */ + public function getValue($recursive = false) + { + $item = $this->data[$this->position][$this->key]; + + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } + if (!$item instanceof Stub) { + return $item; + } + if (Stub::TYPE_STRING === $item->type) { + return $item->value; + } + + $children = $item->position ? $this->data[$item->position] : array(); + + foreach ($children as $k => $v) { + if ($recursive && !$v instanceof Stub) { + continue; + } + $children[$k] = clone $this; + $children[$k]->key = $k; + $children[$k]->position = $item->position; + + if ($recursive) { + if ($v instanceof Stub && Stub::TYPE_REF === $v->type && $v->value instanceof Stub) { + $recursive = (array) $recursive; + if (isset($recursive[$v->value->position])) { + continue; + } + $recursive[$v->value->position] = true; + } + $children[$k] = $children[$k]->getValue($recursive); + } + } + + return $children; + } + + public function count() + { + return count($this->getValue()); + } + + public function getIterator() + { + if (!is_array($value = $this->getValue())) { + throw new \LogicException(sprintf('%s object holds non-iterable type "%s".', self::class, gettype($value))); + } + + foreach ($value as $k => $v) { + yield $k => $v; + } + } + + public function __get($key) + { + if (null !== $data = $this->seek($key)) { + $item = $data->data[$data->position][$data->key]; + + return $item instanceof Stub || array() === $item ? $data : $item; + } + } + + public function __isset($key) + { + return null !== $this->seek($key); + } + + public function offsetExists($key) + { + return $this->__isset($key); + } + + public function offsetGet($key) + { + return $this->__get($key); + } + + public function offsetSet($key, $value) + { + throw new \BadMethodCallException(self::class.' objects are immutable.'); + } + + public function offsetUnset($key) + { + throw new \BadMethodCallException(self::class.' objects are immutable.'); + } + + public function __toString() + { + $value = $this->getValue(); + + if (!is_array($value)) { + return (string) $value; + } + + return sprintf('%s (count=%d)', $this->getType(), count($value)); + } + /** * @return array The raw data structure + * + * @deprecated since version 3.3. Use array or object access instead. */ public function getRawData() { + @trigger_error(sprintf('The %s() method is deprecated since version 3.3 and will be removed in 4.0. Use the array or object access instead.', __METHOD__)); + return $this->data; } @@ -97,6 +233,9 @@ class Data { $item = $this->data[$this->position][$this->key]; + if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) { + $item = $item->value; + } if (!$item instanceof Stub || !$item->position) { return; } diff --git a/src/Symfony/Component/VarDumper/Tests/Cloner/DataTest.php b/src/Symfony/Component/VarDumper/Tests/Cloner/DataTest.php new file mode 100644 index 0000000000..83f6b60074 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Tests/Cloner/DataTest.php @@ -0,0 +1,115 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Tests\Cloner; + +use PHPUnit\Framework\TestCase; +use Symfony\Component\VarDumper\Caster\Caster; +use Symfony\Component\VarDumper\Caster\ClassStub; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Cloner\VarCloner; + +class DataTest extends TestCase +{ + public function testBasicData() + { + $values = array(1 => 123, 4.5, 'abc', null, false); + $data = $this->cloneVar($values); + $clonedValues = array(); + + $this->assertInstanceOf(Data::class, $data); + $this->assertCount(count($values), $data); + $this->assertFalse(isset($data->{0})); + $this->assertFalse(isset($data[0])); + + foreach ($data as $k => $v) { + $this->assertTrue(isset($data->{$k})); + $this->assertTrue(isset($data[$k])); + $this->assertSame(gettype($values[$k]), $data->seek($k)->getType()); + $this->assertSame($values[$k], $data->seek($k)->getValue()); + $this->assertSame($values[$k], $data->{$k}); + $this->assertSame($values[$k], $data[$k]); + $this->assertSame((string) $values[$k], (string) $data->seek($k)); + + $clonedValues[$k] = $v->getValue(); + } + + $this->assertSame($values, $clonedValues); + } + + public function testObject() + { + $data = $this->cloneVar(new \Exception('foo')); + + $this->assertSame('Exception', $data->getType()); + + $this->assertSame('foo', $data->message); + $this->assertSame('foo', $data->{Caster::PREFIX_PROTECTED.'message'}); + + $this->assertSame('foo', $data['message']); + $this->assertSame('foo', $data[Caster::PREFIX_PROTECTED.'message']); + + $this->assertStringMatchesFormat('Exception (count=%d)', (string) $data); + } + + public function testArray() + { + $values = array(array(), array(123)); + $data = $this->cloneVar($values); + + $this->assertSame($values, $data->getValue(true)); + + $children = $data->getValue(); + + $this->assertInternalType('array', $children); + + $this->assertInstanceOf(Data::class, $children[0]); + $this->assertInstanceOf(Data::class, $children[1]); + + $this->assertEquals($children[0], $data[0]); + $this->assertEquals($children[1], $data[1]); + + $this->assertSame($values[0], $children[0]->getValue(true)); + $this->assertSame($values[1], $children[1]->getValue(true)); + } + + public function testStub() + { + $data = $this->cloneVar(array(new ClassStub('stdClass'))); + $data = $data[0]; + + $this->assertSame('string', $data->getType()); + $this->assertSame('stdClass', $data->getValue()); + $this->assertSame('stdClass', (string) $data); + } + + public function testHardRefs() + { + $values = array(array()); + $values[1] = &$values[0]; + $values[2][0] = &$values[2]; + + $data = $this->cloneVar($values); + + $this->assertSame(array(), $data[0]->getValue()); + $this->assertSame(array(), $data[1]->getValue()); + $this->assertEquals(array($data[2]->getValue()), $data[2]->getValue(true)); + + $this->assertSame('array (count=3)', (string) $data); + } + + private function cloneVar($value) + { + $cloner = new VarCloner(); + + return $cloner->cloneVar($value); + } +} diff --git a/src/Symfony/Component/VarDumper/Tests/VarClonerTest.php b/src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php similarity index 99% rename from src/Symfony/Component/VarDumper/Tests/VarClonerTest.php rename to src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php index 273be61121..75774170f3 100644 --- a/src/Symfony/Component/VarDumper/Tests/VarClonerTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Cloner/VarClonerTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\VarDumper\Tests; +namespace Symfony\Component\VarDumper\Tests\Cloner; use PHPUnit\Framework\TestCase; use Symfony\Component\VarDumper\Cloner\VarCloner; diff --git a/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php b/src/Symfony/Component/VarDumper/Tests/Dumper/CliDumperTest.php similarity index 97% rename from src/Symfony/Component/VarDumper/Tests/CliDumperTest.php rename to src/Symfony/Component/VarDumper/Tests/Dumper/CliDumperTest.php index b06fe3e9e1..638570c3db 100644 --- a/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Dumper/CliDumperTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\VarDumper\Tests; +namespace Symfony\Component\VarDumper\Tests\Dumper; use PHPUnit\Framework\TestCase; use Symfony\Component\VarDumper\Cloner\VarCloner; @@ -25,7 +25,7 @@ class CliDumperTest extends TestCase public function testGet() { - require __DIR__.'/Fixtures/dumb-var.php'; + require __DIR__.'/../Fixtures/dumb-var.php'; $dumper = new CliDumper('php://output'); $dumper->setColors(false); @@ -75,8 +75,8 @@ array:24 [ +"bar": "bar" } "closure" => Closure {{$r} - class: "Symfony\Component\VarDumper\Tests\CliDumperTest" - this: Symfony\Component\VarDumper\Tests\CliDumperTest {{$r} …} + class: "Symfony\Component\VarDumper\Tests\Dumper\CliDumperTest" + this: Symfony\Component\VarDumper\Tests\Dumper\CliDumperTest {{$r} …} parameters: { \$a: {} &\$b: { @@ -297,7 +297,7 @@ EOTXT { $out = fopen('php://memory', 'r+b'); - require_once __DIR__.'/Fixtures/Twig.php'; + require_once __DIR__.'/../Fixtures/Twig.php'; $twig = new \__TwigTemplate_VarDumperFixture_u75a09(new \Twig_Environment(new \Twig_Loader_Filesystem())); $dumper = new CliDumper(); diff --git a/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php b/src/Symfony/Component/VarDumper/Tests/Dumper/HtmlDumperTest.php similarity index 95% rename from src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php rename to src/Symfony/Component/VarDumper/Tests/Dumper/HtmlDumperTest.php index 24b4bf6d28..37a310e7ce 100644 --- a/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php +++ b/src/Symfony/Component/VarDumper/Tests/Dumper/HtmlDumperTest.php @@ -9,7 +9,7 @@ * file that was distributed with this source code. */ -namespace Symfony\Component\VarDumper\Tests; +namespace Symfony\Component\VarDumper\Tests\Dumper; use PHPUnit\Framework\TestCase; use Symfony\Component\VarDumper\Cloner\VarCloner; @@ -22,7 +22,7 @@ class HtmlDumperTest extends TestCase { public function testGet() { - require __DIR__.'/Fixtures/dumb-var.php'; + require __DIR__.'/../Fixtures/dumb-var.php'; $dumper = new HtmlDumper('php://output'); $dumper->setDumpHeader(''); @@ -76,9 +76,9 @@ class HtmlDumperTest extends TestCase +"bar": "bar" } "closure" => Closure {{$r} - class: "Symfony\Component\VarDumper\Tests\HtmlDumperTest" - this: HtmlDumperTest {{$r} &%s;} + class: "Symfony\Component\VarDumper\Tests\Dumper\HtmlDumperTest" + this: HtmlDumperTest {{$r} &%s;} parameters: { \$a: {} &\$b: {