feature #21638 [VarDumper] Allow seamless use of Data clones (nicolas-grekas)

This PR was merged into the 3.3-dev branch.

Discussion
----------

[VarDumper] Allow seamless use of Data clones

| Q             | A
| ------------- | ---
| Branch?       | master
| Bug fix?      | no
| New feature?  | yes
| BC breaks?    | no
| Deprecations? | yes
| Tests pass?   | yes
| Fixed tickets | -
| License       | MIT
| Doc PR        | -

By implementing `ArrayAccess`, `Countable`, `IteratorAggregate`, `__get`, `__isset` and `__toString`,  VarDumper's `Data` objects become seamless and behave almost identically from their original clones values, especially from the PoV of Twig.

In data collectors, this allows replacing the many nested calls to `cloneVar` by a single one.

This makes the code simpler, and should make a significant difference in term of performance.

Todo:
- [x] push a Blackfire profile comparison
- [x] double check that the profiler works as expected.

Commits
-------

ab716c64de [VarDumper] Allow seamless use of Data clones
This commit is contained in:
Fabien Potencier 2017-02-27 14:21:39 -08:00
commit f2aa8136a8
38 changed files with 558 additions and 409 deletions

View File

@ -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);
}
/**

View File

@ -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()

View File

@ -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"
},

View File

@ -90,7 +90,7 @@
<div class="metric">
<span class="value">
{% if key == 'time' %}
{{ '%0.2f'|format(1000*value) }} ms
{{ '%0.2f'|format(1000*value.value) }} ms
{% else %}
{{ value }}
{% endif %}

View File

@ -267,7 +267,7 @@
{% for name in collector.bundles|keys|sort %}
<tr>
<th scope="row" class="font-normal">{{ name }}</th>
<td class="font-normal">{{ collector.bundles[name] }}</td>
<td class="font-normal">{{ profiler_dump(collector.bundles[name]) }}</td>
</tr>
{% endfor %}
</tbody>

View File

@ -75,7 +75,7 @@
<tr>
<td class="text-right">{{ listener.priority|default('-') }}</td>
<td class="font-normal">{{ profiler_dump(listener.data) }}</td>
<td class="font-normal">{{ profiler_dump(listener.stub) }}</td>
</tr>
{% if loop.last %}

View File

@ -182,7 +182,7 @@
<a class="btn btn-link text-small sf-toggle" data-toggle-selector="#{{ context_id }}" data-toggle-alt-content="Hide trace">Show trace</a>
<div id="{{ context_id }}" class="context sf-toggle-content sf-toggle-hidden">
{{ profiler_dump(log.context.seek('exception').seek('\0Exception\0trace'), maxDepth=2) }}
{{ profiler_dump(log.context.exception['\0Exception\0trace'], maxDepth=2) }}
</div>
</span>
{% elseif log.context is defined and log.context is not empty %}

View File

@ -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['&quot;'.$v.'&quot;'] = $replacements[$v] = $this->dumpData($env, $context->seek($k));
foreach ($context as $k => $v) {
$k = '{'.twig_escape_filter($env, $k).'}';
$replacements['&quot;'.$k.'&quot;'] = $replacements[$k] = $this->dumpData($env, $v);
}
return '<span class="dump-inline">'.strtr($message, $replacements).'</span>';

View File

@ -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\\": "" },

View File

@ -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;

View File

@ -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"
},

View File

@ -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,
);
}

View File

@ -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());
}

View File

@ -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)

View File

@ -32,14 +32,15 @@
"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/dependency-injection": "<3.2",
"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.",

View File

@ -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()

View File

@ -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;
}
}

View File

@ -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);
}
/**

View File

@ -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,

View File

@ -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();
}
/**

View File

@ -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]);
}
}

View File

@ -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,

View File

@ -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()

View File

@ -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();
}
}

View File

@ -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()

View File

@ -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": "",

View File

@ -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'];

View File

@ -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()

View File

@ -17,6 +17,8 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Helper for filtering out properties in casters.
*
* @author Nicolas Grekas <p@tchwork.com>
*
* @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);
}
}

View File

@ -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']);

View File

@ -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']);

View File

@ -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);
}

View File

@ -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;

View File

@ -16,7 +16,7 @@ use Symfony\Component\VarDumper\Caster\Caster;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
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;
}

View File

@ -0,0 +1,115 @@
<?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\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);
}
}

View File

@ -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;

View File

@ -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();

View File

@ -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('<foo></foo>');
@ -76,9 +76,9 @@ class HtmlDumperTest extends TestCase
+"<span class=sf-dump-public title="Runtime added dynamic property">bar</span>": "<span class=sf-dump-str title="3 characters">bar</span>"
</samp>}
"<span class=sf-dump-key>closure</span>" => <span class=sf-dump-note>Closure</span> {{$r}<samp>
<span class=sf-dump-meta>class</span>: "<span class=sf-dump-str title="Symfony\Component\VarDumper\Tests\HtmlDumperTest
48 characters"><span class=sf-dump-ellipsis>Symfony\Component\VarDumper\Tests</span>\HtmlDumperTest</span>"
<span class=sf-dump-meta>this</span>: <abbr title="Symfony\Component\VarDumper\Tests\HtmlDumperTest" class=sf-dump-note>HtmlDumperTest</abbr> {{$r} &%s;}
<span class=sf-dump-meta>class</span>: "<span class=sf-dump-str title="Symfony\Component\VarDumper\Tests\Dumper\HtmlDumperTest
55 characters"><span class=sf-dump-ellipsis>Symfony\Component\VarDumper\Tests\Dumper</span>\HtmlDumperTest</span>"
<span class=sf-dump-meta>this</span>: <abbr title="Symfony\Component\VarDumper\Tests\Dumper\HtmlDumperTest" class=sf-dump-note>HtmlDumperTest</abbr> {{$r} &%s;}
<span class=sf-dump-meta>parameters</span>: {<samp>
<span class=sf-dump-meta>\$a</span>: {}
<span class=sf-dump-meta>&amp;\$b</span>: {<samp>