Merge pull request #18244 from Hystepik/develop#3

Fix php debug bar for php 8.0
This commit is contained in:
Laurent Destailleur
2021-10-19 20:59:19 +02:00
committed by GitHub
51 changed files with 2393 additions and 585 deletions

View File

@@ -35,7 +35,7 @@
"nnnick/chartjs" : "^2.9", "nnnick/chartjs" : "^2.9",
"stripe/stripe-php" : "6.43.1", "stripe/stripe-php" : "6.43.1",
"maximebf/debugbar" : "1.15.1", "maximebf/debugbar" : "1.15.1",
"symfony/var-dumper" : "3" "symfony/var-dumper" : "3.2"
}, },
"require-dev" : { "require-dev" : {
"php-parallel-lint/php-parallel-lint" : "^0", "php-parallel-lint/php-parallel-lint" : "^0",

4
composer.lock generated
View File

@@ -591,7 +591,7 @@
}, },
{ {
"name": "symfony/var-dumper", "name": "symfony/var-dumper",
"version": "v3.0.0", "version": "v3.2.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/var-dumper.git", "url": "https://github.com/symfony/var-dumper.git",
@@ -616,7 +616,7 @@
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "3.0-dev" "dev-master": "3.2-dev"
} }
}, },
"autoload": { "autoload": {

View File

@@ -48,7 +48,16 @@ class AmqpCaster
{ {
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
// BC layer in the ampq lib $a += array(
$prefix.'is_connected' => $c->isConnected(),
);
// Recent version of the extension already expose private properties
if (isset($a["\x00AMQPConnection\x00login"])) {
return $a;
}
// BC layer in the amqp lib
if (method_exists($c, 'getReadTimeout')) { if (method_exists($c, 'getReadTimeout')) {
$timeout = $c->getReadTimeout(); $timeout = $c->getReadTimeout();
} else { } else {
@@ -56,13 +65,13 @@ class AmqpCaster
} }
$a += array( $a += array(
$prefix.'isConnected' => $c->isConnected(), $prefix.'is_connected' => $c->isConnected(),
$prefix.'login' => $c->getLogin(), $prefix.'login' => $c->getLogin(),
$prefix.'password' => $c->getPassword(), $prefix.'password' => $c->getPassword(),
$prefix.'host' => $c->getHost(), $prefix.'host' => $c->getHost(),
$prefix.'port' => $c->getPort(),
$prefix.'vhost' => $c->getVhost(), $prefix.'vhost' => $c->getVhost(),
$prefix.'readTimeout' => $timeout, $prefix.'port' => $c->getPort(),
$prefix.'read_timeout' => $timeout,
); );
return $a; return $a;
@@ -73,11 +82,19 @@ class AmqpCaster
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
$a += array( $a += array(
$prefix.'isConnected' => $c->isConnected(), $prefix.'is_connected' => $c->isConnected(),
$prefix.'channelId' => $c->getChannelId(), $prefix.'channel_id' => $c->getChannelId(),
$prefix.'prefetchSize' => $c->getPrefetchSize(), );
$prefix.'prefetchCount' => $c->getPrefetchCount(),
// Recent version of the extension already expose private properties
if (isset($a["\x00AMQPChannel\x00connection"])) {
return $a;
}
$a += array(
$prefix.'connection' => $c->getConnection(), $prefix.'connection' => $c->getConnection(),
$prefix.'prefetch_size' => $c->getPrefetchSize(),
$prefix.'prefetch_count' => $c->getPrefetchCount(),
); );
return $a; return $a;
@@ -88,11 +105,19 @@ class AmqpCaster
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
$a += array( $a += array(
$prefix.'name' => $c->getName(),
$prefix.'flags' => self::extractFlags($c->getFlags()), $prefix.'flags' => self::extractFlags($c->getFlags()),
$prefix.'arguments' => $c->getArguments(), );
// Recent version of the extension already expose private properties
if (isset($a["\x00AMQPQueue\x00name"])) {
return $a;
}
$a += array(
$prefix.'connection' => $c->getConnection(), $prefix.'connection' => $c->getConnection(),
$prefix.'channel' => $c->getChannel(), $prefix.'channel' => $c->getChannel(),
$prefix.'name' => $c->getName(),
$prefix.'arguments' => $c->getArguments(),
); );
return $a; return $a;
@@ -103,12 +128,24 @@ class AmqpCaster
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
$a += array( $a += array(
$prefix.'name' => $c->getName(),
$prefix.'flags' => self::extractFlags($c->getFlags()), $prefix.'flags' => self::extractFlags($c->getFlags()),
$prefix.'type' => isset(self::$exchangeTypes[$c->getType()]) ? new ConstStub(self::$exchangeTypes[$c->getType()], $c->getType()) : $c->getType(), );
$prefix.'arguments' => $c->getArguments(),
$prefix.'channel' => $c->getChannel(), $type = isset(self::$exchangeTypes[$c->getType()]) ? new ConstStub(self::$exchangeTypes[$c->getType()], $c->getType()) : $c->getType();
// Recent version of the extension already expose private properties
if (isset($a["\x00AMQPExchange\x00name"])) {
$a["\x00AMQPExchange\x00type"] = $type;
return $a;
}
$a += array(
$prefix.'connection' => $c->getConnection(), $prefix.'connection' => $c->getConnection(),
$prefix.'channel' => $c->getChannel(),
$prefix.'name' => $c->getName(),
$prefix.'type' => $type,
$prefix.'arguments' => $c->getArguments(),
); );
return $a; return $a;
@@ -118,28 +155,37 @@ class AmqpCaster
{ {
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
$deliveryMode = new ConstStub($c->getDeliveryMode().(2 === $c->getDeliveryMode() ? ' (persistent)' : ' (non-persistent)'), $c->getDeliveryMode());
// Recent version of the extension already expose private properties
if (isset($a["\x00AMQPEnvelope\x00body"])) {
$a["\0AMQPEnvelope\0delivery_mode"] = $deliveryMode;
return $a;
}
if (!($filter & Caster::EXCLUDE_VERBOSE)) { if (!($filter & Caster::EXCLUDE_VERBOSE)) {
$a += array($prefix.'body' => $c->getBody()); $a += array($prefix.'body' => $c->getBody());
} }
$a += array( $a += array(
$prefix.'routingKey' => $c->getRoutingKey(), $prefix.'delivery_tag' => $c->getDeliveryTag(),
$prefix.'deliveryTag' => $c->getDeliveryTag(), $prefix.'is_redelivery' => $c->isRedelivery(),
$prefix.'deliveryMode' => new ConstStub($c->getDeliveryMode().(2 === $c->getDeliveryMode() ? ' (persistent)' : ' (non-persistent)'), $c->getDeliveryMode()), $prefix.'exchange_name' => $c->getExchangeName(),
$prefix.'exchangeName' => $c->getExchangeName(), $prefix.'routing_key' => $c->getRoutingKey(),
$prefix.'isRedelivery' => $c->isRedelivery(), $prefix.'content_type' => $c->getContentType(),
$prefix.'contentType' => $c->getContentType(), $prefix.'content_encoding' => $c->getContentEncoding(),
$prefix.'contentEncoding' => $c->getContentEncoding(),
$prefix.'type' => $c->getType(),
$prefix.'timestamp' => $c->getTimestamp(),
$prefix.'priority' => $c->getPriority(),
$prefix.'expiration' => $c->getExpiration(),
$prefix.'userId' => $c->getUserId(),
$prefix.'appId' => $c->getAppId(),
$prefix.'messageId' => $c->getMessageId(),
$prefix.'replyTo' => $c->getReplyTo(),
$prefix.'correlationId' => $c->getCorrelationId(),
$prefix.'headers' => $c->getHeaders(), $prefix.'headers' => $c->getHeaders(),
$prefix.'delivery_mode' => $deliveryMode,
$prefix.'priority' => $c->getPriority(),
$prefix.'correlation_id' => $c->getCorrelationId(),
$prefix.'reply_to' => $c->getReplyTo(),
$prefix.'expiration' => $c->getExpiration(),
$prefix.'message_id' => $c->getMessageId(),
$prefix.'timestamp' => $c->getTimeStamp(),
$prefix.'type' => $c->getType(),
$prefix.'user_id' => $c->getUserId(),
$prefix.'app_id' => $c->getAppId(),
); );
return $a; return $a;

View File

@@ -0,0 +1,80 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarDumper\Caster;
use Symfony\Component\VarDumper\Cloner\Stub;
/**
* Represents a list of function arguments.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ArgsStub extends EnumStub
{
private static $parameters = array();
public function __construct(array $args, $function, $class)
{
list($variadic, $params) = self::getParameters($function, $class);
$values = array();
foreach ($args as $k => $v) {
$values[$k] = !is_scalar($v) && !$v instanceof Stub ? new CutStub($v) : $v;
}
if (null === $params) {
parent::__construct($values, false);
return;
}
if (count($values) < count($params)) {
$params = array_slice($params, 0, count($values));
} elseif (count($values) > count($params)) {
$values[] = new EnumStub(array_splice($values, count($params)), false);
$params[] = $variadic;
}
if (array('...') === $params) {
$this->dumpKeys = false;
$this->value = $values[0]->value;
} else {
$this->value = array_combine($params, $values);
}
}
private static function getParameters($function, $class)
{
if (isset(self::$parameters[$k = $class.'::'.$function])) {
return self::$parameters[$k];
}
try {
$r = null !== $class ? new \ReflectionMethod($class, $function) : new \ReflectionFunction($function);
} catch (\ReflectionException $e) {
return array(null, null);
}
$variadic = '...';
$params = array();
foreach ($r->getParameters() as $v) {
$k = '$'.$v->name;
if ($v->isPassedByReference()) {
$k = '&'.$k;
}
if (method_exists($v, 'isVariadic') && $v->isVariadic()) {
$variadic .= $k;
} else {
$params[] = $k;
}
}
return self::$parameters[$k] = array($variadic, $params);
}
}

View File

@@ -11,6 +11,8 @@
namespace Symfony\Component\VarDumper\Caster; namespace Symfony\Component\VarDumper\Caster;
use Symfony\Component\VarDumper\Cloner\Stub;
/** /**
* Helper for filtering out properties in casters. * Helper for filtering out properties in casters.
* *
@@ -36,29 +38,39 @@ class Caster
/** /**
* Casts objects to arrays and adds the dynamic property prefix. * Casts objects to arrays and adds the dynamic property prefix.
* *
* @param object $obj The object to cast. * @param object $obj The object to cast
* @param \ReflectionClass $reflector The class reflector to use for inspecting the object definition. * @param \ReflectionClass $reflector The class reflector to use for inspecting the object definition
* *
* @return array The array-cast of the object, with prefixed dynamic properties. * @return array The array-cast of the object, with prefixed dynamic properties
*/ */
public static function castObject($obj, \ReflectionClass $reflector) public static function castObject($obj, \ReflectionClass $reflector)
{ {
if ($reflector->hasMethod('__debugInfo')) { if ($reflector->hasMethod('__debugInfo')) {
$a = $obj->__debugInfo(); $a = $obj->__debugInfo();
} elseif ($obj instanceof \Closure) {
$a = array();
} else { } else {
$a = (array) $obj; $a = (array) $obj;
} }
if ($obj instanceof \__PHP_Incomplete_Class) {
return $a;
}
if ($a) { if ($a) {
$combine = false;
$p = array_keys($a); $p = array_keys($a);
foreach ($p as $i => $k) { foreach ($p as $i => $k) {
if (!isset($k[0]) || ("\0" !== $k[0] && !$reflector->hasProperty($k))) { if (isset($k[0]) ? "\0" !== $k[0] && !$reflector->hasProperty($k) : \PHP_VERSION_ID >= 70200) {
$combine = true;
$p[$i] = self::PREFIX_DYNAMIC.$k; $p[$i] = self::PREFIX_DYNAMIC.$k;
} elseif (isset($k[16]) && "\0" === $k[16] && 0 === strpos($k, "\0class@anonymous\0")) { } elseif (isset($k[16]) && "\0" === $k[16] && 0 === strpos($k, "\0class@anonymous\0")) {
$combine = true;
$p[$i] = "\0".$reflector->getParentClass().'@anonymous'.strrchr($k, "\0"); $p[$i] = "\0".$reflector->getParentClass().'@anonymous'.strrchr($k, "\0");
} }
} }
$a = array_combine($p, $a); if ($combine) {
$a = array_combine($p, $a);
}
} }
return $a; return $a;
@@ -70,14 +82,17 @@ class Caster
* By default, a single match in the $filter bit field filters properties out, following an "or" logic. * By default, a single match in the $filter bit field filters properties out, following an "or" logic.
* When EXCLUDE_STRICT is set, an "and" logic is applied: all bits must match for a property to be removed. * When EXCLUDE_STRICT is set, an "and" logic is applied: all bits must match for a property to be removed.
* *
* @param array $a The array containing the properties to filter. * @param array $a The array containing the properties to filter
* @param int $filter A bit field of Caster::EXCLUDE_* constants specifying which properties to filter out. * @param int $filter A bit field of Caster::EXCLUDE_* constants specifying which properties to filter out
* @param string[] $listedProperties List of properties to exclude when Caster::EXCLUDE_VERBOSE is set, and to preserve when Caster::EXCLUDE_NOT_IMPORTANT is set. * @param string[] $listedProperties List of properties to exclude when Caster::EXCLUDE_VERBOSE is set, and to preserve when Caster::EXCLUDE_NOT_IMPORTANT is set
* @param int &$count Set to the number of removed properties
* *
* @return array The filtered array * @return array The filtered array
*/ */
public static function filter(array $a, $filter, array $listedProperties = array()) public static function filter(array $a, $filter, array $listedProperties = array(), &$count = 0)
{ {
$count = 0;
foreach ($a as $k => $v) { foreach ($a as $k => $v) {
$type = self::EXCLUDE_STRICT & $filter; $type = self::EXCLUDE_STRICT & $filter;
@@ -108,9 +123,20 @@ class Caster
if ((self::EXCLUDE_STRICT & $filter) ? $type === $filter : $type) { if ((self::EXCLUDE_STRICT & $filter) ? $type === $filter : $type) {
unset($a[$k]); unset($a[$k]);
++$count;
} }
} }
return $a; return $a;
} }
public static function castPhpIncompleteClass(\__PHP_Incomplete_Class $c, array $a, Stub $stub, $isNested)
{
if (isset($a['__PHP_Incomplete_Class_Name'])) {
$stub->class .= '('.$a['__PHP_Incomplete_Class_Name'].')';
unset($a['__PHP_Incomplete_Class_Name']);
}
return $a;
}
} }

View File

@@ -0,0 +1,87 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarDumper\Caster;
/**
* Represents a PHP class identifier.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ClassStub extends ConstStub
{
/**
* Constructor.
*
* @param string A PHP identifier, e.g. a class, method, interface, etc. name
* @param callable The callable targeted by the identifier when it is ambiguous or not a real PHP identifier
*/
public function __construct($identifier, $callable = null)
{
$this->value = $identifier;
if (0 < $i = strrpos($identifier, '\\')) {
$this->attr['ellipsis'] = strlen($identifier) - $i;
}
try {
if (null !== $callable) {
if ($callable instanceof \Closure) {
$r = new \ReflectionFunction($callable);
} elseif (is_object($callable)) {
$r = array($callable, '__invoke');
} elseif (is_array($callable)) {
$r = $callable;
} elseif (false !== $i = strpos($callable, '::')) {
$r = array(substr($callable, 0, $i), substr($callable, 2 + $i));
} else {
$r = new \ReflectionFunction($callable);
}
} elseif (false !== $i = strpos($identifier, '::')) {
$r = array(substr($identifier, 0, $i), substr($identifier, 2 + $i));
} else {
$r = new \ReflectionClass($identifier);
}
if (is_array($r)) {
try {
$r = new \ReflectionMethod($r[0], $r[1]);
} catch (\ReflectionException $e) {
$r = new \ReflectionClass($r[0]);
}
}
} catch (\ReflectionException $e) {
return;
}
if ($f = $r->getFileName()) {
$this->attr['file'] = $f;
$this->attr['line'] = $r->getStartLine();
}
}
public static function wrapCallable($callable)
{
if (is_object($callable) || !is_callable($callable)) {
return $callable;
}
if (!is_array($callable)) {
$callable = new static($callable);
} elseif (is_string($callable[0])) {
$callable[0] = new static($callable[0]);
} else {
$callable[1] = new static($callable[1], $callable);
}
return $callable;
}
}

View File

@@ -25,4 +25,9 @@ class ConstStub extends Stub
$this->class = $name; $this->class = $name;
$this->value = $value; $this->value = $value;
} }
public function __toString()
{
return (string) $this->value;
}
} }

View File

@@ -39,9 +39,12 @@ class CutStub extends Stub
case 'resource': case 'resource':
case 'unknown type': case 'unknown type':
case 'resource (closed)':
$this->type = self::TYPE_RESOURCE; $this->type = self::TYPE_RESOURCE;
$this->handle = (int) $value; $this->handle = (int) $value;
$this->class = @get_resource_type($value); if ('Unknown' === $this->class = @get_resource_type($value)) {
$this->class = 'Closed';
}
$this->cut = -1; $this->cut = -1;
break; break;

View File

@@ -107,7 +107,7 @@ class DOMCaster
'namespaceURI' => $dom->namespaceURI, 'namespaceURI' => $dom->namespaceURI,
'prefix' => $dom->prefix, 'prefix' => $dom->prefix,
'localName' => $dom->localName, 'localName' => $dom->localName,
'baseURI' => $dom->baseURI, 'baseURI' => $dom->baseURI ? new LinkStub($dom->baseURI) : $dom->baseURI,
'textContent' => new CutStub($dom->textContent), 'textContent' => new CutStub($dom->textContent),
); );
@@ -144,7 +144,7 @@ class DOMCaster
'version' => $dom->version, 'version' => $dom->version,
'xmlVersion' => $dom->xmlVersion, 'xmlVersion' => $dom->xmlVersion,
'strictErrorChecking' => $dom->strictErrorChecking, 'strictErrorChecking' => $dom->strictErrorChecking,
'documentURI' => $dom->documentURI, 'documentURI' => $dom->documentURI ? new LinkStub($dom->documentURI) : $dom->documentURI,
'config' => $dom->config, 'config' => $dom->config,
'formatOutput' => $dom->formatOutput, 'formatOutput' => $dom->formatOutput,
'validateOnParse' => $dom->validateOnParse, 'validateOnParse' => $dom->validateOnParse,
@@ -237,7 +237,7 @@ class DOMCaster
'columnNumber' => $dom->columnNumber, 'columnNumber' => $dom->columnNumber,
'offset' => $dom->offset, 'offset' => $dom->offset,
'relatedNode' => $dom->relatedNode, 'relatedNode' => $dom->relatedNode,
'uri' => $dom->uri, 'uri' => $dom->uri ? new LinkStub($dom->uri, $dom->lineNumber) : $dom->uri,
); );
return $a; return $a;

View File

@@ -20,8 +20,11 @@ use Symfony\Component\VarDumper\Cloner\Stub;
*/ */
class EnumStub extends Stub class EnumStub extends Stub
{ {
public function __construct(array $values) public $dumpKeys = true;
public function __construct(array $values, $dumpKeys = true)
{ {
$this->value = $values; $this->value = $values;
$this->dumpKeys = $dumpKeys;
} }
} }

View File

@@ -41,6 +41,8 @@ class ExceptionCaster
E_STRICT => 'E_STRICT', E_STRICT => 'E_STRICT',
); );
private static $framesCache = array();
public static function castError(\Error $e, array $a, Stub $stub, $isNested, $filter = 0) public static function castError(\Error $e, array $a, Stub $stub, $isNested, $filter = 0)
{ {
return self::filterExceptionArray($stub->class, $a, "\0Error\0", $filter); return self::filterExceptionArray($stub->class, $a, "\0Error\0", $filter);
@@ -65,13 +67,9 @@ class ExceptionCaster
$prefix = Caster::PREFIX_PROTECTED; $prefix = Caster::PREFIX_PROTECTED;
$xPrefix = "\0Exception\0"; $xPrefix = "\0Exception\0";
if (isset($a[$xPrefix.'previous'], $a[$xPrefix.'trace'])) { if (isset($a[$xPrefix.'previous'], $a[$xPrefix.'trace']) && $a[$xPrefix.'previous'] instanceof \Exception) {
$b = (array) $a[$xPrefix.'previous']; $b = (array) $a[$xPrefix.'previous'];
array_unshift($b[$xPrefix.'trace'], array( self::traceUnshift($b[$xPrefix.'trace'], get_class($a[$xPrefix.'previous']), $b[$prefix.'file'], $b[$prefix.'line']);
'function' => 'new '.get_class($a[$xPrefix.'previous']),
'file' => $b[$prefix.'file'],
'line' => $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, -1 - count($a[$xPrefix.'trace']->value));
} }
@@ -88,6 +86,7 @@ class ExceptionCaster
$stub->class = ''; $stub->class = '';
$stub->handle = 0; $stub->handle = 0;
$frames = $trace->value; $frames = $trace->value;
$prefix = Caster::PREFIX_VIRTUAL;
$a = array(); $a = array();
$j = count($frames); $j = count($frames);
@@ -97,34 +96,38 @@ class ExceptionCaster
if (!isset($trace->value[$i])) { if (!isset($trace->value[$i])) {
return array(); return array();
} }
$lastCall = isset($frames[$i]['function']) ? ' ==> '.(isset($frames[$i]['class']) ? $frames[0]['class'].$frames[$i]['type'] : '').$frames[$i]['function'].'()' : ''; $lastCall = isset($frames[$i]['function']) ? (isset($frames[$i]['class']) ? $frames[0]['class'].$frames[$i]['type'] : '').$frames[$i]['function'].'()' : '';
$frames[] = array('function' => '');
for ($j += $trace->numberingOffset - $i++; isset($frames[$i]); ++$i, --$j) { for ($j += $trace->numberingOffset - $i++; isset($frames[$i]); ++$i, --$j) {
$call = isset($frames[$i]['function']) ? (isset($frames[$i]['class']) ? $frames[$i]['class'].$frames[$i]['type'] : '').$frames[$i]['function'].'()' : '???'; $f = $frames[$i];
$call = isset($f['function']) ? (isset($f['class']) ? $f['class'].$f['type'] : '').$f['function'].'()' : '???';
$a[Caster::PREFIX_VIRTUAL.$j.'. '.$call.$lastCall] = new FrameStub( $label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$lastCall;
$frame = new FrameStub(
array( array(
'object' => isset($frames[$i]['object']) ? $frames[$i]['object'] : null, 'object' => isset($f['object']) ? $f['object'] : null,
'class' => isset($frames[$i]['class']) ? $frames[$i]['class'] : null, 'class' => isset($f['class']) ? $f['class'] : null,
'type' => isset($frames[$i]['type']) ? $frames[$i]['type'] : null, 'type' => isset($f['type']) ? $f['type'] : null,
'function' => isset($frames[$i]['function']) ? $frames[$i]['function'] : null, 'function' => isset($f['function']) ? $f['function'] : null,
) + $frames[$i - 1], ) + $frames[$i - 1],
$trace->keepArgs, false,
true true
); );
$f = self::castFrameStub($frame, array(), $frame, true);
if (isset($f[$prefix.'src'])) {
foreach ($f[$prefix.'src']->value as $label => $frame) {
$label = substr_replace($label, "title=Stack level $j.&", 2, 0);
}
$f = $frames[$i - 1];
if ($trace->keepArgs && !empty($f['args']) && $frame instanceof EnumStub) {
$frame->value['arguments'] = new ArgsStub($f['args'], isset($f['function']) ? $f['function'] : null, isset($f['class']) ? $f['class'] : null);
}
}
$a[$label] = $frame;
$lastCall = ' ==> '.$call; $lastCall = $call;
} }
$a[Caster::PREFIX_VIRTUAL.$j.'. {main}'.$lastCall] = new FrameStub(
array(
'object' => null,
'class' => null,
'type' => null,
'function' => '{main}',
) + $frames[$i - 1],
$trace->keepArgs,
true
);
if (null !== $trace->sliceLength) { if (null !== $trace->sliceLength) {
$a = array_slice($a, 0, $trace->sliceLength, true); $a = array_slice($a, 0, $trace->sliceLength, true);
} }
@@ -141,30 +144,52 @@ class ExceptionCaster
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
if (isset($f['file'], $f['line'])) { if (isset($f['file'], $f['line'])) {
if (preg_match('/\((\d+)\)(?:\([\da-f]{32}\))? : (?:eval\(\)\'d code|runtime-created function)$/', $f['file'], $match)) { $cacheKey = $f;
$f['file'] = substr($f['file'], 0, -strlen($match[0])); unset($cacheKey['object'], $cacheKey['args']);
$f['line'] = (int) $match[1]; $cacheKey[] = self::$srcContext;
} $cacheKey = implode('-', $cacheKey);
if (file_exists($f['file']) && 0 <= self::$srcContext) {
$src[$f['file'].':'.$f['line']] = self::extractSource(explode("\n", file_get_contents($f['file'])), $f['line'], self::$srcContext);
if (!empty($f['class']) && is_subclass_of($f['class'], 'Twig_Template') && method_exists($f['class'], 'getDebugInfo')) { if (isset(self::$framesCache[$cacheKey])) {
$template = isset($f['object']) ? $f['object'] : new $f['class'](new \Twig_Environment(new \Twig_Loader_Filesystem())); $a[$prefix.'src'] = self::$framesCache[$cacheKey];
} else {
if (preg_match('/\((\d+)\)(?:\([\da-f]{32}\))? : (?:eval\(\)\'d code|runtime-created function)$/', $f['file'], $match)) {
$f['file'] = substr($f['file'], 0, -strlen($match[0]));
$f['line'] = (int) $match[1];
}
$caller = isset($f['function']) ? sprintf('in %s() on line %d', (isset($f['class']) ? $f['class'].$f['type'] : '').$f['function'], $f['line']) : null;
$src = $f['line'];
$srcKey = $f['file'];
$ellipsis = explode(DIRECTORY_SEPARATOR, $srcKey);
$ellipsis = 3 < count($ellipsis) ? 2 + strlen(implode(array_slice($ellipsis, -2))) : 0;
try { if (file_exists($f['file']) && 0 <= self::$srcContext) {
$templateName = $template->getTemplateName(); if (!empty($f['class']) && (is_subclass_of($f['class'], 'Twig\Template') || is_subclass_of($f['class'], 'Twig_Template')) && method_exists($f['class'], 'getDebugInfo')) {
$templateSrc = explode("\n", method_exists($template, 'getSource') ? $template->getSource() : $template->getEnvironment()->getLoader()->getSource($templateName)); $template = isset($f['object']) ? $f['object'] : unserialize(sprintf('O:%d:"%s":0:{}', strlen($f['class']), $f['class']));
$ellipsis = 0;
$templateSrc = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : '');
$templateInfo = $template->getDebugInfo(); $templateInfo = $template->getDebugInfo();
if (isset($templateInfo[$f['line']])) { if (isset($templateInfo[$f['line']])) {
$src[$templateName.':'.$templateInfo[$f['line']]] = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext); if (!method_exists($template, 'getSourceContext') || !file_exists($templatePath = $template->getSourceContext()->getPath())) {
$templatePath = null;
}
if ($templateSrc) {
$src = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext, $caller, 'twig', $templatePath);
$srcKey = ($templatePath ?: $template->getTemplateName()).':'.$templateInfo[$f['line']];
}
}
}
if ($srcKey == $f['file']) {
$src = self::extractSource(file_get_contents($f['file']), $f['line'], self::$srcContext, $caller, 'php', $f['file']);
$srcKey .= ':'.$f['line'];
if ($ellipsis) {
$ellipsis += 1 + strlen($f['line']);
} }
} catch (\Twig_Error_Loader $e) {
} }
} }
} else { $srcAttr = $ellipsis ? 'ellipsis='.$ellipsis : '';
$src[$f['file']] = $f['line']; self::$framesCache[$cacheKey] = $a[$prefix.'src'] = new EnumStub(array("\0~$srcAttr\0$srcKey" => $src));
} }
$a[$prefix.'src'] = new EnumStub($src);
} }
unset($a[$prefix.'args'], $a[$prefix.'line'], $a[$prefix.'file']); unset($a[$prefix.'args'], $a[$prefix.'line'], $a[$prefix.'file']);
@@ -176,8 +201,8 @@ class ExceptionCaster
unset($a[$k]); unset($a[$k]);
} }
} }
if ($frame->keepArgs && isset($f['args'])) { if ($frame->keepArgs && !empty($f['args'])) {
$a[$prefix.'args'] = $f['args']; $a[$prefix.'arguments'] = new ArgsStub($f['args'], $f['function'], $f['class']);
} }
return $a; return $a;
@@ -193,46 +218,81 @@ class ExceptionCaster
} }
if (!($filter & Caster::EXCLUDE_VERBOSE)) { if (!($filter & Caster::EXCLUDE_VERBOSE)) {
array_unshift($trace, array( if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) {
'function' => $xClass ? 'new '.$xClass : null, self::traceUnshift($trace, $xClass, $a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']);
'file' => $a[Caster::PREFIX_PROTECTED.'file'], }
'line' => $a[Caster::PREFIX_PROTECTED.'line'], $a[$xPrefix.'trace'] = new TraceStub($trace, self::$traceArgs);
));
$a[$xPrefix.'trace'] = new TraceStub($trace);
} }
if (empty($a[$xPrefix.'previous'])) { if (empty($a[$xPrefix.'previous'])) {
unset($a[$xPrefix.'previous']); unset($a[$xPrefix.'previous']);
} }
unset($a[$xPrefix.'string'], $a[Caster::PREFIX_DYNAMIC.'xdebug_message'], $a[Caster::PREFIX_DYNAMIC.'__destructorException']); unset($a[$xPrefix.'string'], $a[Caster::PREFIX_DYNAMIC.'xdebug_message'], $a[Caster::PREFIX_DYNAMIC.'__destructorException']);
if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) {
$a[Caster::PREFIX_PROTECTED.'file'] = new LinkStub($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']);
}
return $a; return $a;
} }
private static function extractSource(array $srcArray, $line, $srcContext) private static function traceUnshift(&$trace, $class, $file, $line)
{ {
if (isset($trace[0]['file'], $trace[0]['line']) && $trace[0]['file'] === $file && $trace[0]['line'] === $line) {
return;
}
array_unshift($trace, array(
'function' => $class ? 'new '.$class : null,
'file' => $file,
'line' => $line,
));
}
private static function extractSource($srcLines, $line, $srcContext, $title, $lang, $file = null)
{
$srcLines = explode("\n", $srcLines);
$src = array(); $src = array();
for ($i = $line - 1 - $srcContext; $i <= $line - 1 + $srcContext; ++$i) { for ($i = $line - 1 - $srcContext; $i <= $line - 1 + $srcContext; ++$i) {
$src[] = (isset($srcArray[$i]) ? $srcArray[$i] : '')."\n"; $src[] = (isset($srcLines[$i]) ? $srcLines[$i] : '')."\n";
} }
$srcLines = array();
$ltrim = 0; $ltrim = 0;
while (' ' === $src[0][$ltrim] || "\t" === $src[0][$ltrim]) { do {
$i = $srcContext << 1; $pad = null;
while ($i > 0 && $src[0][$ltrim] === $src[$i][$ltrim]) { for ($i = $srcContext << 1; $i >= 0; --$i) {
--$i; if (isset($src[$i][$ltrim]) && "\r" !== ($c = $src[$i][$ltrim]) && "\n" !== $c) {
} if (null === $pad) {
if ($i) { $pad = $c;
break; }
if ((' ' !== $c && "\t" !== $c) || $pad !== $c) {
break;
}
}
} }
++$ltrim; ++$ltrim;
} } while (0 > $i && null !== $pad);
if ($ltrim) {
foreach ($src as $i => $line) { --$ltrim;
$src[$i] = substr($line, $ltrim);
foreach ($src as $i => $c) {
if ($ltrim) {
$c = isset($c[$ltrim]) && "\r" !== $c[$ltrim] ? substr($c, $ltrim) : ltrim($c, " \t");
} }
$c = substr($c, 0, -1);
if ($i !== $srcContext) {
$c = new ConstStub('default', $c);
} else {
$c = new ConstStub($c, $title);
if (null !== $file) {
$c->attr['file'] = $file;
$c->attr['line'] = $line;
}
}
$c->attr['lang'] = $lang;
$srcLines[sprintf("\0~%d\0", $i + $line - $srcContext)] = $c;
} }
return implode('', $src); return new EnumStub($srcLines);
} }
} }

View File

@@ -0,0 +1,51 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarDumper\Caster;
/**
* Represents a file or a URL.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class LinkStub extends ConstStub
{
public function __construct($label, $line = 0, $href = null)
{
$this->value = $label;
if (null === $href) {
$href = $label;
}
if (is_string($href)) {
if (0 === strpos($href, 'file://')) {
if ($href === $label) {
$label = substr($label, 7);
}
$href = substr($href, 7);
} elseif (false !== strpos($href, '://')) {
$this->attr['href'] = $href;
return;
}
if (file_exists($href)) {
if ($line) {
$this->attr['line'] = $line;
}
$this->attr['file'] = realpath($href) ?: $href;
if ($this->attr['file'] === $label && 3 < count($ellipsis = explode(DIRECTORY_SEPARATOR, $href))) {
$this->attr['ellipsis'] = 2 + strlen(implode(array_slice($ellipsis, -2)));
}
}
}
}
}

View File

@@ -77,6 +77,12 @@ class PdoCaster
} catch (\Exception $e) { } catch (\Exception $e) {
} }
} }
if (isset($attr[$k = 'STATEMENT_CLASS'][1])) {
if ($attr[$k][1]) {
$attr[$k][1] = new ArgsStub($attr[$k][1], '__construct', $attr[$k][0]);
}
$attr[$k][0] = new ClassStub($attr[$k][0]);
}
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
$a += array( $a += array(

View File

@@ -0,0 +1,77 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarDumper\Caster;
use Symfony\Component\VarDumper\Cloner\Stub;
/**
* Casts Redis class from ext-redis to array representation.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class RedisCaster
{
private static $serializer = array(
\Redis::SERIALIZER_NONE => 'NONE',
\Redis::SERIALIZER_PHP => 'PHP',
2 => 'IGBINARY', // Optional Redis::SERIALIZER_IGBINARY
);
public static function castRedis(\Redis $c, array $a, Stub $stub, $isNested)
{
$prefix = Caster::PREFIX_VIRTUAL;
if (defined('HHVM_VERSION_ID')) {
if (isset($a[Caster::PREFIX_PROTECTED.'serializer'])) {
$ser = $a[Caster::PREFIX_PROTECTED.'serializer'];
$a[Caster::PREFIX_PROTECTED.'serializer'] = isset(self::$serializer[$ser]) ? new ConstStub(self::$serializer[$ser], $ser) : $ser;
}
return $a;
}
if (!$connected = $c->isConnected()) {
return $a + array(
$prefix.'isConnected' => $connected,
);
}
$ser = $c->getOption(\Redis::OPT_SERIALIZER);
$retry = defined('Redis::OPT_SCAN') ? $c->getOption(\Redis::OPT_SCAN) : 0;
return $a + array(
$prefix.'isConnected' => $connected,
$prefix.'host' => $c->getHost(),
$prefix.'port' => $c->getPort(),
$prefix.'auth' => $c->getAuth(),
$prefix.'dbNum' => $c->getDbNum(),
$prefix.'timeout' => $c->getTimeout(),
$prefix.'persistentId' => $c->getPersistentID(),
$prefix.'options' => new EnumStub(array(
'READ_TIMEOUT' => $c->getOption(\Redis::OPT_READ_TIMEOUT),
'SERIALIZER' => isset(self::$serializer[$ser]) ? new ConstStub(self::$serializer[$ser], $ser) : $ser,
'PREFIX' => $c->getOption(\Redis::OPT_PREFIX),
'SCAN' => new ConstStub($retry ? 'RETRY' : 'NORETRY', $retry),
)),
);
}
public static function castRedisArray(\RedisArray $c, array $a, Stub $stub, $isNested)
{
$prefix = Caster::PREFIX_VIRTUAL;
return $a + array(
$prefix.'hosts' => $c->_hosts(),
$prefix.'function' => ClassStub::wrapCallable($c->_function()),
);
}
}

View File

@@ -53,19 +53,32 @@ class ReflectionCaster
} }
if ($f = $c->getFileName()) { if ($f = $c->getFileName()) {
$a[$prefix.'file'] = $f; $a[$prefix.'file'] = new LinkStub($f, $c->getStartLine());
$a[$prefix.'line'] = $c->getStartLine().' to '.$c->getEndLine(); $a[$prefix.'line'] = $c->getStartLine().' to '.$c->getEndLine();
} }
$prefix = Caster::PREFIX_DYNAMIC; $prefix = Caster::PREFIX_DYNAMIC;
unset($a['name'], $a[$prefix.'0'], $a[$prefix.'this'], $a[$prefix.'parameter'], $a[Caster::PREFIX_VIRTUAL.'extra']); unset($a['name'], $a[$prefix.'this'], $a[$prefix.'parameter'], $a[Caster::PREFIX_VIRTUAL.'extra']);
return $a; return $a;
} }
public static function castGenerator(\Generator $c, array $a, Stub $stub, $isNested) public static function castGenerator(\Generator $c, array $a, Stub $stub, $isNested)
{ {
return class_exists('ReflectionGenerator', false) ? self::castReflectionGenerator(new \ReflectionGenerator($c), $a, $stub, $isNested) : $a; if (!class_exists('ReflectionGenerator', false)) {
return $a;
}
// Cannot create ReflectionGenerator based on a terminated Generator
try {
$reflectionGenerator = new \ReflectionGenerator($c);
} catch (\Exception $e) {
$a[Caster::PREFIX_VIRTUAL.'closed'] = true;
return $a;
}
return self::castReflectionGenerator($reflectionGenerator, $a, $stub, $isNested);
} }
public static function castType(\ReflectionType $c, array $a, Stub $stub, $isNested) public static function castType(\ReflectionType $c, array $a, Stub $stub, $isNested)
@@ -73,7 +86,7 @@ class ReflectionCaster
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
$a += array( $a += array(
$prefix.'type' => $c->__toString(), $prefix.'name' => $c instanceof \ReflectionNamedType ? $c->getName() : $c->__toString(),
$prefix.'allowsNull' => $c->allowsNull(), $prefix.'allowsNull' => $c->allowsNull(),
$prefix.'isBuiltin' => $c->isBuiltin(), $prefix.'isBuiltin' => $c->isBuiltin(),
); );
@@ -88,31 +101,33 @@ class ReflectionCaster
if ($c->getThis()) { if ($c->getThis()) {
$a[$prefix.'this'] = new CutStub($c->getThis()); $a[$prefix.'this'] = new CutStub($c->getThis());
} }
$x = $c->getFunction(); $function = $c->getFunction();
$frame = array( $frame = array(
'class' => isset($x->class) ? $x->class : null, 'class' => isset($function->class) ? $function->class : null,
'type' => isset($x->class) ? ($x->isStatic() ? '::' : '->') : null, 'type' => isset($function->class) ? ($function->isStatic() ? '::' : '->') : null,
'function' => $x->name, 'function' => $function->name,
'file' => $c->getExecutingFile(), 'file' => $c->getExecutingFile(),
'line' => $c->getExecutingLine(), 'line' => $c->getExecutingLine(),
); );
if ($trace = $c->getTrace(DEBUG_BACKTRACE_IGNORE_ARGS)) { if ($trace = $c->getTrace(DEBUG_BACKTRACE_IGNORE_ARGS)) {
$x = new \ReflectionGenerator($c->getExecutingGenerator()); $function = new \ReflectionGenerator($c->getExecutingGenerator());
array_unshift($trace, array( array_unshift($trace, array(
'function' => 'yield', 'function' => 'yield',
'file' => $x->getExecutingFile(), 'file' => $function->getExecutingFile(),
'line' => $x->getExecutingLine() - 1, 'line' => $function->getExecutingLine() - 1,
)); ));
$trace[] = $frame; $trace[] = $frame;
$a[$prefix.'trace'] = new TraceStub($trace, false, 0, -1, -1); $a[$prefix.'trace'] = new TraceStub($trace, false, 0, -1, -1);
} else { } else {
$x = new FrameStub($frame, false, true); $function = new FrameStub($frame, false, true);
$x = ExceptionCaster::castFrameStub($x, array(), $x, true); $function = ExceptionCaster::castFrameStub($function, array(), $function, true);
$a[$prefix.'executing'] = new EnumStub(array( $a[$prefix.'executing'] = new EnumStub(array(
$frame['class'].$frame['type'].$frame['function'].'()' => $x[$prefix.'src'], $frame['class'].$frame['type'].$frame['function'].'()' => $function[$prefix.'src'],
)); ));
} }
$a[Caster::PREFIX_VIRTUAL.'closed'] = false;
return $a; return $a;
} }
@@ -157,7 +172,12 @@ class ReflectionCaster
)); ));
if (isset($a[$prefix.'returnType'])) { if (isset($a[$prefix.'returnType'])) {
$a[$prefix.'returnType'] = (string) $a[$prefix.'returnType']; $v = $a[$prefix.'returnType'];
$v = $v instanceof \ReflectionNamedType ? $v->getName() : $v->__toString();
$a[$prefix.'returnType'] = new ClassStub($a[$prefix.'returnType']->allowsNull() ? '?'.$v : $v, array(class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', ''));
}
if (isset($a[$prefix.'class'])) {
$a[$prefix.'class'] = new ClassStub($a[$prefix.'class']);
} }
if (isset($a[$prefix.'this'])) { if (isset($a[$prefix.'this'])) {
$a[$prefix.'this'] = new CutStub($a[$prefix.'this']); $a[$prefix.'this'] = new CutStub($a[$prefix.'this']);
@@ -165,12 +185,12 @@ class ReflectionCaster
foreach ($c->getParameters() as $v) { foreach ($c->getParameters() as $v) {
$k = '$'.$v->name; $k = '$'.$v->name;
if ($v->isPassedByReference()) {
$k = '&'.$k;
}
if (method_exists($v, 'isVariadic') && $v->isVariadic()) { if (method_exists($v, 'isVariadic') && $v->isVariadic()) {
$k = '...'.$k; $k = '...'.$k;
} }
if ($v->isPassedByReference()) {
$k = '&'.$k;
}
$a[$prefix.'parameters'][$k] = $v; $a[$prefix.'parameters'][$k] = $v;
} }
if (isset($a[$prefix.'parameters'])) { if (isset($a[$prefix.'parameters'])) {
@@ -213,24 +233,22 @@ class ReflectionCaster
'position' => 'getPosition', 'position' => 'getPosition',
'isVariadic' => 'isVariadic', 'isVariadic' => 'isVariadic',
'byReference' => 'isPassedByReference', 'byReference' => 'isPassedByReference',
'allowsNull' => 'allowsNull',
)); ));
try { if (method_exists($c, 'getType')) {
if (method_exists($c, 'hasType')) { if ($v = $c->getType()) {
if ($c->hasType()) { $a[$prefix.'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : $v->__toString();
$a[$prefix.'typeHint'] = $c->getType()->__toString();
}
} elseif ($c->isArray()) {
$a[$prefix.'typeHint'] = 'array';
} elseif (method_exists($c, 'isCallable') && $c->isCallable()) {
$a[$prefix.'typeHint'] = 'callable';
} elseif ($v = $c->getClass()) {
$a[$prefix.'typeHint'] = $v->name;
}
} catch (\ReflectionException $e) {
if (preg_match('/^Class ([^ ]++) does not exist$/', $e->getMessage(), $m)) {
$a[$prefix.'typeHint'] = $m[1];
} }
} elseif (preg_match('/^(?:[^ ]++ ){4}([a-zA-Z_\x7F-\xFF][^ ]++)/', $c, $v)) {
$a[$prefix.'typeHint'] = $v[1];
}
if (isset($a[$prefix.'typeHint'])) {
$v = $a[$prefix.'typeHint'];
$a[$prefix.'typeHint'] = new ClassStub($v, array(class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', ''));
} else {
unset($a[$prefix.'allowsNull']);
} }
try { try {
@@ -238,9 +256,13 @@ class ReflectionCaster
if (method_exists($c, 'isDefaultValueConstant') && $c->isDefaultValueConstant()) { if (method_exists($c, 'isDefaultValueConstant') && $c->isDefaultValueConstant()) {
$a[$prefix.'default'] = new ConstStub($c->getDefaultValueConstantName(), $v); $a[$prefix.'default'] = new ConstStub($c->getDefaultValueConstantName(), $v);
} }
if (null === $v) {
unset($a[$prefix.'allowsNull']);
}
} catch (\ReflectionException $e) { } catch (\ReflectionException $e) {
if (isset($a[$prefix.'typeHint']) && $c->allowsNull()) { if (isset($a[$prefix.'typeHint']) && $c->allowsNull() && !class_exists('ReflectionNamedType', false)) {
$a[$prefix.'default'] = null; $a[$prefix.'default'] = null;
unset($a[$prefix.'allowsNull']);
} }
} }
@@ -288,7 +310,7 @@ class ReflectionCaster
$x = isset($a[Caster::PREFIX_VIRTUAL.'extra']) ? $a[Caster::PREFIX_VIRTUAL.'extra']->value : array(); $x = isset($a[Caster::PREFIX_VIRTUAL.'extra']) ? $a[Caster::PREFIX_VIRTUAL.'extra']->value : array();
if (method_exists($c, 'getFileName') && $m = $c->getFileName()) { if (method_exists($c, 'getFileName') && $m = $c->getFileName()) {
$x['file'] = $m; $x['file'] = new LinkStub($m, $c->getStartLine());
$x['line'] = $c->getStartLine().' to '.$c->getEndLine(); $x['line'] = $c->getStartLine().' to '.$c->getEndLine();
} }

View File

@@ -40,12 +40,17 @@ class ResourceCaster
public static function castStream($stream, array $a, Stub $stub, $isNested) public static function castStream($stream, array $a, Stub $stub, $isNested)
{ {
return stream_get_meta_data($stream) + static::castStreamContext($stream, $a, $stub, $isNested); $a = stream_get_meta_data($stream) + static::castStreamContext($stream, $a, $stub, $isNested);
if (isset($a['uri'])) {
$a['uri'] = new LinkStub($a['uri']);
}
return $a;
} }
public static function castStreamContext($stream, array $a, Stub $stub, $isNested) public static function castStreamContext($stream, array $a, Stub $stub, $isNested)
{ {
return stream_context_get_params($stream); return @stream_context_get_params($stream) ?: $a;
} }
public static function castGd($gd, array $a, Stub $stub, $isNested) public static function castGd($gd, array $a, Stub $stub, $isNested)

View File

@@ -36,7 +36,7 @@ class SplCaster
$b = array( $b = array(
$prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST), $prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST),
$prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS), $prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS),
$prefix.'iteratorClass' => $c->getIteratorClass(), $prefix.'iteratorClass' => new ClassStub($c->getIteratorClass()),
$prefix.'storage' => $c->getArrayCopy(), $prefix.'storage' => $c->getArrayCopy(),
); );
@@ -71,7 +71,7 @@ class SplCaster
$c->setIteratorMode(\SplDoublyLinkedList::IT_MODE_KEEP | $mode & ~\SplDoublyLinkedList::IT_MODE_DELETE); $c->setIteratorMode(\SplDoublyLinkedList::IT_MODE_KEEP | $mode & ~\SplDoublyLinkedList::IT_MODE_DELETE);
$a += array( $a += array(
$prefix.'mode' => new ConstStub((($mode & \SplDoublyLinkedList::IT_MODE_LIFO) ? 'IT_MODE_LIFO' : 'IT_MODE_FIFO').' | '.(($mode & \SplDoublyLinkedList::IT_MODE_KEEP) ? 'IT_MODE_KEEP' : 'IT_MODE_DELETE'), $mode), $prefix.'mode' => new ConstStub((($mode & \SplDoublyLinkedList::IT_MODE_LIFO) ? 'IT_MODE_LIFO' : 'IT_MODE_FIFO').' | '.(($mode & \SplDoublyLinkedList::IT_MODE_DELETE) ? 'IT_MODE_DELETE' : 'IT_MODE_KEEP'), $mode),
$prefix.'dllist' => iterator_to_array($c), $prefix.'dllist' => iterator_to_array($c),
); );
$c->setIteratorMode($mode); $c->setIteratorMode($mode);
@@ -115,6 +115,10 @@ class SplCaster
} }
} }
if (isset($a[$prefix.'realPath'])) {
$a[$prefix.'realPath'] = new LinkStub($a[$prefix.'realPath']);
}
if (isset($a[$prefix.'perms'])) { if (isset($a[$prefix.'perms'])) {
$a[$prefix.'perms'] = new ConstStub(sprintf('0%o', $a[$prefix.'perms']), $a[$prefix.'perms']); $a[$prefix.'perms'] = new ConstStub(sprintf('0%o', $a[$prefix.'perms']), $a[$prefix.'perms']);
} }
@@ -180,7 +184,7 @@ class SplCaster
$storage = array(); $storage = array();
unset($a[Caster::PREFIX_DYNAMIC."\0gcdata"]); // Don't hit https://bugs.php.net/65967 unset($a[Caster::PREFIX_DYNAMIC."\0gcdata"]); // Don't hit https://bugs.php.net/65967
foreach ($c as $obj) { foreach (clone $c as $obj) {
$storage[spl_object_hash($obj)] = array( $storage[spl_object_hash($obj)] = array(
'object' => $obj, 'object' => $obj,
'info' => $c->getInfo(), 'info' => $c->getInfo(),

View File

@@ -28,9 +28,17 @@ class StubCaster
$stub->value = $c->value; $stub->value = $c->value;
$stub->handle = $c->handle; $stub->handle = $c->handle;
$stub->cut = $c->cut; $stub->cut = $c->cut;
$stub->attr = $c->attr;
return array(); if (Stub::TYPE_REF === $c->type && !$c->class && is_string($c->value) && !preg_match('//u', $c->value)) {
$stub->type = Stub::TYPE_STRING;
$stub->class = Stub::STRING_BINARY;
}
$a = array();
} }
return $a;
} }
public static function castCutArray(CutArrayStub $c, array $a, Stub $stub, $isNested) public static function castCutArray(CutArrayStub $c, array $a, Stub $stub, $isNested)
@@ -52,15 +60,17 @@ class StubCaster
public static function castEnum(EnumStub $c, array $a, Stub $stub, $isNested) public static function castEnum(EnumStub $c, array $a, Stub $stub, $isNested)
{ {
if ($isNested) { if ($isNested) {
$stub->class = ''; $stub->class = $c->dumpKeys ? '' : null;
$stub->handle = 0; $stub->handle = 0;
$stub->value = null; $stub->value = null;
$stub->cut = $c->cut;
$stub->attr = $c->attr;
$a = array(); $a = array();
if ($c->value) { if ($c->value) {
foreach (array_keys($c->value) as $k) { foreach (array_keys($c->value) as $k) {
$keys[] = Caster::PREFIX_VIRTUAL.$k; $keys[] = !isset($k[0]) || "\0" !== $k[0] ? Caster::PREFIX_VIRTUAL.$k : $k;
} }
// Preserve references with array_combine() // Preserve references with array_combine()
$a = array_combine($keys, $c->value); $a = array_combine($keys, $c->value);

View File

@@ -0,0 +1,77 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarDumper\Caster;
use Symfony\Component\VarDumper\Cloner\Stub;
/**
* Casts XmlReader class to array representation.
*
* @author Baptiste Clavié <clavie.b@gmail.com>
*/
class XmlReaderCaster
{
private static $nodeTypes = array(
\XmlReader::NONE => 'NONE',
\XmlReader::ELEMENT => 'ELEMENT',
\XmlReader::ATTRIBUTE => 'ATTRIBUTE',
\XmlReader::TEXT => 'TEXT',
\XmlReader::CDATA => 'CDATA',
\XmlReader::ENTITY_REF => 'ENTITY_REF',
\XmlReader::ENTITY => 'ENTITY',
\XmlReader::PI => 'PI (Processing Instruction)',
\XmlReader::COMMENT => 'COMMENT',
\XmlReader::DOC => 'DOC',
\XmlReader::DOC_TYPE => 'DOC_TYPE',
\XmlReader::DOC_FRAGMENT => 'DOC_FRAGMENT',
\XmlReader::NOTATION => 'NOTATION',
\XmlReader::WHITESPACE => 'WHITESPACE',
\XmlReader::SIGNIFICANT_WHITESPACE => 'SIGNIFICANT_WHITESPACE',
\XmlReader::END_ELEMENT => 'END_ELEMENT',
\XmlReader::END_ENTITY => 'END_ENTITY',
\XmlReader::XML_DECLARATION => 'XML_DECLARATION',
);
public static function castXmlReader(\XmlReader $reader, array $a, Stub $stub, $isNested)
{
$props = Caster::PREFIX_VIRTUAL.'parserProperties';
$info = array(
'localName' => $reader->localName,
'prefix' => $reader->prefix,
'nodeType' => new ConstStub(self::$nodeTypes[$reader->nodeType], $reader->nodeType),
'depth' => $reader->depth,
'isDefault' => $reader->isDefault,
'isEmptyElement' => \XmlReader::NONE === $reader->nodeType ? null : $reader->isEmptyElement,
'xmlLang' => $reader->xmlLang,
'attributeCount' => $reader->attributeCount,
'value' => $reader->value,
'namespaceURI' => $reader->namespaceURI,
'baseURI' => $reader->baseURI ? new LinkStub($reader->baseURI) : $reader->baseURI,
$props => array(
'LOADDTD' => $reader->getParserProperty(\XmlReader::LOADDTD),
'DEFAULTATTRS' => $reader->getParserProperty(\XmlReader::DEFAULTATTRS),
'VALIDATE' => $reader->getParserProperty(\XmlReader::VALIDATE),
'SUBST_ENTITIES' => $reader->getParserProperty(\XmlReader::SUBST_ENTITIES),
),
);
if ($info[$props] = Caster::filter($info[$props], Caster::EXCLUDE_EMPTY, array(), $count)) {
$info[$props] = new EnumStub($info[$props]);
$info[$props]->cut = $count;
}
$info = Caster::filter($info, Caster::EXCLUDE_EMPTY, array(), $count);
// +2 because hasValue and hasAttributes are always filtered
$stub->cut += $count + 2;
return $a + $info;
}
}

View File

@@ -22,6 +22,8 @@ use Symfony\Component\VarDumper\Exception\ThrowingCasterException;
abstract class AbstractCloner implements ClonerInterface abstract class AbstractCloner implements ClonerInterface
{ {
public static $defaultCasters = array( public static $defaultCasters = array(
'__PHP_Incomplete_Class' => 'Symfony\Component\VarDumper\Caster\Caster::castPhpIncompleteClass',
'Symfony\Component\VarDumper\Caster\CutStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castStub', '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\CutArrayStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castCutArray',
'Symfony\Component\VarDumper\Caster\ConstStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castStub', 'Symfony\Component\VarDumper\Caster\ConstStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castStub',
@@ -67,6 +69,8 @@ abstract class AbstractCloner implements ClonerInterface
'DOMProcessingInstruction' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castProcessingInstruction', 'DOMProcessingInstruction' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castProcessingInstruction',
'DOMXPath' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castXPath', 'DOMXPath' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castXPath',
'XmlReader' => 'Symfony\Component\VarDumper\Caster\XmlReaderCaster::castXmlReader',
'ErrorException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castErrorException', 'ErrorException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castErrorException',
'Exception' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castException', 'Exception' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castException',
'Error' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castError', 'Error' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castError',
@@ -100,6 +104,9 @@ abstract class AbstractCloner implements ClonerInterface
'MongoCursorInterface' => 'Symfony\Component\VarDumper\Caster\MongoCaster::castCursor', 'MongoCursorInterface' => 'Symfony\Component\VarDumper\Caster\MongoCaster::castCursor',
'Redis' => 'Symfony\Component\VarDumper\Caster\RedisCaster::castRedis',
'RedisArray' => 'Symfony\Component\VarDumper\Caster\RedisCaster::castRedisArray',
':curl' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castCurl', ':curl' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castCurl',
':dba' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba', ':dba' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba',
':dba persistent' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba', ':dba persistent' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba',
@@ -111,6 +118,7 @@ abstract class AbstractCloner implements ClonerInterface
':pgsql result' => 'Symfony\Component\VarDumper\Caster\PgSqlCaster::castResult', ':pgsql result' => 'Symfony\Component\VarDumper\Caster\PgSqlCaster::castResult',
':process' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castProcess', ':process' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castProcess',
':stream' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStream', ':stream' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStream',
':persistent stream' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStream',
':stream-context' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStreamContext', ':stream-context' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStreamContext',
':xml' => 'Symfony\Component\VarDumper\Caster\XmlResourceCaster::castXml', ':xml' => 'Symfony\Component\VarDumper\Caster\XmlResourceCaster::castXml',
); );
@@ -125,7 +133,7 @@ abstract class AbstractCloner implements ClonerInterface
private $filter = 0; private $filter = 0;
/** /**
* @param callable[]|null $casters A map of casters. * @param callable[]|null $casters A map of casters
* *
* @see addCasters * @see addCasters
*/ */
@@ -146,7 +154,7 @@ abstract class AbstractCloner implements ClonerInterface
* Resource types are to be prefixed with a `:`, * Resource types are to be prefixed with a `:`,
* see e.g. static::$defaultCasters. * see e.g. static::$defaultCasters.
* *
* @param callable[] $casters A map of casters. * @param callable[] $casters A map of casters
*/ */
public function addCasters(array $casters) public function addCasters(array $casters)
{ {
@@ -178,10 +186,10 @@ abstract class AbstractCloner implements ClonerInterface
/** /**
* Clones a PHP variable. * Clones a PHP variable.
* *
* @param mixed $var Any PHP variable. * @param mixed $var Any PHP variable
* @param int $filter A bit field of Caster::EXCLUDE_* constants. * @param int $filter A bit field of Caster::EXCLUDE_* constants
* *
* @return Data The cloned variable represented by a Data object. * @return Data The cloned variable represented by a Data object
*/ */
public function cloneVar($var, $filter = 0) public function cloneVar($var, $filter = 0)
{ {
@@ -216,19 +224,19 @@ abstract class AbstractCloner implements ClonerInterface
/** /**
* Effectively clones the PHP variable. * Effectively clones the PHP variable.
* *
* @param mixed $var Any PHP variable. * @param mixed $var Any PHP variable
* *
* @return array The cloned variable represented in an array. * @return array The cloned variable represented in an array
*/ */
abstract protected function doClone($var); abstract protected function doClone($var);
/** /**
* Casts an object to an array representation. * Casts an object to an array representation.
* *
* @param Stub $stub The Stub for the casted object. * @param Stub $stub The Stub for the casted object
* @param bool $isNested True if the object is nested in the dumped structure. * @param bool $isNested True if the object is nested in the dumped structure
* *
* @return array The object casted as array. * @return array The object casted as array
*/ */
protected function castObject(Stub $stub, $isNested) protected function castObject(Stub $stub, $isNested)
{ {
@@ -245,14 +253,15 @@ abstract class AbstractCloner implements ClonerInterface
new \ReflectionClass($class), new \ReflectionClass($class),
array_reverse(array($class => $class) + class_parents($class) + class_implements($class) + array('*' => '*')), array_reverse(array($class => $class) + class_parents($class) + class_implements($class) + array('*' => '*')),
); );
$classInfo[1] = array_map('strtolower', $classInfo[1]);
$this->classInfo[$class] = $classInfo; $this->classInfo[$class] = $classInfo;
} }
$a = $this->callCaster('Symfony\Component\VarDumper\Caster\Caster::castObject', $obj, $classInfo[0], null, $isNested); $a = Caster::castObject($obj, $classInfo[0]);
foreach ($classInfo[1] as $p) { foreach ($classInfo[1] as $p) {
if (!empty($this->casters[$p = strtolower($p)])) { if (!empty($this->casters[$p])) {
foreach ($this->casters[$p] as $p) { foreach ($this->casters[$p] as $p) {
$a = $this->callCaster($p, $obj, $a, $stub, $isNested); $a = $this->callCaster($p, $obj, $a, $stub, $isNested);
} }
@@ -265,10 +274,10 @@ abstract class AbstractCloner implements ClonerInterface
/** /**
* Casts a resource to an array representation. * Casts a resource to an array representation.
* *
* @param Stub $stub The Stub for the casted resource. * @param Stub $stub The Stub for the casted resource
* @param bool $isNested True if the object is nested in the dumped structure. * @param bool $isNested True if the object is nested in the dumped structure
* *
* @return array The resource casted as array. * @return array The resource casted as array
*/ */
protected function castResource(Stub $stub, $isNested) protected function castResource(Stub $stub, $isNested)
{ {
@@ -288,13 +297,13 @@ abstract class AbstractCloner implements ClonerInterface
/** /**
* Calls a custom caster. * Calls a custom caster.
* *
* @param callable $callback The caster. * @param callable $callback The caster
* @param object|resource $obj The object/resource being casted. * @param object|resource $obj The object/resource being casted
* @param array $a The result of the previous cast for chained casters. * @param array $a The result of the previous cast for chained casters
* @param Stub $stub The Stub for the casted object/resource. * @param Stub $stub The Stub for the casted object/resource
* @param bool $isNested True if $obj is nested in the dumped structure. * @param bool $isNested True if $obj is nested in the dumped structure
* *
* @return array The casted object/resource. * @return array The casted object/resource
*/ */
private function callCaster($callback, $obj, $a, $stub, $isNested) private function callCaster($callback, $obj, $a, $stub, $isNested)
{ {
@@ -305,7 +314,7 @@ abstract class AbstractCloner implements ClonerInterface
$a = $cast; $a = $cast;
} }
} catch (\Exception $e) { } catch (\Exception $e) {
$a[(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠'] = new ThrowingCasterException($e); $a = array((Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)) + $a;
} }
return $a; return $a;

View File

@@ -19,9 +19,9 @@ interface ClonerInterface
/** /**
* Clones a PHP variable. * Clones a PHP variable.
* *
* @param mixed $var Any PHP variable. * @param mixed $var Any PHP variable
* *
* @return Data The cloned variable represented by a Data object. * @return Data The cloned variable represented by a Data object
*/ */
public function cloneVar($var); public function cloneVar($var);
} }

View File

@@ -38,4 +38,5 @@ class Cursor
public $hashLength = 0; public $hashLength = 0;
public $hashCut = 0; public $hashCut = 0;
public $stop = false; public $stop = false;
public $attr = array();
} }

View File

@@ -11,18 +11,22 @@
namespace Symfony\Component\VarDumper\Cloner; namespace Symfony\Component\VarDumper\Cloner;
use Symfony\Component\VarDumper\Caster\Caster;
/** /**
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*/ */
class Data class Data
{ {
private $data; private $data;
private $position = 0;
private $key = 0;
private $maxDepth = 20; private $maxDepth = 20;
private $maxItemsPerDepth = -1; private $maxItemsPerDepth = -1;
private $useRefHandles = -1; private $useRefHandles = -1;
/** /**
* @param array $data A array as returned by ClonerInterface::cloneVar(). * @param array $data A array as returned by ClonerInterface::cloneVar()
*/ */
public function __construct(array $data) public function __construct(array $data)
{ {
@@ -30,7 +34,7 @@ class Data
} }
/** /**
* @return array The raw data structure. * @return array The raw data structure
*/ */
public function getRawData() public function getRawData()
{ {
@@ -40,9 +44,9 @@ class Data
/** /**
* Returns a depth limited clone of $this. * Returns a depth limited clone of $this.
* *
* @param int $maxDepth The max dumped depth level. * @param int $maxDepth The max dumped depth level
* *
* @return self A clone of $this. * @return self A clone of $this
*/ */
public function withMaxDepth($maxDepth) public function withMaxDepth($maxDepth)
{ {
@@ -53,11 +57,11 @@ class Data
} }
/** /**
* Limits the numbers of elements per depth level. * Limits the number of elements per depth level.
* *
* @param int $maxItemsPerDepth The max number of items dumped per depth level. * @param int $maxItemsPerDepth The max number of items dumped per depth level
* *
* @return self A clone of $this. * @return self A clone of $this
*/ */
public function withMaxItemsPerDepth($maxItemsPerDepth) public function withMaxItemsPerDepth($maxItemsPerDepth)
{ {
@@ -70,9 +74,9 @@ class Data
/** /**
* Enables/disables objects' identifiers tracking. * Enables/disables objects' identifiers tracking.
* *
* @param bool $useRefHandles False to hide global ref. handles. * @param bool $useRefHandles False to hide global ref. handles
* *
* @return self A clone of $this. * @return self A clone of $this
*/ */
public function withRefHandles($useRefHandles) public function withRefHandles($useRefHandles)
{ {
@@ -82,22 +86,66 @@ class Data
return $data; return $data;
} }
/**
* Seeks to a specific key in nested data structures.
*
* @param string|int $key The key to seek to
*
* @return self|null A clone of $this of null if the key is not set
*/
public function seek($key)
{
$item = $this->data[$this->position][$this->key];
if (!$item instanceof Stub || !$item->position) {
return;
}
$keys = array($key);
switch ($item->type) {
case Stub::TYPE_OBJECT:
$keys[] = Caster::PREFIX_DYNAMIC.$key;
$keys[] = Caster::PREFIX_PROTECTED.$key;
$keys[] = Caster::PREFIX_VIRTUAL.$key;
$keys[] = "\0$item->class\0$key";
case Stub::TYPE_ARRAY:
case Stub::TYPE_RESOURCE:
break;
default:
return;
}
$data = null;
$children = $this->data[$item->position];
foreach ($keys as $key) {
if (isset($children[$key]) || array_key_exists($key, $children)) {
$data = clone $this;
$data->key = $key;
$data->position = $item->position;
break;
}
}
return $data;
}
/** /**
* Dumps data with a DumperInterface dumper. * Dumps data with a DumperInterface dumper.
*/ */
public function dump(DumperInterface $dumper) public function dump(DumperInterface $dumper)
{ {
$refs = array(0); $refs = array(0);
$this->dumpItem($dumper, new Cursor(), $refs, $this->data[0][0]); $this->dumpItem($dumper, new Cursor(), $refs, $this->data[$this->position][$this->key]);
} }
/** /**
* Depth-first dumping of items. * Depth-first dumping of items.
* *
* @param DumperInterface $dumper The dumper being used for dumping. * @param DumperInterface $dumper The dumper being used for dumping
* @param Cursor $cursor A cursor used for tracking dumper state position. * @param Cursor $cursor A cursor used for tracking dumper state position
* @param array &$refs A map of all references discovered while dumping. * @param array &$refs A map of all references discovered while dumping
* @param mixed $item A Stub object or the original value being dumped. * @param mixed $item A Stub object or the original value being dumped
*/ */
private function dumpItem($dumper, $cursor, &$refs, $item) private function dumpItem($dumper, $cursor, &$refs, $item)
{ {
@@ -107,6 +155,7 @@ class Data
$firstSeen = true; $firstSeen = true;
if (!$item instanceof Stub) { if (!$item instanceof Stub) {
$cursor->attr = array();
$type = gettype($item); $type = gettype($item);
} elseif (Stub::TYPE_REF === $item->type) { } elseif (Stub::TYPE_REF === $item->type) {
if ($item->handle) { if ($item->handle) {
@@ -119,6 +168,7 @@ class Data
$cursor->hardRefHandle = $this->useRefHandles & $item->handle; $cursor->hardRefHandle = $this->useRefHandles & $item->handle;
$cursor->hardRefCount = $item->refCount; $cursor->hardRefCount = $item->refCount;
} }
$cursor->attr = $item->attr;
$type = $item->class ?: gettype($item->value); $type = $item->class ?: gettype($item->value);
$item = $item->value; $item = $item->value;
} }
@@ -133,6 +183,7 @@ class Data
} }
$cursor->softRefHandle = $this->useRefHandles & $item->handle; $cursor->softRefHandle = $this->useRefHandles & $item->handle;
$cursor->softRefCount = $item->refCount; $cursor->softRefCount = $item->refCount;
$cursor->attr = $item->attr;
$cut = $item->cut; $cut = $item->cut;
if ($item->position && $firstSeen) { if ($item->position && $firstSeen) {
@@ -162,7 +213,7 @@ class Data
$withChildren = $children && $cursor->depth !== $this->maxDepth && $this->maxItemsPerDepth; $withChildren = $children && $cursor->depth !== $this->maxDepth && $this->maxItemsPerDepth;
$dumper->enterHash($cursor, $item->type, $item->class, $withChildren); $dumper->enterHash($cursor, $item->type, $item->class, $withChildren);
if ($withChildren) { if ($withChildren) {
$cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type); $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type, null !== $item->class);
} elseif ($children && 0 <= $cut) { } elseif ($children && 0 <= $cut) {
$cut += count($children); $cut += count($children);
} }
@@ -186,15 +237,16 @@ class Data
* Dumps children of hash structures. * Dumps children of hash structures.
* *
* @param DumperInterface $dumper * @param DumperInterface $dumper
* @param Cursor $parentCursor The cursor of the parent hash. * @param Cursor $parentCursor The cursor of the parent hash
* @param array &$refs A map of all references discovered while dumping. * @param array &$refs A map of all references discovered while dumping
* @param array $children The children to dump. * @param array $children The children to dump
* @param int $hashCut The number of items removed from the original hash. * @param int $hashCut The number of items removed from the original hash
* @param string $hashType A Cursor::HASH_* const. * @param string $hashType A Cursor::HASH_* const
* @param bool $dumpKeys Whether keys should be dumped or not
* *
* @return int The final number of removed items. * @return int The final number of removed items
*/ */
private function dumpChildren($dumper, $parentCursor, &$refs, $children, $hashCut, $hashType) private function dumpChildren($dumper, $parentCursor, &$refs, $children, $hashCut, $hashType, $dumpKeys)
{ {
$cursor = clone $parentCursor; $cursor = clone $parentCursor;
++$cursor->depth; ++$cursor->depth;
@@ -204,7 +256,7 @@ class Data
$cursor->hashCut = $hashCut; $cursor->hashCut = $hashCut;
foreach ($children as $key => $child) { foreach ($children as $key => $child) {
$cursor->hashKeyIsBinary = isset($key[0]) && !preg_match('//u', $key); $cursor->hashKeyIsBinary = isset($key[0]) && !preg_match('//u', $key);
$cursor->hashKey = $key; $cursor->hashKey = $dumpKeys ? $key : null;
$this->dumpItem($dumper, $cursor, $refs, $child); $this->dumpItem($dumper, $cursor, $refs, $child);
if (++$cursor->hashIndex === $this->maxItemsPerDepth || $cursor->stop) { if (++$cursor->hashIndex === $this->maxItemsPerDepth || $cursor->stop) {
$parentCursor->stop = true; $parentCursor->stop = true;

View File

@@ -21,40 +21,40 @@ interface DumperInterface
/** /**
* Dumps a scalar value. * Dumps a scalar value.
* *
* @param Cursor $cursor The Cursor position in the dump. * @param Cursor $cursor The Cursor position in the dump
* @param string $type The PHP type of the value being dumped. * @param string $type The PHP type of the value being dumped
* @param scalar $value The scalar value being dumped. * @param scalar $value The scalar value being dumped
*/ */
public function dumpScalar(Cursor $cursor, $type, $value); public function dumpScalar(Cursor $cursor, $type, $value);
/** /**
* Dumps a string. * Dumps a string.
* *
* @param Cursor $cursor The Cursor position in the dump. * @param Cursor $cursor The Cursor position in the dump
* @param string $str The string being dumped. * @param string $str The string being dumped
* @param bool $bin Whether $str is UTF-8 or binary encoded. * @param bool $bin Whether $str is UTF-8 or binary encoded
* @param int $cut The number of characters $str has been cut by. * @param int $cut The number of characters $str has been cut by
*/ */
public function dumpString(Cursor $cursor, $str, $bin, $cut); public function dumpString(Cursor $cursor, $str, $bin, $cut);
/** /**
* Dumps while entering an hash. * Dumps while entering an hash.
* *
* @param Cursor $cursor The Cursor position in the dump. * @param Cursor $cursor The Cursor position in the dump
* @param int $type A Cursor::HASH_* const for the type of hash. * @param int $type A Cursor::HASH_* const for the type of hash
* @param string $class The object class, resource type or array count. * @param string $class The object class, resource type or array count
* @param bool $hasChild When the dump of the hash has child item. * @param bool $hasChild When the dump of the hash has child item
*/ */
public function enterHash(Cursor $cursor, $type, $class, $hasChild); public function enterHash(Cursor $cursor, $type, $class, $hasChild);
/** /**
* Dumps while leaving an hash. * Dumps while leaving an hash.
* *
* @param Cursor $cursor The Cursor position in the dump. * @param Cursor $cursor The Cursor position in the dump
* @param int $type A Cursor::HASH_* const for the type of hash. * @param int $type A Cursor::HASH_* const for the type of hash
* @param string $class The object class, resource type or array count. * @param string $class The object class, resource type or array count
* @param bool $hasChild When the dump of the hash has child item. * @param bool $hasChild When the dump of the hash has child item
* @param int $cut The number of items the hash has been cut by. * @param int $cut The number of items the hash has been cut by
*/ */
public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut); public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut);
} }

View File

@@ -37,4 +37,5 @@ class Stub
public $handle = 0; public $handle = 0;
public $refCount = 0; public $refCount = 0;
public $position = 0; public $position = 0;
public $attr = array();
} }

View File

@@ -25,10 +25,9 @@ class VarCloner extends AbstractCloner
protected function doClone($var) protected function doClone($var)
{ {
$useExt = $this->useExt; $useExt = $this->useExt;
$i = 0; // Current iteration position in $queue
$len = 1; // Length of $queue $len = 1; // Length of $queue
$pos = 0; // Number of cloned items past the first level $pos = 0; // Number of cloned items past the first level
$refs = 0; // Hard references counter $refsCounter = 0; // Hard references counter
$queue = array(array($var)); // This breadth-first queue is the return value $queue = array(array($var)); // This breadth-first queue is the return value
$arrayRefs = array(); // Map of queue indexes to stub array objects $arrayRefs = array(); // Map of queue indexes to stub array objects
$hardRefs = array(); // Map of original zval hashes to stub objects $hardRefs = array(); // Map of original zval hashes to stub objects
@@ -60,27 +59,32 @@ class VarCloner extends AbstractCloner
for ($i = 0; $i < $len; ++$i) { for ($i = 0; $i < $len; ++$i) {
$indexed = true; // Whether the currently iterated array is numerically indexed or not $indexed = true; // Whether the currently iterated array is numerically indexed or not
$j = -1; // Position in the currently iterated array $j = -1; // Position in the currently iterated array
$step = $queue[$i]; // Copy of the currently iterated array used for hard references detection $fromObjCast = array_keys($queue[$i]);
foreach ($step as $k => $v) { $fromObjCast = array_keys(array_flip($fromObjCast)) !== $fromObjCast;
$refs = $vals = $fromObjCast ? array_values($queue[$i]) : $queue[$i];
foreach ($queue[$i] as $k => $v) {
// $k is the original key // $k is the original key
// $v is the original value or a stub object in case of hard references // $v is the original value or a stub object in case of hard references
if ($indexed && $k !== ++$j) { if ($k !== ++$j) {
$indexed = false; $indexed = false;
} }
if ($fromObjCast) {
$k = $j;
}
if ($useExt) { if ($useExt) {
$zval = symfony_zval_info($k, $step); $zval = symfony_zval_info($k, $refs);
} else { } else {
$step[$k] = $cookie; $refs[$k] = $cookie;
if ($zval['zval_isref'] = $queue[$i][$k] === $cookie) { if ($zval['zval_isref'] = $vals[$k] === $cookie) {
$zval['zval_hash'] = $v instanceof Stub ? spl_object_hash($v) : null; $zval['zval_hash'] = $v instanceof Stub ? spl_object_hash($v) : null;
} }
$zval['type'] = gettype($v); $zval['type'] = gettype($v);
} }
if ($zval['zval_isref']) { if ($zval['zval_isref']) {
$queue[$i][$k] = &$stub; // Break hard references to make $queue completely $vals[$k] = &$stub; // Break hard references to make $queue completely
unset($stub); // independent from the original structure unset($stub); // independent from the original structure
if (isset($hardRefs[$zval['zval_hash']])) { if (isset($hardRefs[$zval['zval_hash']])) {
$queue[$i][$k] = $useExt ? ($v = $hardRefs[$zval['zval_hash']]) : ($step[$k] = $v); $vals[$k] = $useExt ? ($v = $hardRefs[$zval['zval_hash']]) : ($refs[$k] = $v);
if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) { if ($v->value instanceof Stub && (Stub::TYPE_OBJECT === $v->value->type || Stub::TYPE_RESOURCE === $v->value->type)) {
++$v->value->refCount; ++$v->value->refCount;
} }
@@ -102,12 +106,12 @@ class VarCloner extends AbstractCloner
} else { } else {
$stub->value = $v; $stub->value = $v;
} }
} elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = iconv_strlen($v, 'UTF-8') - $maxString) { } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = mb_strlen($v, 'UTF-8') - $maxString) {
$stub = new Stub(); $stub = new Stub();
$stub->type = Stub::TYPE_STRING; $stub->type = Stub::TYPE_STRING;
$stub->class = Stub::STRING_UTF8; $stub->class = Stub::STRING_UTF8;
$stub->cut = $cut; $stub->cut = $cut;
$stub->value = iconv_substr($v, 0, $maxString, 'UTF-8'); $stub->value = mb_substr($v, 0, $maxString, 'UTF-8');
} }
break; break;
@@ -178,10 +182,13 @@ class VarCloner extends AbstractCloner
case 'resource': case 'resource':
case 'unknown type': case 'unknown type':
case 'resource (closed)':
if (empty($resRefs[$h = (int) $v])) { if (empty($resRefs[$h = (int) $v])) {
$stub = new Stub(); $stub = new Stub();
$stub->type = Stub::TYPE_RESOURCE; $stub->type = Stub::TYPE_RESOURCE;
$stub->class = $zval['resource_type'] ?: get_resource_type($v); if ('Unknown' === $stub->class = $zval['resource_type'] ?: @get_resource_type($v)) {
$stub->class = 'Closed';
}
$stub->value = $v; $stub->value = $v;
$stub->handle = $h; $stub->handle = $h;
$a = $this->castResource($stub, 0 < $i); $a = $this->castResource($stub, 0 < $i);
@@ -204,18 +211,18 @@ class VarCloner extends AbstractCloner
if (isset($stub)) { if (isset($stub)) {
if ($zval['zval_isref']) { if ($zval['zval_isref']) {
if ($useExt) { if ($useExt) {
$queue[$i][$k] = $hardRefs[$zval['zval_hash']] = $v = new Stub(); $vals[$k] = $hardRefs[$zval['zval_hash']] = $v = new Stub();
$v->value = $stub; $v->value = $stub;
} else { } else {
$step[$k] = new Stub(); $refs[$k] = new Stub();
$step[$k]->value = $stub; $refs[$k]->value = $stub;
$h = spl_object_hash($step[$k]); $h = spl_object_hash($refs[$k]);
$queue[$i][$k] = $hardRefs[$h] = &$step[$k]; $vals[$k] = $hardRefs[$h] = &$refs[$k];
$values[$h] = $v; $values[$h] = $v;
} }
$queue[$i][$k]->handle = ++$refs; $vals[$k]->handle = ++$refsCounter;
} else { } else {
$queue[$i][$k] = $stub; $vals[$k] = $stub;
} }
if ($a) { if ($a) {
@@ -243,19 +250,38 @@ class VarCloner extends AbstractCloner
$stub = $a = null; $stub = $a = null;
} elseif ($zval['zval_isref']) { } elseif ($zval['zval_isref']) {
if ($useExt) { if ($useExt) {
$queue[$i][$k] = $hardRefs[$zval['zval_hash']] = new Stub(); $vals[$k] = $hardRefs[$zval['zval_hash']] = new Stub();
$queue[$i][$k]->value = $v; $vals[$k]->value = $v;
} else { } else {
$step[$k] = $queue[$i][$k] = new Stub(); $refs[$k] = $vals[$k] = new Stub();
$step[$k]->value = $v; $refs[$k]->value = $v;
$h = spl_object_hash($step[$k]); $h = spl_object_hash($refs[$k]);
$hardRefs[$h] = &$step[$k]; $hardRefs[$h] = &$refs[$k];
$values[$h] = $v; $values[$h] = $v;
} }
$queue[$i][$k]->handle = ++$refs; $vals[$k]->handle = ++$refsCounter;
} }
} }
if ($fromObjCast) {
$refs = $vals;
$vals = array();
$j = -1;
foreach ($queue[$i] as $k => $v) {
foreach (array($k => $v) as $a => $v) {
}
if ($a !== $k) {
$vals = (object) $vals;
$vals->{$k} = $refs[++$j];
$vals = (array) $vals;
} else {
$vals[$k] = $refs[++$j];
}
}
}
$queue[$i] = $vals;
if (isset($arrayRefs[$i])) { if (isset($arrayRefs[$i])) {
if ($indexed) { if ($indexed) {
$arrayRefs[$i]->class = Stub::ARRAY_INDEXED; $arrayRefs[$i]->class = Stub::ARRAY_INDEXED;
@@ -291,7 +317,7 @@ class VarCloner extends AbstractCloner
if (!empty($frame['line'])) { if (!empty($frame['line'])) {
ob_start(); ob_start();
debug_zval_dump($obj); debug_zval_dump($obj);
self::$hashMask = substr(ob_get_clean(), 17); self::$hashMask = (int) substr(ob_get_clean(), 17);
} }
} }

View File

@@ -21,6 +21,9 @@ use Symfony\Component\VarDumper\Cloner\DumperInterface;
*/ */
abstract class AbstractDumper implements DataDumperInterface, DumperInterface abstract class AbstractDumper implements DataDumperInterface, DumperInterface
{ {
const DUMP_LIGHT_ARRAY = 1;
const DUMP_STRING_LENGTH = 2;
public static $defaultOutput = 'php://output'; public static $defaultOutput = 'php://output';
protected $line = ''; protected $line = '';
@@ -28,18 +31,21 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
protected $outputStream; protected $outputStream;
protected $decimalPoint; // This is locale dependent protected $decimalPoint; // This is locale dependent
protected $indentPad = ' '; protected $indentPad = ' ';
protected $flags;
private $charset; private $charset;
/** /**
* @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path, defaults to static::$defaultOutput. * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path, defaults to static::$defaultOutput
* @param string $charset The default character encoding to use for non-UTF8 strings. * @param string $charset The default character encoding to use for non-UTF8 strings
* @param int $flags A bit field of static::DUMP_* constants to fine tune dumps representation
*/ */
public function __construct($output = null, $charset = null) public function __construct($output = null, $charset = null, $flags = 0)
{ {
$this->flags = (int) $flags;
$this->setCharset($charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8'); $this->setCharset($charset ?: ini_get('php.output_encoding') ?: ini_get('default_charset') ?: 'UTF-8');
$this->decimalPoint = (string) 0.5; $this->decimalPoint = localeconv();
$this->decimalPoint = $this->decimalPoint[1]; $this->decimalPoint = $this->decimalPoint['decimal_point'];
$this->setOutput($output ?: static::$defaultOutput); $this->setOutput($output ?: static::$defaultOutput);
if (!$output && is_string(static::$defaultOutput)) { if (!$output && is_string(static::$defaultOutput)) {
static::$defaultOutput = $this->outputStream; static::$defaultOutput = $this->outputStream;
@@ -49,9 +55,9 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
/** /**
* Sets the output destination of the dumps. * Sets the output destination of the dumps.
* *
* @param callable|resource|string $output A line dumper callable, an opened stream or an output path. * @param callable|resource|string $output A line dumper callable, an opened stream or an output path
* *
* @return callable|resource|string The previous output destination. * @return callable|resource|string The previous output destination
*/ */
public function setOutput($output) public function setOutput($output)
{ {
@@ -74,9 +80,9 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
/** /**
* Sets the default character encoding to use for non-UTF8 strings. * Sets the default character encoding to use for non-UTF8 strings.
* *
* @param string $charset The default character encoding to use for non-UTF8 strings. * @param string $charset The default character encoding to use for non-UTF8 strings
* *
* @return string The previous charset. * @return string The previous charset
*/ */
public function setCharset($charset) public function setCharset($charset)
{ {
@@ -93,9 +99,9 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
/** /**
* Sets the indentation pad string. * Sets the indentation pad string.
* *
* @param string $pad A string the will be prepended to dumped lines, repeated by nesting level. * @param string $pad A string the will be prepended to dumped lines, repeated by nesting level
* *
* @return string The indent pad. * @return string The indent pad
*/ */
public function setIndentPad($pad) public function setIndentPad($pad)
{ {
@@ -108,33 +114,43 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
/** /**
* Dumps a Data object. * Dumps a Data object.
* *
* @param Data $data A Data object. * @param Data $data A Data object
* @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path. * @param callable|resource|string|true|null $output A line dumper callable, an opened stream, an output path or true to return the dump
*
* @return string|null The dump as string when $output is true
*/ */
public function dump(Data $data, $output = null) public function dump(Data $data, $output = null)
{ {
$exception = null; $this->decimalPoint = localeconv();
$this->decimalPoint = $this->decimalPoint['decimal_point'];
if ($returnDump = true === $output) {
$output = fopen('php://memory', 'r+b');
}
if ($output) { if ($output) {
$prevOutput = $this->setOutput($output); $prevOutput = $this->setOutput($output);
} }
try { try {
$data->dump($this); $data->dump($this);
$this->dumpLine(-1); $this->dumpLine(-1);
} catch (\Exception $exception) {
// Re-thrown below if ($returnDump) {
} $result = stream_get_contents($output, -1, 0);
if ($output) { fclose($output);
$this->setOutput($prevOutput);
} return $result;
if (null !== $exception) { }
throw $exception; } finally {
if ($output) {
$this->setOutput($prevOutput);
}
} }
} }
/** /**
* Dumps the current line. * Dumps the current line.
* *
* @param int $depth The recursive depth in the dumped structure for the line being dumped. * @param int $depth The recursive depth in the dumped structure for the line being dumped
*/ */
protected function dumpLine($depth) protected function dumpLine($depth)
{ {
@@ -145,8 +161,9 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
/** /**
* Generic line dumper callback. * Generic line dumper callback.
* *
* @param string $line The line to write. * @param string $line The line to write
* @param int $depth The recursive depth in the dumped structure. * @param int $depth The recursive depth in the dumped structure
* @param string $indentPad The line indent pad
*/ */
protected function echoLine($line, $depth, $indentPad) protected function echoLine($line, $depth, $indentPad)
{ {
@@ -158,12 +175,20 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
/** /**
* Converts a non-UTF-8 string to UTF-8. * Converts a non-UTF-8 string to UTF-8.
* *
* @param string $s The non-UTF-8 string to convert. * @param string $s The non-UTF-8 string to convert
* *
* @return string The string converted to UTF-8. * @return string The string converted to UTF-8
*/ */
protected function utf8Encode($s) protected function utf8Encode($s)
{ {
if (preg_match('//u', $s)) {
return $s;
}
if (!function_exists('iconv')) {
throw new \RuntimeException('Unable to convert a non-UTF-8 string to UTF-8: required function iconv() does not exist. You should install ext-iconv or symfony/polyfill-iconv.');
}
if (false !== $c = @iconv($this->charset, 'UTF-8', $s)) { if (false !== $c = @iconv($this->charset, 'UTF-8', $s)) {
return $c; return $c;
} }

View File

@@ -54,12 +54,12 @@ class CliDumper extends AbstractDumper
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function __construct($output = null, $charset = null) public function __construct($output = null, $charset = null, $flags = 0)
{ {
parent::__construct($output, $charset); parent::__construct($output, $charset, $flags);
if ('\\' === DIRECTORY_SEPARATOR && false !== @getenv('ANSICON')) { if ('\\' === DIRECTORY_SEPARATOR && 'ON' !== @getenv('ConEmuANSI') && 'xterm' !== @getenv('TERM')) {
// Use only the base 16 xterm colors when using ANSICON // Use only the base 16 xterm colors when using ANSICON or standard Windows 10 CLI
$this->setStyles(array( $this->setStyles(array(
'default' => '31', 'default' => '31',
'num' => '1;34', 'num' => '1;34',
@@ -97,7 +97,7 @@ class CliDumper extends AbstractDumper
/** /**
* Configures styles. * Configures styles.
* *
* @param array $styles A map of style names to style definitions. * @param array $styles A map of style names to style definitions
*/ */
public function setStyles(array $styles) public function setStyles(array $styles)
{ {
@@ -112,9 +112,13 @@ class CliDumper extends AbstractDumper
$this->dumpKey($cursor); $this->dumpKey($cursor);
$style = 'const'; $style = 'const';
$attr = array(); $attr = $cursor->attr;
switch ($type) { switch ($type) {
case 'default':
$style = 'default';
break;
case 'integer': case 'integer':
$style = 'num'; $style = 'num';
break; break;
@@ -123,9 +127,9 @@ class CliDumper extends AbstractDumper
$style = 'num'; $style = 'num';
switch (true) { switch (true) {
case INF === $value: $value = 'INF'; break; case INF === $value: $value = 'INF'; break;
case -INF === $value: $value = '-INF'; break; case -INF === $value: $value = '-INF'; break;
case is_nan($value): $value = 'NAN'; break; case is_nan($value): $value = 'NAN'; break;
default: default:
$value = (string) $value; $value = (string) $value;
if (false === strpos($value, $this->decimalPoint)) { if (false === strpos($value, $this->decimalPoint)) {
@@ -144,8 +148,8 @@ class CliDumper extends AbstractDumper
break; break;
default: default:
$attr['value'] = isset($value[0]) && !preg_match('//u', $value) ? $this->utf8Encode($value) : $value; $attr += array('value' => $this->utf8Encode($value));
$value = isset($type[0]) && !preg_match('//u', $type) ? $this->utf8Encode($type) : $type; $value = $this->utf8Encode($type);
break; break;
} }
@@ -160,6 +164,7 @@ class CliDumper extends AbstractDumper
public function dumpString(Cursor $cursor, $str, $bin, $cut) public function dumpString(Cursor $cursor, $str, $bin, $cut)
{ {
$this->dumpKey($cursor); $this->dumpKey($cursor);
$attr = $cursor->attr;
if ($bin) { if ($bin) {
$str = $this->utf8Encode($str); $str = $this->utf8Encode($str);
@@ -168,8 +173,8 @@ class CliDumper extends AbstractDumper
$this->line .= '""'; $this->line .= '""';
$this->dumpLine($cursor->depth, true); $this->dumpLine($cursor->depth, true);
} else { } else {
$attr = array( $attr += array(
'length' => 0 <= $cut ? iconv_strlen($str, 'UTF-8') + $cut : 0, 'length' => 0 <= $cut ? mb_strlen($str, 'UTF-8') + $cut : 0,
'binary' => $bin, 'binary' => $bin,
); );
$str = explode("\n", $str); $str = explode("\n", $str);
@@ -180,6 +185,9 @@ class CliDumper extends AbstractDumper
$m = count($str) - 1; $m = count($str) - 1;
$i = $lineCut = 0; $i = $lineCut = 0;
if (self::DUMP_STRING_LENGTH & $this->flags) {
$this->line .= '('.$attr['length'].') ';
}
if ($bin) { if ($bin) {
$this->line .= 'b'; $this->line .= 'b';
} }
@@ -195,8 +203,8 @@ class CliDumper extends AbstractDumper
if ($i < $m) { if ($i < $m) {
$str .= "\n"; $str .= "\n";
} }
if (0 < $this->maxStringWidth && $this->maxStringWidth < $len = iconv_strlen($str, 'UTF-8')) { if (0 < $this->maxStringWidth && $this->maxStringWidth < $len = mb_strlen($str, 'UTF-8')) {
$str = iconv_substr($str, 0, $this->maxStringWidth, 'UTF-8'); $str = mb_substr($str, 0, $this->maxStringWidth, 'UTF-8');
$lineCut = $len - $this->maxStringWidth; $lineCut = $len - $this->maxStringWidth;
} }
if ($m && 0 < $cursor->depth) { if ($m && 0 < $cursor->depth) {
@@ -241,15 +249,13 @@ class CliDumper extends AbstractDumper
{ {
$this->dumpKey($cursor); $this->dumpKey($cursor);
if (!preg_match('//u', $class)) { $class = $this->utf8Encode($class);
$class = $this->utf8Encode($class);
}
if (Cursor::HASH_OBJECT === $type) { if (Cursor::HASH_OBJECT === $type) {
$prefix = $class && 'stdClass' !== $class ? $this->style('note', $class).' {' : '{'; $prefix = $class && 'stdClass' !== $class ? $this->style('note', $class).' {' : '{';
} elseif (Cursor::HASH_RESOURCE === $type) { } elseif (Cursor::HASH_RESOURCE === $type) {
$prefix = $this->style('note', $class.' resource').($hasChild ? ' {' : ' '); $prefix = $this->style('note', $class.' resource').($hasChild ? ' {' : ' ');
} else { } else {
$prefix = $class ? $this->style('note', 'array:'.$class).' [' : '['; $prefix = $class && !(self::DUMP_LIGHT_ARRAY & $this->flags) ? $this->style('note', 'array:'.$class).' [' : '[';
} }
if ($cursor->softRefCount || 0 < $cursor->softRefHandle) { if ($cursor->softRefCount || 0 < $cursor->softRefHandle) {
@@ -280,9 +286,9 @@ class CliDumper extends AbstractDumper
/** /**
* Dumps an ellipsis for cut children. * Dumps an ellipsis for cut children.
* *
* @param Cursor $cursor The Cursor position in the dump. * @param Cursor $cursor The Cursor position in the dump
* @param bool $hasChild When the dump of the hash has child item. * @param bool $hasChild When the dump of the hash has child item
* @param int $cut The number of items the hash has been cut by. * @param int $cut The number of items the hash has been cut by
*/ */
protected function dumpEllipsis(Cursor $cursor, $hasChild, $cut) protected function dumpEllipsis(Cursor $cursor, $hasChild, $cut)
{ {
@@ -300,7 +306,7 @@ class CliDumper extends AbstractDumper
/** /**
* Dumps a key in a hash structure. * Dumps a key in a hash structure.
* *
* @param Cursor $cursor The Cursor position in the dump. * @param Cursor $cursor The Cursor position in the dump
*/ */
protected function dumpKey(Cursor $cursor) protected function dumpKey(Cursor $cursor)
{ {
@@ -314,6 +320,9 @@ class CliDumper extends AbstractDumper
switch ($cursor->hashType) { switch ($cursor->hashType) {
default: default:
case Cursor::HASH_INDEXED: case Cursor::HASH_INDEXED:
if (self::DUMP_LIGHT_ARRAY & $this->flags) {
break;
}
$style = 'index'; $style = 'index';
case Cursor::HASH_ASSOC: case Cursor::HASH_ASSOC:
if (is_int($key)) { if (is_int($key)) {
@@ -332,13 +341,17 @@ class CliDumper extends AbstractDumper
} elseif (0 < strpos($key, "\0", 1)) { } elseif (0 < strpos($key, "\0", 1)) {
$key = explode("\0", substr($key, 1), 2); $key = explode("\0", substr($key, 1), 2);
switch ($key[0]) { switch ($key[0][0]) {
case '+': // User inserted keys case '+': // User inserted keys
$attr['dynamic'] = true; $attr['dynamic'] = true;
$this->line .= '+'.$bin.'"'.$this->style('public', $key[1], $attr).'": '; $this->line .= '+'.$bin.'"'.$this->style('public', $key[1], $attr).'": ';
break 2; break 2;
case '~': case '~':
$style = 'meta'; $style = 'meta';
if (isset($key[0][1])) {
parse_str(substr($key[0], 1), $attr);
$attr += array('binary' => $cursor->hashKeyIsBinary);
}
break; break;
case '*': case '*':
$style = 'protected'; $style = 'protected';
@@ -368,11 +381,11 @@ class CliDumper extends AbstractDumper
/** /**
* Decorates a value with some style. * Decorates a value with some style.
* *
* @param string $style The type of style being applied. * @param string $style The type of style being applied
* @param string $value The value being styled. * @param string $value The value being styled
* @param array $attr Optional context information. * @param array $attr Optional context information
* *
* @return string The value with style decoration. * @return string The value with style decoration
*/ */
protected function style($style, $value, $attr = array()) protected function style($style, $value, $attr = array())
{ {
@@ -412,7 +425,7 @@ class CliDumper extends AbstractDumper
} }
/** /**
* @return bool Tells if the current output stream supports ANSI colors or not. * @return bool Tells if the current output stream supports ANSI colors or not
*/ */
protected function supportsColors() protected function supportsColors()
{ {
@@ -446,7 +459,12 @@ class CliDumper extends AbstractDumper
} }
if ('\\' === DIRECTORY_SEPARATOR) { if ('\\' === DIRECTORY_SEPARATOR) {
static::$defaultColors = @(false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM')); static::$defaultColors = @(
'10.0.10586' === PHP_WINDOWS_VERSION_MAJOR.'.'.PHP_WINDOWS_VERSION_MINOR.'.'.PHP_WINDOWS_VERSION_BUILD
|| false !== getenv('ANSICON')
|| 'ON' === getenv('ConEmuANSI')
|| 'xterm' === getenv('TERM')
);
} elseif (function_exists('posix_isatty')) { } elseif (function_exists('posix_isatty')) {
$h = stream_get_meta_data($this->outputStream) + array('wrapper_type' => null); $h = stream_get_meta_data($this->outputStream) + array('wrapper_type' => null);
$h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'wb') : $this->outputStream; $h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'wb') : $this->outputStream;

View File

@@ -23,7 +23,7 @@ interface DataDumperInterface
/** /**
* Dumps a Data object. * Dumps a Data object.
* *
* @param Data $data A Data object. * @param Data $data A Data object
*/ */
public function dump(Data $data); public function dump(Data $data);
} }

View File

@@ -25,13 +25,13 @@ class HtmlDumper extends CliDumper
protected $dumpHeader; protected $dumpHeader;
protected $dumpPrefix = '<pre class=sf-dump id=%s data-indent-pad="%s">'; protected $dumpPrefix = '<pre class=sf-dump id=%s data-indent-pad="%s">';
protected $dumpSuffix = '</pre><script>Sfdump("%s")</script>'; protected $dumpSuffix = '</pre><script>Sfdump(%s)</script>';
protected $dumpId = 'sf-dump'; protected $dumpId = 'sf-dump';
protected $colors = true; protected $colors = true;
protected $headerIsDumped = false; protected $headerIsDumped = false;
protected $lastDepth = -1; protected $lastDepth = -1;
protected $styles = array( protected $styles = array(
'default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:100000; word-break: normal', 'default' => 'background-color:#18171B; color:#FF8400; line-height:1.2em; font:12px Menlo, Monaco, Consolas, monospace; word-wrap: break-word; white-space: pre-wrap; position:relative; z-index:99999; word-break: normal',
'num' => 'font-weight:bold; color:#1299DA', 'num' => 'font-weight:bold; color:#1299DA',
'const' => 'font-weight:bold', 'const' => 'font-weight:bold',
'str' => 'font-weight:bold; color:#56DB3A', 'str' => 'font-weight:bold; color:#56DB3A',
@@ -43,27 +43,24 @@ class HtmlDumper extends CliDumper
'meta' => 'color:#B729D9', 'meta' => 'color:#B729D9',
'key' => 'color:#56DB3A', 'key' => 'color:#56DB3A',
'index' => 'color:#1299DA', 'index' => 'color:#1299DA',
'ellipsis' => 'color:#FF8400',
); );
private $displayOptions = array(
'maxDepth' => 1,
'maxStringLength' => 160,
'fileLinkFormat' => null,
);
private $extraDisplayOptions = array();
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function __construct($output = null, $charset = null) public function __construct($output = null, $charset = null, $flags = 0)
{ {
AbstractDumper::__construct($output, $charset); AbstractDumper::__construct($output, $charset, $flags);
$this->dumpId = 'sf-dump-'.mt_rand(); $this->dumpId = 'sf-dump-'.mt_rand();
} $this->displayOptions['fileLinkFormat'] = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format');
/**
* {@inheritdoc}
*/
public function setOutput($output)
{
if ($output !== $prev = parent::setOutput($output)) {
$this->headerIsDumped = false;
}
return $prev;
} }
/** /**
@@ -75,10 +72,21 @@ class HtmlDumper extends CliDumper
$this->styles = $styles + $this->styles; $this->styles = $styles + $this->styles;
} }
/**
* Configures display options.
*
* @param array $displayOptions A map of display options to customize the behavior
*/
public function setDisplayOptions(array $displayOptions)
{
$this->headerIsDumped = false;
$this->displayOptions = $displayOptions + $this->displayOptions;
}
/** /**
* Sets an HTML header that will be dumped once in the output stream. * Sets an HTML header that will be dumped once in the output stream.
* *
* @param string $header An HTML string. * @param string $header An HTML string
*/ */
public function setDumpHeader($header) public function setDumpHeader($header)
{ {
@@ -88,8 +96,8 @@ class HtmlDumper extends CliDumper
/** /**
* Sets an HTML prefix and suffix that will encapse every single dump. * Sets an HTML prefix and suffix that will encapse every single dump.
* *
* @param string $prefix The prepended HTML string. * @param string $prefix The prepended HTML string
* @param string $suffix The appended HTML string. * @param string $suffix The appended HTML string
*/ */
public function setDumpBoundaries($prefix, $suffix) public function setDumpBoundaries($prefix, $suffix)
{ {
@@ -100,10 +108,13 @@ class HtmlDumper extends CliDumper
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function dump(Data $data, $output = null) public function dump(Data $data, $output = null, array $extraDisplayOptions = array())
{ {
parent::dump($data, $output); $this->extraDisplayOptions = $extraDisplayOptions;
$result = parent::dump($data, $output);
$this->dumpId = 'sf-dump-'.mt_rand(); $this->dumpId = 'sf-dump-'.mt_rand();
return $result;
} }
/** /**
@@ -111,13 +122,13 @@ class HtmlDumper extends CliDumper
*/ */
protected function getDumpHeader() protected function getDumpHeader()
{ {
$this->headerIsDumped = true; $this->headerIsDumped = null !== $this->outputStream ? $this->outputStream : $this->lineDumper;
if (null !== $this->dumpHeader) { if (null !== $this->dumpHeader) {
return $this->dumpHeader; return $this->dumpHeader;
} }
$line = <<<'EOHTML' $line = str_replace('{$options}', json_encode($this->displayOptions, JSON_FORCE_OBJECT), <<<'EOHTML'
<script> <script>
Sfdump = window.Sfdump || (function (doc) { Sfdump = window.Sfdump || (function (doc) {
@@ -173,15 +184,30 @@ function toggle(a, recursive) {
return true; return true;
}; };
return function (root) { return function (root, x) {
root = doc.getElementById(root); root = doc.getElementById(root);
var indentRx = new RegExp('^('+(root.getAttribute('data-indent-pad') || ' ').replace(rxEsc, '\\$1')+')+', 'm'),
options = {$options},
elt = root.getElementsByTagName('A'),
len = elt.length,
i = 0, s, h,
t = [];
while (i < len) t.push(elt[i++]);
for (i in x) {
options[i] = x[i];
}
function a(e, f) { function a(e, f) {
addEventListener(root, e, function (e) { addEventListener(root, e, function (e) {
if ('A' == e.target.tagName) { if ('A' == e.target.tagName) {
f(e.target, e); f(e.target, e);
} else if ('A' == e.target.parentNode.tagName) { } else if ('A' == e.target.parentNode.tagName) {
f(e.target.parentNode, e); f(e.target.parentNode, e);
} else if (e.target.nextElementSibling && 'A' == e.target.nextElementSibling.tagName) {
f(e.target.nextElementSibling, e, true);
} }
}); });
}; };
@@ -193,15 +219,17 @@ return function (root) {
refStyle.innerHTML = ''; refStyle.innerHTML = '';
} }
}); });
a('mouseover', function (a) { a('mouseover', function (a, e, c) {
if (a = idRx.exec(a.className)) { if (c) {
e.target.style.cursor = "pointer";
} else if (a = idRx.exec(a.className)) {
try { try {
refStyle.innerHTML = 'pre.sf-dump .'+a[0]+'{background-color: #B729D9; color: #FFF !important; border-radius: 2px}'; refStyle.innerHTML = 'pre.sf-dump .'+a[0]+'{background-color: #B729D9; color: #FFF !important; border-radius: 2px}';
} catch (e) { } catch (e) {
} }
} }
}); });
a('click', function (a, e) { a('click', function (a, e, c) {
if (/\bsf-dump-toggle\b/.test(a.className)) { if (/\bsf-dump-toggle\b/.test(a.className)) {
e.preventDefault(); e.preventDefault();
if (!toggle(a, isCtrlKey(e))) { if (!toggle(a, isCtrlKey(e))) {
@@ -222,7 +250,8 @@ return function (root) {
} }
} }
if (doc.getSelection) { if (c) {
} else if (doc.getSelection) {
try { try {
doc.getSelection().removeAllRanges(); doc.getSelection().removeAllRanges();
} catch (e) { } catch (e) {
@@ -231,31 +260,24 @@ return function (root) {
} else { } else {
doc.selection.empty(); doc.selection.empty();
} }
} else if (/\bsf-dump-str-toggle\b/.test(a.className)) {
e.preventDefault();
e = a.parentNode.parentNode;
e.className = e.className.replace(/sf-dump-str-(expand|collapse)/, a.parentNode.className);
} }
}); });
var indentRx = new RegExp('^('+(root.getAttribute('data-indent-pad') || ' ').replace(rxEsc, '\\$1')+')+', 'm'),
elt = root.getElementsByTagName('A'),
len = elt.length,
i = 0,
t = [];
while (i < len) t.push(elt[i++]);
elt = root.getElementsByTagName('SAMP'); elt = root.getElementsByTagName('SAMP');
len = elt.length; len = elt.length;
i = 0; i = 0;
while (i < len) t.push(elt[i++]); while (i < len) t.push(elt[i++]);
root = t;
len = t.length; len = t.length;
i = t = 0;
while (i < len) { for (i = 0; i < len; ++i) {
elt = root[i]; elt = t[i];
if ("SAMP" == elt.tagName) { if ('SAMP' == elt.tagName) {
elt.className = "sf-dump-expanded"; elt.className = 'sf-dump-expanded';
a = elt.previousSibling || {}; a = elt.previousSibling || {};
if ('A' != a.tagName) { if ('A' != a.tagName) {
a = doc.createElement('A'); a = doc.createElement('A');
@@ -267,19 +289,24 @@ return function (root) {
a.title = (a.title ? a.title+'\n[' : '[')+keyHint+'+click] Expand all children'; a.title = (a.title ? a.title+'\n[' : '[')+keyHint+'+click] Expand all children';
a.innerHTML += '<span>▼</span>'; a.innerHTML += '<span>▼</span>';
a.className += ' sf-dump-toggle'; a.className += ' sf-dump-toggle';
x = 1;
if ('sf-dump' != elt.parentNode.className) { if ('sf-dump' != elt.parentNode.className) {
x += elt.parentNode.getAttribute('data-depth')/1;
}
elt.setAttribute('data-depth', x);
if (x > options.maxDepth) {
toggle(a); toggle(a);
} }
} else if ("sf-dump-ref" == elt.className && (a = elt.getAttribute('href'))) { } else if ('sf-dump-ref' == elt.className && (a = elt.getAttribute('href'))) {
a = a.substr(1); a = a.substr(1);
elt.className += ' '+a; elt.className += ' '+a;
if (/[\[{]$/.test(elt.previousSibling.nodeValue)) { if (/[\[{]$/.test(elt.previousSibling.nodeValue)) {
a = a != elt.nextSibling.id && doc.getElementById(a); a = a != elt.nextSibling.id && doc.getElementById(a);
try { try {
t = a.nextSibling; s = a.nextSibling;
elt.appendChild(a); elt.appendChild(a);
t.parentNode.insertBefore(a, t); s.parentNode.insertBefore(a, s);
if (/^[@#]/.test(elt.innerHTML)) { if (/^[@#]/.test(elt.innerHTML)) {
elt.innerHTML += ' <span>▶</span>'; elt.innerHTML += ' <span>▶</span>';
} else { } else {
@@ -295,13 +322,38 @@ return function (root) {
} }
} }
} }
++i; }
if (0 >= options.maxStringLength) {
return;
}
try {
elt = root.querySelectorAll('.sf-dump-str');
len = elt.length;
i = 0;
t = [];
while (i < len) t.push(elt[i++]);
len = t.length;
for (i = 0; i < len; ++i) {
elt = t[i];
s = elt.innerText || elt.textContent;
x = s.length - options.maxStringLength;
if (0 < x) {
h = elt.innerHTML;
elt[elt.innerText ? 'innerText' : 'textContent'] = s.substring(0, options.maxStringLength);
elt.className += ' sf-dump-str-collapse';
elt.innerHTML = '<span class=sf-dump-str-collapse>'+h+'<a class="sf-dump-ref sf-dump-str-toggle" title="Collapse"> ◀</a></span>'+
'<span class=sf-dump-str-expand>'+elt.innerHTML+'<a class="sf-dump-ref sf-dump-str-toggle" title="'+x+' remaining characters"> ▶</a></span>';
}
}
} catch (e) {
} }
}; };
})(document); })(document);
</script> </script><style>
<style>
pre.sf-dump { pre.sf-dump {
display: block; display: block;
white-space: pre; white-space: pre;
@@ -323,11 +375,33 @@ pre.sf-dump a {
cursor: pointer; cursor: pointer;
border: 0; border: 0;
outline: none; outline: none;
color: inherit;
} }
EOHTML; pre.sf-dump .sf-dump-ellipsis {
display: inline-block;
overflow: visible;
text-overflow: ellipsis;
max-width: 5em;
white-space: nowrap;
overflow: hidden;
vertical-align: top;
}
pre.sf-dump code {
display:inline;
padding:0;
background:none;
}
.sf-dump-str-collapse .sf-dump-str-collapse {
display: none;
}
.sf-dump-str-expand .sf-dump-str-expand {
display: none;
}
EOHTML
);
foreach ($this->styles as $class => $style) { foreach ($this->styles as $class => $style) {
$line .= 'pre.sf-dump'.('default' !== $class ? ' .sf-dump-'.$class : '').'{'.$style.'}'; $line .= 'pre.sf-dump'.('default' === $class ? ', pre.sf-dump' : '').' .sf-dump-'.$class.'{'.$style.'}';
} }
return $this->dumpHeader = preg_replace('/\s+/', ' ', $line).'</style>'.$this->dumpHeader; return $this->dumpHeader = preg_replace('/\s+/', ' ', $line).'</style>'.$this->dumpHeader;
@@ -374,7 +448,7 @@ EOHTML;
return ''; return '';
} }
$v = htmlspecialchars($value, ENT_QUOTES, 'UTF-8'); $v = esc($value);
if ('ref' === $style) { if ('ref' === $style) {
if (empty($attr['count'])) { if (empty($attr['count'])) {
@@ -385,41 +459,47 @@ EOHTML;
return sprintf('<a class=sf-dump-ref href=#%s-ref%s title="%d occurrences">%s</a>', $this->dumpId, $r, 1 + $attr['count'], $v); return sprintf('<a class=sf-dump-ref href=#%s-ref%s title="%d occurrences">%s</a>', $this->dumpId, $r, 1 + $attr['count'], $v);
} }
if ('const' === $style && array_key_exists('value', $attr)) { if ('const' === $style && isset($attr['value'])) {
$style .= sprintf(' title="%s"', htmlspecialchars(json_encode($attr['value']), ENT_QUOTES, 'UTF-8')); $style .= sprintf(' title="%s"', esc(is_scalar($attr['value']) ? $attr['value'] : json_encode($attr['value'])));
} elseif ('public' === $style) { } elseif ('public' === $style) {
$style .= sprintf(' title="%s"', empty($attr['dynamic']) ? 'Public property' : 'Runtime added dynamic property'); $style .= sprintf(' title="%s"', empty($attr['dynamic']) ? 'Public property' : 'Runtime added dynamic property');
} elseif ('str' === $style && 1 < $attr['length']) { } elseif ('str' === $style && 1 < $attr['length']) {
$style .= sprintf(' title="%s%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : ''); $style .= sprintf(' title="%d%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : '');
} elseif ('note' === $style && false !== $c = strrpos($v, '\\')) { } elseif ('note' === $style && false !== $c = strrpos($v, '\\')) {
return sprintf('<abbr title="%s" class=sf-dump-%s>%s</abbr>', $v, $style, substr($v, $c + 1)); return sprintf('<abbr title="%s" class=sf-dump-%s>%s</abbr>', $v, $style, substr($v, $c + 1));
} elseif ('protected' === $style) { } elseif ('protected' === $style) {
$style .= ' title="Protected property"'; $style .= ' title="Protected property"';
} elseif ('meta' === $style && isset($attr['title'])) {
$style .= sprintf(' title="%s"', esc($this->utf8Encode($attr['title'])));
} elseif ('private' === $style) { } elseif ('private' === $style) {
$style .= sprintf(' title="Private property defined in class:&#10;`%s`"', $attr['class']); $style .= sprintf(' title="Private property defined in class:&#10;`%s`"', esc($this->utf8Encode($attr['class'])));
}
$map = static::$controlCharsMap;
if (isset($attr['ellipsis'])) {
$label = esc(substr($value, -$attr['ellipsis']));
$style = str_replace(' title="', " title=\"$v\n", $style);
$v = sprintf('<span class=sf-dump-ellipsis>%s</span>%s', substr($v, 0, -strlen($label)), $label);
} }
$map = static::$controlCharsMap; $v = "<span class=sf-dump-{$style}>".preg_replace_callback(static::$controlCharsRx, function ($c) use ($map) {
$style = "<span class=sf-dump-{$style}>"; $s = '<span class=sf-dump-default>';
$v = preg_replace_callback(static::$controlCharsRx, function ($c) use ($map, $style) {
$s = '</span>';
$c = $c[$i = 0]; $c = $c[$i = 0];
do { do {
$s .= isset($map[$c[$i]]) ? $map[$c[$i]] : sprintf('\x%02X', ord($c[$i])); $s .= isset($map[$c[$i]]) ? $map[$c[$i]] : sprintf('\x%02X', ord($c[$i]));
} while (isset($c[++$i])); } while (isset($c[++$i]));
return $s.$style; return $s.'</span>';
}, $v, -1, $cchrCount); }, $v).'</span>';
if ($cchrCount && '<' === $v[0]) { if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], isset($attr['line']) ? $attr['line'] : 0)) {
$v = substr($v, 7); $attr['href'] = $href;
} else {
$v = $style.$v;
} }
if ($cchrCount && '>' === substr($v, -1)) { if (isset($attr['href'])) {
$v = substr($v, 0, -strlen($style)); $v = sprintf('<a href="%s" target="_blank" rel="noopener noreferrer">%s</a>', esc($this->utf8Encode($attr['href'])), $v);
} else { }
$v .= '</span>'; if (isset($attr['lang'])) {
$v = sprintf('<code class="%s">%s</code>', esc($attr['lang']), $v);
} }
return $v; return $v;
@@ -433,12 +513,17 @@ EOHTML;
if (-1 === $this->lastDepth) { if (-1 === $this->lastDepth) {
$this->line = sprintf($this->dumpPrefix, $this->dumpId, $this->indentPad).$this->line; $this->line = sprintf($this->dumpPrefix, $this->dumpId, $this->indentPad).$this->line;
} }
if (!$this->headerIsDumped) { if ($this->headerIsDumped !== (null !== $this->outputStream ? $this->outputStream : $this->lineDumper)) {
$this->line = $this->getDumpHeader().$this->line; $this->line = $this->getDumpHeader().$this->line;
} }
if (-1 === $depth) { if (-1 === $depth) {
$this->line .= sprintf($this->dumpSuffix, $this->dumpId); $args = array('"'.$this->dumpId.'"');
if ($this->extraDisplayOptions) {
$args[] = json_encode($this->extraDisplayOptions, JSON_FORCE_OBJECT);
}
// Replace is for BC
$this->line .= sprintf(str_replace('"%s"', '%s', $this->dumpSuffix), implode(', ', $args));
} }
$this->lastDepth = $depth; $this->lastDepth = $depth;
@@ -449,4 +534,20 @@ EOHTML;
} }
AbstractDumper::dumpLine($depth); AbstractDumper::dumpLine($depth);
} }
private function getSourceLink($file, $line)
{
$options = $this->extraDisplayOptions + $this->displayOptions;
if ($fmt = $options['fileLinkFormat']) {
return is_string($fmt) ? strtr($fmt, array('%f' => $file, '%l' => $line)) : $fmt->format($file, $line);
}
return false;
}
}
function esc($str)
{
return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
} }

View File

@@ -1,4 +1,4 @@
Copyright (c) 2014-2015 Fabien Potencier Copyright (c) 2014-2017 Fabien Potencier
Permission is hereby granted, free of charge, to any person obtaining a copy Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal of this software and associated documentation files (the "Software"), to deal

View File

@@ -1,14 +1,15 @@
Symfony mechanism for exploring and dumping PHP variables VarDumper Component
========================================================= ===================
This component provides a mechanism that allows exploring then dumping The VarDumper component provides mechanisms for walking through any arbitrary
any PHP variable. PHP variable. Built on top, it provides a better `dump()` function that you
can use instead of `var_dump`.
It handles scalars, objects and resources properly, taking hard and soft Resources
references into account. More than being immune to infinite recursion ---------
problems, it allows dumping where references link to each other.
It explores recursive structures using a breadth-first algorithm.
The component exposes all the parts involved in the different steps of * [Documentation](https://symfony.com/doc/current/components/var_dumper/introduction.html)
cloning then dumping a PHP variable, while applying size limits and having * [Contributing](https://symfony.com/doc/current/contributing/index.html)
specialized output formats and methods. * [Report issues](https://github.com/symfony/symfony/issues) and
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

View File

@@ -29,17 +29,20 @@ trait VarDumperTestTrait
$this->assertStringMatchesFormat(rtrim($dump), $this->getDump($data), $message); $this->assertStringMatchesFormat(rtrim($dump), $this->getDump($data), $message);
} }
protected function getDump($data) protected function getDump($data, $key = null)
{ {
$h = fopen('php://memory', 'r+b'); $flags = getenv('DUMP_LIGHT_ARRAY') ? CliDumper::DUMP_LIGHT_ARRAY : 0;
$flags |= getenv('DUMP_STRING_LENGTH') ? CliDumper::DUMP_STRING_LENGTH : 0;
$cloner = new VarCloner(); $cloner = new VarCloner();
$cloner->setMaxItems(-1); $cloner->setMaxItems(-1);
$dumper = new CliDumper($h); $dumper = new CliDumper(null, null, $flags);
$dumper->setColors(false); $dumper->setColors(false);
$dumper->dump($cloner->cloneVar($data)->withRefHandles(false)); $data = $cloner->cloneVar($data)->withRefHandles(false);
$data = stream_get_contents($h, -1, 0); if (null !== $key && null === $data = $data->seek($key)) {
fclose($h); return;
}
return rtrim($data); return rtrim($dumper->dump($data, true));
} }
} }

View File

@@ -11,13 +11,14 @@
namespace Symfony\Component\VarDumper\Tests\Caster; namespace Symfony\Component\VarDumper\Tests\Caster;
use PHPUnit\Framework\TestCase;
use Symfony\Component\VarDumper\Caster\Caster; use Symfony\Component\VarDumper\Caster\Caster;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait; use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
/** /**
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*/ */
class CasterTest extends \PHPUnit_Framework_TestCase class CasterTest extends TestCase
{ {
use VarDumperTestTrait; use VarDumperTestTrait;

View File

@@ -0,0 +1,225 @@
<?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\Caster;
use PHPUnit\Framework\TestCase;
use Symfony\Component\VarDumper\Caster\ExceptionCaster;
use Symfony\Component\VarDumper\Caster\FrameStub;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
class ExceptionCasterTest extends TestCase
{
use VarDumperTestTrait;
private function getTestException($msg, &$ref = null)
{
return new \Exception(''.$msg);
}
protected function tearDown()
{
ExceptionCaster::$srcContext = 1;
ExceptionCaster::$traceArgs = true;
}
public function testDefaultSettings()
{
$ref = array('foo');
$e = $this->getTestException('foo', $ref);
$expectedDump = <<<'EODUMP'
Exception {
#message: "foo"
#code: 0
#file: "%sExceptionCasterTest.php"
#line: 27
-trace: {
%sExceptionCasterTest.php:27: {
: {
: return new \Exception(''.$msg);
: }
}
%sExceptionCasterTest.php:%d: {
: $ref = array('foo');
: $e = $this->getTestException('foo', $ref);
:
arguments: {
$msg: "foo"
&$ref: array:1 [ …1]
}
}
%A
EODUMP;
$this->assertDumpMatchesFormat($expectedDump, $e);
$this->assertSame(array('foo'), $ref);
}
public function testSeek()
{
$e = $this->getTestException(2);
$expectedDump = <<<'EODUMP'
{
%sExceptionCasterTest.php:27: {
: {
: return new \Exception(''.$msg);
: }
}
%sExceptionCasterTest.php:%d: {
: {
: $e = $this->getTestException(2);
:
arguments: {
$msg: 2
}
}
%A
EODUMP;
$this->assertStringMatchesFormat($expectedDump, $this->getDump($e, 'trace'));
}
public function testNoArgs()
{
$e = $this->getTestException(1);
ExceptionCaster::$traceArgs = false;
$expectedDump = <<<'EODUMP'
Exception {
#message: "1"
#code: 0
#file: "%sExceptionCasterTest.php"
#line: 27
-trace: {
%sExceptionCasterTest.php:27: {
: {
: return new \Exception(''.$msg);
: }
}
%sExceptionCasterTest.php:%d: {
: {
: $e = $this->getTestException(1);
: ExceptionCaster::$traceArgs = false;
}
%A
EODUMP;
$this->assertDumpMatchesFormat($expectedDump, $e);
}
public function testNoSrcContext()
{
$e = $this->getTestException(1);
ExceptionCaster::$srcContext = -1;
$expectedDump = <<<'EODUMP'
Exception {
#message: "1"
#code: 0
#file: "%sExceptionCasterTest.php"
#line: 27
-trace: {
%sExceptionCasterTest.php: 27
%sExceptionCasterTest.php: %d
%A
EODUMP;
$this->assertDumpMatchesFormat($expectedDump, $e);
}
public function testHtmlDump()
{
$e = $this->getTestException(1);
ExceptionCaster::$srcContext = -1;
$cloner = new VarCloner();
$cloner->setMaxItems(1);
$dumper = new HtmlDumper();
$dumper->setDumpHeader('<foo></foo>');
$dumper->setDumpBoundaries('<bar>', '</bar>');
$dump = $dumper->dump($cloner->cloneVar($e)->withRefHandles(false), true);
$expectedDump = <<<'EODUMP'
<foo></foo><bar><span class=sf-dump-note>Exception</span> {<samp>
#<span class=sf-dump-protected title="Protected property">message</span>: "<span class=sf-dump-str>1</span>"
#<span class=sf-dump-protected title="Protected property">code</span>: <span class=sf-dump-num>0</span>
#<span class=sf-dump-protected title="Protected property">file</span>: "<span class=sf-dump-str title="%sExceptionCasterTest.php
%d characters"><span class=sf-dump-ellipsis>%sTests</span>%eCaster%eExceptionCasterTest.php</span>"
#<span class=sf-dump-protected title="Protected property">line</span>: <span class=sf-dump-num>27</span>
-<span class=sf-dump-private title="Private property defined in class:&#10;`Exception`">trace</span>: {<samp>
<span class=sf-dump-meta title="%sExceptionCasterTest.php
Stack level %d."><span class=sf-dump-ellipsis>%sVarDumper%eTests</span>%eCaster%eExceptionCasterTest.php</span>: <span class=sf-dump-num>27</span>
&hellip;%d
</samp>}
</samp>}
</bar>
EODUMP;
$this->assertStringMatchesFormat($expectedDump, $dump);
}
/**
* @requires function Twig\Template::getSourceContext
*/
public function testFrameWithTwig()
{
require_once dirname(__DIR__).'/Fixtures/Twig.php';
$f = array(
new FrameStub(array(
'file' => dirname(__DIR__).'/Fixtures/Twig.php',
'line' => 20,
'class' => '__TwigTemplate_VarDumperFixture_u75a09',
)),
new FrameStub(array(
'file' => dirname(__DIR__).'/Fixtures/Twig.php',
'line' => 21,
'class' => '__TwigTemplate_VarDumperFixture_u75a09',
'object' => new \__TwigTemplate_VarDumperFixture_u75a09(null, __FILE__),
)),
);
$expectedDump = <<<'EODUMP'
array:2 [
0 => {
class: "__TwigTemplate_VarDumperFixture_u75a09"
src: {
%sTwig.php:1: {
:
: foo bar
: twig source
}
}
}
1 => {
class: "__TwigTemplate_VarDumperFixture_u75a09"
object: __TwigTemplate_VarDumperFixture_u75a09 {
%A
}
src: {
%sExceptionCasterTest.php:2: {
: foo bar
: twig source
:
}
}
}
]
EODUMP;
$this->assertDumpMatchesFormat($expectedDump, $f);
}
}

View File

@@ -11,14 +11,18 @@
namespace Symfony\Component\VarDumper\Tests\Caster; namespace Symfony\Component\VarDumper\Tests\Caster;
use PHPUnit\Framework\TestCase;
use Symfony\Component\VarDumper\Caster\PdoCaster; use Symfony\Component\VarDumper\Caster\PdoCaster;
use Symfony\Component\VarDumper\Cloner\Stub; use Symfony\Component\VarDumper\Cloner\Stub;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
/** /**
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*/ */
class PdoCasterTest extends \PHPUnit_Framework_TestCase class PdoCasterTest extends TestCase
{ {
use VarDumperTestTrait;
/** /**
* @requires extension pdo_sqlite * @requires extension pdo_sqlite
*/ */
@@ -36,22 +40,25 @@ class PdoCasterTest extends \PHPUnit_Framework_TestCase
$this->assertSame('NATURAL', $attr['CASE']->class); $this->assertSame('NATURAL', $attr['CASE']->class);
$this->assertSame('BOTH', $attr['DEFAULT_FETCH_MODE']->class); $this->assertSame('BOTH', $attr['DEFAULT_FETCH_MODE']->class);
$xCast = array( $xDump = <<<'EODUMP'
"\0~\0inTransaction" => $pdo->inTransaction(), array:2 [
"\0~\0attributes" => array( "\x00~\x00inTransaction" => false
'CASE' => $attr['CASE'], "\x00~\x00attributes" => array:9 [
'ERRMODE' => $attr['ERRMODE'], "CASE" => NATURAL
'PERSISTENT' => false, "ERRMODE" => SILENT
'DRIVER_NAME' => 'sqlite', "PERSISTENT" => false
'ORACLE_NULLS' => $attr['ORACLE_NULLS'], "DRIVER_NAME" => "sqlite"
'CLIENT_VERSION' => $pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION), "ORACLE_NULLS" => NATURAL
'SERVER_VERSION' => $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION), "CLIENT_VERSION" => "%s"
'STATEMENT_CLASS' => array('PDOStatement'), "SERVER_VERSION" => "%s"
'DEFAULT_FETCH_MODE' => $attr['DEFAULT_FETCH_MODE'], "STATEMENT_CLASS" => array:%d [
), 0 => "PDOStatement"%A
); ]
unset($cast["\0~\0attributes"]['STATEMENT_CLASS'][1]); "DEFAULT_FETCH_MODE" => BOTH
]
]
EODUMP;
$this->assertSame($xCast, $cast); $this->assertDumpMatchesFormat($xDump, $cast);
} }
} }

View File

@@ -0,0 +1,85 @@
<?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\Caster;
use PHPUnit\Framework\TestCase;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
/**
* @author Nicolas Grekas <p@tchwork.com>
* @requires extension redis
*/
class RedisCasterTest extends TestCase
{
use VarDumperTestTrait;
public function testNotConnected()
{
$redis = new \Redis();
if (defined('HHVM_VERSION_ID')) {
$xCast = <<<'EODUMP'
Redis {
#host: ""
%A
}
EODUMP;
} else {
$xCast = <<<'EODUMP'
Redis {
isConnected: false
}
EODUMP;
}
$this->assertDumpMatchesFormat($xCast, $redis);
}
public function testConnected()
{
$redis = new \Redis();
if (!@$redis->connect('127.0.0.1')) {
$e = error_get_last();
self::markTestSkipped($e['message']);
}
if (defined('HHVM_VERSION_ID')) {
$xCast = <<<'EODUMP'
Redis {
#host: "127.0.0.1"
%A
}
EODUMP;
} else {
$xCast = <<<'EODUMP'
Redis {
+"socket": Redis Socket Buffer resource
isConnected: true
host: "127.0.0.1"
port: 6379
auth: null
dbNum: 0
timeout: 0.0
persistentId: null
options: {
READ_TIMEOUT: 0.0
SERIALIZER: NONE
PREFIX: null
SCAN: NORETRY
}
}
EODUMP;
}
$this->assertDumpMatchesFormat($xCast, $redis);
}
}

View File

@@ -11,13 +11,15 @@
namespace Symfony\Component\VarDumper\Tests\Caster; namespace Symfony\Component\VarDumper\Tests\Caster;
use PHPUnit\Framework\TestCase;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait; use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
use Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo; use Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo;
use Symfony\Component\VarDumper\Tests\Fixtures\NotLoadableClass;
/** /**
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*/ */
class ReflectionCasterTest extends \PHPUnit_Framework_TestCase class ReflectionCasterTest extends TestCase
{ {
use VarDumperTestTrait; use VarDumperTestTrait;
@@ -49,7 +51,7 @@ ReflectionClass {
"export" => ReflectionMethod { "export" => ReflectionMethod {
+name: "export" +name: "export"
+class: "ReflectionClass" +class: "ReflectionClass"
parameters: { %A parameters: {
$%s: ReflectionParameter { $%s: ReflectionParameter {
%A position: 0 %A position: 0
%A %A
@@ -75,7 +77,7 @@ Closure {
\$b: & 123 \$b: & 123
} }
file: "%sReflectionCasterTest.php" file: "%sReflectionCasterTest.php"
line: "65 to 65" line: "67 to 67"
} }
EOTXT EOTXT
, $var , $var
@@ -91,7 +93,7 @@ EOTXT
ReflectionParameter { ReflectionParameter {
+name: "arg1" +name: "arg1"
position: 0 position: 0
typeHint: "Symfony\Component\VarDumper\Tests\Caster\NotExistingClass" typeHint: "Symfony\Component\VarDumper\Tests\Fixtures\NotLoadableClass"
default: null default: null
} }
EOTXT EOTXT
@@ -146,83 +148,88 @@ EOTXT
*/ */
public function testGenerator() public function testGenerator()
{ {
$g = new GeneratorDemo(); if (extension_loaded('xdebug')) {
$g = $g->baz(); $this->markTestSkipped('xdebug is active');
$r = new \ReflectionGenerator($g); }
$xDump = <<<'EODUMP' $generator = new GeneratorDemo();
$generator = $generator->baz();
$expectedDump = <<<'EODUMP'
Generator { Generator {
this: Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo { …} this: Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo { …}
executing: { executing: {
Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo->baz(): { Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo->baz(): {
%sGeneratorDemo.php:14: """ %sGeneratorDemo.php:14: {
{\n : {
yield from bar();\n : yield from bar();
}\n : }
""" }
} }
} }
closed: false
} }
EODUMP; EODUMP;
$this->assertDumpMatchesFormat($xDump, $g); $this->assertDumpMatchesFormat($expectedDump, $generator);
foreach ($g as $v) { foreach ($generator as $v) {
break; break;
} }
$xDump = <<<'EODUMP' $expectedDump = <<<'EODUMP'
array:2 [ array:2 [
0 => ReflectionGenerator { 0 => ReflectionGenerator {
this: Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo { …} this: Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo { …}
trace: { trace: {
3. Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo::foo() ==> yield(): { %sGeneratorDemo.php:9: {
src: { : {
%sGeneratorDemo.php:9: """ : yield 1;
{\n : }
yield 1;\n
}\n
"""
}
} }
2. Symfony\Component\VarDumper\Tests\Fixtures\bar() ==> Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo::foo(): { %sGeneratorDemo.php:20: {
src: { : {
%sGeneratorDemo.php:20: """ : yield from GeneratorDemo::foo();
{\n : }
yield from GeneratorDemo::foo();\n
}\n
"""
}
} }
1. Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo->baz() ==> Symfony\Component\VarDumper\Tests\Fixtures\bar(): { %sGeneratorDemo.php:14: {
src: { : {
%sGeneratorDemo.php:14: """ : yield from bar();
{\n : }
yield from bar();\n
}\n
"""
}
} }
} }
closed: false
} }
1 => Generator { 1 => Generator {
executing: { executing: {
Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo::foo(): { Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo::foo(): {
%sGeneratorDemo.php:10: """ %sGeneratorDemo.php:10: {
yield 1;\n : yield 1;
}\n : }
\n :
""" }
} }
} }
closed: false
} }
] ]
EODUMP; EODUMP;
$this->assertDumpMatchesFormat($xDump, array($r, $r->getExecutingGenerator())); $r = new \ReflectionGenerator($generator);
$this->assertDumpMatchesFormat($expectedDump, array($r, $r->getExecutingGenerator()));
foreach ($generator as $v) {
}
$expectedDump = <<<'EODUMP'
Generator {
closed: true
}
EODUMP;
$this->assertDumpMatchesFormat($expectedDump, $generator);
} }
} }
function reflectionParameterFixture(NotExistingClass $arg1 = null, $arg2) function reflectionParameterFixture(NotLoadableClass $arg1 = null, $arg2)
{ {
} }

View File

@@ -11,12 +11,13 @@
namespace Symfony\Component\VarDumper\Tests\Caster; namespace Symfony\Component\VarDumper\Tests\Caster;
use PHPUnit\Framework\TestCase;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait; use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
/** /**
* @author Grégoire Pineau <lyrixx@lyrixx.info> * @author Grégoire Pineau <lyrixx@lyrixx.info>
*/ */
class SplCasterTest extends \PHPUnit_Framework_TestCase class SplCasterTest extends TestCase
{ {
use VarDumperTestTrait; use VarDumperTestTrait;
@@ -57,12 +58,6 @@ SplFileInfo {
pathname: "https://google.com/about" pathname: "https://google.com/about"
extension: "" extension: ""
realPath: false realPath: false
writable: false
readable: false
executable: false
file: false
dir: false
link: false
%A} %A}
EOTXT EOTXT
), ),
@@ -102,10 +97,10 @@ SplFileObject {
file: true file: true
dir: false dir: false
link: false link: false
%AcsvControl: array:2 [ %AcsvControl: array:%d [
0 => "," 0 => ","
1 => """ 1 => """
] %A]
flags: DROP_NEW_LINE|SKIP_EMPTY flags: DROP_NEW_LINE|SKIP_EMPTY
maxLineLen: 0 maxLineLen: 0
fstat: array:26 [ fstat: array:26 [
@@ -123,4 +118,30 @@ SplFileObject {
EOTXT; EOTXT;
$this->assertDumpMatchesFormat($dump, $var); $this->assertDumpMatchesFormat($dump, $var);
} }
/**
* @dataProvider provideCastSplDoublyLinkedList
*/
public function testCastSplDoublyLinkedList($modeValue, $modeDump)
{
$var = new \SplDoublyLinkedList();
$var->setIteratorMode($modeValue);
$dump = <<<EOTXT
SplDoublyLinkedList {
%Amode: $modeDump
dllist: []
}
EOTXT;
$this->assertDumpMatchesFormat($dump, $var);
}
public function provideCastSplDoublyLinkedList()
{
return array(
array(\SplDoublyLinkedList::IT_MODE_FIFO, 'IT_MODE_FIFO | IT_MODE_KEEP'),
array(\SplDoublyLinkedList::IT_MODE_LIFO, 'IT_MODE_LIFO | IT_MODE_KEEP'),
array(\SplDoublyLinkedList::IT_MODE_FIFO | \SplDoublyLinkedList::IT_MODE_DELETE, 'IT_MODE_FIFO | IT_MODE_DELETE'),
array(\SplDoublyLinkedList::IT_MODE_LIFO | \SplDoublyLinkedList::IT_MODE_DELETE, 'IT_MODE_LIFO | IT_MODE_DELETE'),
);
}
} }

View File

@@ -0,0 +1,171 @@
<?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\Caster;
use PHPUnit\Framework\TestCase;
use Symfony\Component\VarDumper\Caster\ArgsStub;
use Symfony\Component\VarDumper\Caster\ClassStub;
use Symfony\Component\VarDumper\Caster\LinkStub;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
use Symfony\Component\VarDumper\Tests\Fixtures\FooInterface;
class StubCasterTest extends TestCase
{
use VarDumperTestTrait;
public function testArgsStubWithDefaults($foo = 234, $bar = 456)
{
$args = array(new ArgsStub(array(123), __FUNCTION__, __CLASS__));
$expectedDump = <<<'EODUMP'
array:1 [
0 => {
$foo: 123
}
]
EODUMP;
$this->assertDumpMatchesFormat($expectedDump, $args);
}
public function testArgsStubWithExtraArgs($foo = 234)
{
$args = array(new ArgsStub(array(123, 456), __FUNCTION__, __CLASS__));
$expectedDump = <<<'EODUMP'
array:1 [
0 => {
$foo: 123
...: {
456
}
}
]
EODUMP;
$this->assertDumpMatchesFormat($expectedDump, $args);
}
public function testArgsStubNoParamWithExtraArgs()
{
$args = array(new ArgsStub(array(123), __FUNCTION__, __CLASS__));
$expectedDump = <<<'EODUMP'
array:1 [
0 => {
123
}
]
EODUMP;
$this->assertDumpMatchesFormat($expectedDump, $args);
}
public function testArgsStubWithClosure()
{
$args = array(new ArgsStub(array(123), '{closure}', null));
$expectedDump = <<<'EODUMP'
array:1 [
0 => {
123
}
]
EODUMP;
$this->assertDumpMatchesFormat($expectedDump, $args);
}
public function testLinkStub()
{
$var = array(new LinkStub(__CLASS__, 0, __FILE__));
$cloner = new VarCloner();
$dumper = new HtmlDumper();
$dumper->setDumpHeader('<foo></foo>');
$dumper->setDumpBoundaries('<bar>', '</bar>');
$dumper->setDisplayOptions(array('fileLinkFormat' => '%f:%l'));
$dump = $dumper->dump($cloner->cloneVar($var), true);
$expectedDump = <<<'EODUMP'
<foo></foo><bar><span class=sf-dump-note>array:1</span> [<samp>
<span class=sf-dump-index>0</span> => "<a href="%sStubCasterTest.php:0" target="_blank" rel="noopener noreferrer"><span class=sf-dump-str title="55 characters">Symfony\Component\VarDumper\Tests\Caster\StubCasterTest</span></a>"
</samp>]
</bar>
EODUMP;
$this->assertStringMatchesFormat($expectedDump, $dump);
}
public function testClassStub()
{
$var = array(new ClassStub('hello', array(FooInterface::class, 'foo')));
$cloner = new VarCloner();
$dumper = new HtmlDumper();
$dumper->setDumpHeader('<foo></foo>');
$dumper->setDumpBoundaries('<bar>', '</bar>');
$dump = $dumper->dump($cloner->cloneVar($var), true, array('fileLinkFormat' => '%f:%l'));
$expectedDump = <<<'EODUMP'
<foo></foo><bar><span class=sf-dump-note>array:1</span> [<samp>
<span class=sf-dump-index>0</span> => "<a href="%sFooInterface.php:10" target="_blank" rel="noopener noreferrer"><span class=sf-dump-str title="5 characters">hello</span></a>"
</samp>]
</bar>
EODUMP;
$this->assertStringMatchesFormat($expectedDump, $dump);
}
public function testClassStubWithNotExistingClass()
{
$var = array(new ClassStub(NotExisting::class));
$cloner = new VarCloner();
$dumper = new HtmlDumper();
$dumper->setDumpHeader('<foo></foo>');
$dumper->setDumpBoundaries('<bar>', '</bar>');
$dump = $dumper->dump($cloner->cloneVar($var), true);
$expectedDump = <<<'EODUMP'
<foo></foo><bar><span class=sf-dump-note>array:1</span> [<samp>
<span class=sf-dump-index>0</span> => "<span class=sf-dump-str title="Symfony\Component\VarDumper\Tests\Caster\NotExisting
52 characters"><span class=sf-dump-ellipsis>Symfony\Component\VarDumper\Tests\Caster</span>\NotExisting</span>"
</samp>]
</bar>
EODUMP;
$this->assertStringMatchesFormat($expectedDump, $dump);
}
public function testClassStubWithNotExistingMethod()
{
$var = array(new ClassStub('hello', array(FooInterface::class, 'missing')));
$cloner = new VarCloner();
$dumper = new HtmlDumper();
$dumper->setDumpHeader('<foo></foo>');
$dumper->setDumpBoundaries('<bar>', '</bar>');
$dump = $dumper->dump($cloner->cloneVar($var), true, array('fileLinkFormat' => '%f:%l'));
$expectedDump = <<<'EODUMP'
<foo></foo><bar><span class=sf-dump-note>array:1</span> [<samp>
<span class=sf-dump-index>0</span> => "<a href="%sFooInterface.php:5" target="_blank" rel="noopener noreferrer"><span class=sf-dump-str title="5 characters">hello</span></a>"
</samp>]
</bar>
EODUMP;
$this->assertStringMatchesFormat($expectedDump, $dump);
}
}

View File

@@ -0,0 +1,248 @@
<?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\Caster;
use PHPUnit\Framework\TestCase;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
/**
* @author Baptiste Clavié <clavie.b@gmail.com>
*/
class XmlReaderCasterTest extends TestCase
{
use VarDumperTestTrait;
/** @var \XmlReader */
private $reader;
protected function setUp()
{
$this->reader = new \XmlReader();
$this->reader->open(__DIR__.'/../Fixtures/xml_reader.xml');
}
protected function tearDown()
{
$this->reader->close();
}
public function testParserProperty()
{
$this->reader->setParserProperty(\XMLReader::SUBST_ENTITIES, true);
$expectedDump = <<<'EODUMP'
XMLReader {
+nodeType: NONE
parserProperties: {
SUBST_ENTITIES: true
…3
}
…12
}
EODUMP;
$this->assertDumpMatchesFormat($expectedDump, $this->reader);
}
/**
* @dataProvider provideNodes
*/
public function testNodes($seek, $expectedDump)
{
while ($seek--) {
$this->reader->read();
}
$this->assertDumpMatchesFormat($expectedDump, $this->reader);
}
public function provideNodes()
{
return array(
array(0, <<<'EODUMP'
XMLReader {
+nodeType: NONE
…13
}
EODUMP
),
array(1, <<<'EODUMP'
XMLReader {
+localName: "foo"
+nodeType: ELEMENT
+baseURI: "%sxml_reader.xml"
…11
}
EODUMP
),
array(2, <<<'EODUMP'
XMLReader {
+localName: "#text"
+nodeType: SIGNIFICANT_WHITESPACE
+depth: 1
+value: """
\n
"""
+baseURI: "%sxml_reader.xml"
…9
}
EODUMP
),
array(3, <<<'EODUMP'
XMLReader {
+localName: "bar"
+nodeType: ELEMENT
+depth: 1
+baseURI: "%sxml_reader.xml"
…10
}
EODUMP
),
array(4, <<<'EODUMP'
XMLReader {
+localName: "bar"
+nodeType: END_ELEMENT
+depth: 1
+baseURI: "%sxml_reader.xml"
…10
}
EODUMP
),
array(6, <<<'EODUMP'
XMLReader {
+localName: "bar"
+nodeType: ELEMENT
+depth: 1
+isEmptyElement: true
+baseURI: "%sxml_reader.xml"
…9
}
EODUMP
),
array(9, <<<'EODUMP'
XMLReader {
+localName: "#text"
+nodeType: TEXT
+depth: 2
+value: "With text"
+baseURI: "%sxml_reader.xml"
…9
}
EODUMP
),
array(12, <<<'EODUMP'
XMLReader {
+localName: "bar"
+nodeType: ELEMENT
+depth: 1
+attributeCount: 2
+baseURI: "%sxml_reader.xml"
…9
}
EODUMP
),
array(13, <<<'EODUMP'
XMLReader {
+localName: "bar"
+nodeType: END_ELEMENT
+depth: 1
+baseURI: "%sxml_reader.xml"
…10
}
EODUMP
),
array(15, <<<'EODUMP'
XMLReader {
+localName: "bar"
+nodeType: ELEMENT
+depth: 1
+attributeCount: 1
+baseURI: "%sxml_reader.xml"
…9
}
EODUMP
),
array(16, <<<'EODUMP'
XMLReader {
+localName: "#text"
+nodeType: SIGNIFICANT_WHITESPACE
+depth: 2
+value: """
\n
"""
+baseURI: "%sxml_reader.xml"
…9
}
EODUMP
),
array(17, <<<'EODUMP'
XMLReader {
+localName: "baz"
+prefix: "baz"
+nodeType: ELEMENT
+depth: 2
+namespaceURI: "http://symfony.com"
+baseURI: "%sxml_reader.xml"
…8
}
EODUMP
),
array(18, <<<'EODUMP'
XMLReader {
+localName: "baz"
+prefix: "baz"
+nodeType: END_ELEMENT
+depth: 2
+namespaceURI: "http://symfony.com"
+baseURI: "%sxml_reader.xml"
…8
}
EODUMP
),
array(19, <<<'EODUMP'
XMLReader {
+localName: "#text"
+nodeType: SIGNIFICANT_WHITESPACE
+depth: 2
+value: """
\n
"""
+baseURI: "%sxml_reader.xml"
…9
}
EODUMP
),
array(21, <<<'EODUMP'
XMLReader {
+localName: "#text"
+nodeType: SIGNIFICANT_WHITESPACE
+depth: 1
+value: "\n"
+baseURI: "%sxml_reader.xml"
…9
}
EODUMP
),
array(22, <<<'EODUMP'
XMLReader {
+localName: "foo"
+nodeType: END_ELEMENT
+baseURI: "%sxml_reader.xml"
…11
}
EODUMP
),
);
}
}

View File

@@ -11,14 +11,17 @@
namespace Symfony\Component\VarDumper\Tests; namespace Symfony\Component\VarDumper\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\VarDumper\Cloner\VarCloner; use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper; use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait; use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
use Twig\Environment;
use Twig\Loader\FilesystemLoader;
/** /**
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*/ */
class CliDumperTest extends \PHPUnit_Framework_TestCase class CliDumperTest extends TestCase
{ {
use VarDumperTestTrait; use VarDumperTestTrait;
@@ -62,15 +65,12 @@ array:24 [
7 => b"é\\x00" 7 => b"é\\x00"
"[]" => [] "[]" => []
"res" => stream resource {@{$res} "res" => stream resource {@{$res}
wrapper_type: "plainfile" %A wrapper_type: "plainfile"
stream_type: "STDIO" stream_type: "STDIO"
mode: "r" mode: "r"
unread_bytes: 0 unread_bytes: 0
seekable: true seekable: true
timed_out: false %A options: []
blocked: true
eof: false
options: []
} }
"obj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {#%d "obj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {#%d
+foo: "foo" +foo: "foo"
@@ -118,7 +118,7 @@ EOTXT
$var = xml_parser_create(); $var = xml_parser_create();
$this->assertDumpMatchesFormat( $this->assertDumpMatchesFormat(
<<<EOTXT <<<'EOTXT'
xml resource { xml resource {
current_byte_index: %i current_byte_index: %i
current_column_number: %i current_column_number: %i
@@ -131,6 +131,72 @@ EOTXT
); );
} }
public function testJsonCast()
{
$var = (array) json_decode('{"0":{},"1":null}');
foreach ($var as &$v) {
}
$var[] = &$v;
$var[''] = 2;
if (\PHP_VERSION_ID >= 70200) {
$this->assertDumpMatchesFormat(
<<<'EOTXT'
array:4 [
0 => {}
1 => &1 null
2 => &1 null
"" => 2
]
EOTXT
,
$var
);
} else {
$this->assertDumpMatchesFormat(
<<<'EOTXT'
array:4 [
"0" => {}
"1" => &1 null
0 => &1 null
"" => 2
]
EOTXT
,
$var
);
}
}
public function testObjectCast()
{
$var = (object) array(1 => 1);
$var->{1} = 2;
if (\PHP_VERSION_ID >= 70200) {
$this->assertDumpMatchesFormat(
<<<'EOTXT'
{
+"1": 2
}
EOTXT
,
$var
);
} else {
$this->assertDumpMatchesFormat(
<<<'EOTXT'
{
+1: 1
+"1": 2
}
EOTXT
,
$var
);
}
}
public function testClosedResource() public function testClosedResource()
{ {
if (defined('HHVM_VERSION') && HHVM_VERSION_ID < 30600) { if (defined('HHVM_VERSION') && HHVM_VERSION_ID < 30600) {
@@ -152,7 +218,7 @@ EOTXT
$this->assertStringMatchesFormat( $this->assertStringMatchesFormat(
<<<EOTXT <<<EOTXT
Unknown resource @{$res} Closed resource @{$res}
EOTXT EOTXT
, ,
@@ -160,12 +226,47 @@ EOTXT
); );
} }
public function testFlags()
{
putenv('DUMP_LIGHT_ARRAY=1');
putenv('DUMP_STRING_LENGTH=1');
$var = array(
range(1, 3),
array('foo', 2 => 'bar'),
);
$this->assertDumpEquals(
<<<EOTXT
[
[
1
2
3
]
[
0 => (3) "foo"
2 => (3) "bar"
]
]
EOTXT
,
$var
);
putenv('DUMP_LIGHT_ARRAY=');
putenv('DUMP_STRING_LENGTH=');
}
/**
* @requires function Twig\Template::getSourceContext
*/
public function testThrowingCaster() public function testThrowingCaster()
{ {
$out = fopen('php://memory', 'r+b'); $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())); $twig = new \__TwigTemplate_VarDumperFixture_u75a09(new Environment(new FilesystemLoader()));
$dumper = new CliDumper(); $dumper = new CliDumper();
$dumper->setColors(false); $dumper->setColors(false);
@@ -181,96 +282,56 @@ EOTXT
':stream' => eval('return function () use ($twig) { ':stream' => eval('return function () use ($twig) {
try { try {
$twig->render(array()); $twig->render(array());
} catch (\Twig_Error_Runtime $e) { } catch (\Twig\Error\RuntimeError $e) {
throw $e->getPrevious(); throw $e->getPrevious();
} }
};'), };'),
)); ));
$line = __LINE__ - 2;
$ref = (int) $out; $ref = (int) $out;
$data = $cloner->cloneVar($out); $data = $cloner->cloneVar($out);
$dumper->dump($data, $out); $dumper->dump($data, $out);
rewind($out); $out = stream_get_contents($out, -1, 0);
$out = stream_get_contents($out);
if (method_exists($twig, 'getSource')) {
$twig = <<<EOTXT
foo.twig:2: """
foo bar\\n
twig source\\n
\\n
"""
EOTXT;
} else {
$twig = '';
}
$r = defined('HHVM_VERSION') ? '' : '#%d'; $r = defined('HHVM_VERSION') ? '' : '#%d';
$this->assertStringMatchesFormat( $this->assertStringMatchesFormat(
<<<EOTXT <<<EOTXT
stream resource {@{$ref} stream resource {@{$ref}
wrapper_type: "PHP" ⚠: Symfony\Component\VarDumper\Exception\ThrowingCasterException {{$r}
#message: "Unexpected Exception thrown from a caster: Foobar"
-trace: {
%sTwig.php:2: {
: foo bar
: twig source
:
}
%sTemplate.php:%d: {
: try {
: \$this->doDisplay(\$context, \$blocks);
: } catch (Twig%sError \$e) {
}
%sTemplate.php:%d: {
: {
: \$this->displayWithErrorHandling(\$this->env->mergeGlobals(\$context), array_merge(\$this->blocks, \$blocks));
: }
}
%sTemplate.php:%d: {
: try {
: \$this->display(\$context);
: } catch (%s \$e) {
}
%sCliDumperTest.php:%d: {
%A
}
}
}
%Awrapper_type: "PHP"
stream_type: "MEMORY" stream_type: "MEMORY"
mode: "%s+b" mode: "%s+b"
unread_bytes: 0 unread_bytes: 0
seekable: true seekable: true
uri: "php://memory" uri: "php://memory"
timed_out: false %Aoptions: []
blocked: true
eof: false
options: []
⚠: Symfony\Component\VarDumper\Exception\ThrowingCasterException {{$r}
#message: "Unexpected Exception thrown from a caster: Foobar"
-trace: {
%d. __TwigTemplate_VarDumperFixture_u75a09->doDisplay() ==> new Exception(): {
src: {
%sTwig.php:19: """
// line 2\\n
throw new \Exception('Foobar');\\n
}\\n
"""
{$twig} }
}
%d. Twig_Template->displayWithErrorHandling() ==> __TwigTemplate_VarDumperFixture_u75a09->doDisplay(): {
src: {
%sTemplate.php:%d: """
try {\\n
\$this->doDisplay(\$context, \$blocks);\\n
} catch (Twig_Error \$e) {\\n
"""
}
}
%d. Twig_Template->display() ==> Twig_Template->displayWithErrorHandling(): {
src: {
%sTemplate.php:%d: """
{\\n
\$this->displayWithErrorHandling(\$this->env->mergeGlobals(\$context), array_merge(\$this->blocks, \$blocks));\\n
}\\n
"""
}
}
%d. Twig_Template->render() ==> Twig_Template->display(): {
src: {
%sTemplate.php:%d: """
try {\\n
\$this->display(\$context);\\n
} catch (Exception \$e) {\\n
"""
}
}
%d. %slosure%s() ==> Twig_Template->render(): {
src: {
%sCliDumperTest.php:{$line}: """
}\\n
};'),\\n
));\\n
"""
}
}
}
}
} }
EOTXT EOTXT
@@ -288,11 +349,8 @@ EOTXT
$dumper->setColors(false); $dumper->setColors(false);
$cloner = new VarCloner(); $cloner = new VarCloner();
$out = fopen('php://memory', 'r+b');
$data = $cloner->cloneVar($var); $data = $cloner->cloneVar($var);
$dumper->dump($data, $out); $out = $dumper->dump($data, true);
rewind($out);
$out = stream_get_contents($out);
$r = defined('HHVM_VERSION') ? '' : '#%d'; $r = defined('HHVM_VERSION') ? '' : '#%d';
$this->assertStringMatchesFormat( $this->assertStringMatchesFormat(
@@ -318,7 +376,7 @@ EOTXT
$var = $this->getSpecialVars(); $var = $this->getSpecialVars();
$this->assertDumpEquals( $this->assertDumpEquals(
<<<EOTXT <<<'EOTXT'
array:3 [ array:3 [
0 => array:1 [ 0 => array:1 [
0 => &1 array:1 [ 0 => &1 array:1 [
@@ -364,7 +422,7 @@ EOTXT
$dumper->dump($data); $dumper->dump($data);
$this->assertSame( $this->assertSame(
<<<EOTXT <<<'EOTXT'
array:2 [ array:2 [
1 => array:1 [ 1 => array:1 [
"GLOBALS" => &1 array:1 [ "GLOBALS" => &1 array:1 [
@@ -386,7 +444,7 @@ EOTXT
*/ */
public function testBuggyRefs() public function testBuggyRefs()
{ {
if (PHP_VERSION_ID >= 50600) { if (\PHP_VERSION_ID >= 50600) {
$this->markTestSkipped('PHP 5.6 fixed refs counting'); $this->markTestSkipped('PHP 5.6 fixed refs counting');
} }
@@ -406,7 +464,7 @@ EOTXT
}); });
$this->assertSame( $this->assertSame(
<<<EOTXT <<<'EOTXT'
array:1 [ array:1 [
0 => array:1 [ 0 => array:1 [
0 => array:1 [ 0 => array:1 [
@@ -421,6 +479,21 @@ EOTXT
); );
} }
public function testIncompleteClass()
{
$unserializeCallbackHandler = ini_set('unserialize_callback_func', null);
$var = unserialize('O:8:"Foo\Buzz":0:{}');
ini_set('unserialize_callback_func', $unserializeCallbackHandler);
$this->assertDumpMatchesFormat(
<<<EOTXT
__PHP_Incomplete_Class(Foo\Buzz) {}
EOTXT
,
$var
);
}
private function getSpecialVars() private function getSpecialVars()
{ {
foreach (array_keys($GLOBALS) as $var) { foreach (array_keys($GLOBALS) as $var) {

View File

@@ -0,0 +1,11 @@
<?php
namespace Symfony\Component\VarDumper\Tests\Fixtures;
interface FooInterface
{
/**
* Hello.
*/
public function foo();
}

View File

@@ -0,0 +1,7 @@
<?php
namespace Symfony\Component\VarDumper\Tests\Fixtures;
class NotLoadableClass extends NotLoadableClass
{
}

View File

@@ -1,16 +1,18 @@
<?php <?php
/* foo.twig */ /* foo.twig */
class __TwigTemplate_VarDumperFixture_u75a09 extends Twig_Template class __TwigTemplate_VarDumperFixture_u75a09 extends Twig\Template
{ {
public function __construct(Twig_Environment $env) private $path;
public function __construct(Twig\Environment $env = null, $path = null)
{ {
parent::__construct($env); if (null !== $env) {
parent::__construct($env);
}
$this->parent = false; $this->parent = false;
$this->blocks = array();
$this->blocks = array( $this->path = $path;
);
} }
protected function doDisplay(array $context, array $blocks = array()) protected function doDisplay(array $context, array $blocks = array())
@@ -26,9 +28,11 @@ class __TwigTemplate_VarDumperFixture_u75a09 extends Twig_Template
public function getDebugInfo() public function getDebugInfo()
{ {
return array (19 => 2); return array(20 => 1, 21 => 2);
}
public function getSourceContext()
{
return new Twig\Source(" foo bar\n twig source\n\n", 'foo.twig', $this->path ?: __FILE__);
} }
} }
/* foo bar*/
/* twig source*/
/* */

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="UTF-8"?>
<foo>
<bar></bar>
<bar />
<bar>With text</bar>
<bar foo="bar" baz="fubar"></bar>
<bar xmlns:baz="http://symfony.com">
<baz:baz></baz:baz>
</bar>
</foo>

View File

@@ -11,13 +11,14 @@
namespace Symfony\Component\VarDumper\Tests; namespace Symfony\Component\VarDumper\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\VarDumper\Cloner\VarCloner; use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\HtmlDumper; use Symfony\Component\VarDumper\Dumper\HtmlDumper;
/** /**
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*/ */
class HtmlDumperTest extends \PHPUnit_Framework_TestCase class HtmlDumperTest extends TestCase
{ {
public function testGet() public function testGet()
{ {
@@ -59,26 +60,24 @@ class HtmlDumperTest extends \PHPUnit_Framework_TestCase
<span class=sf-dump-key>4</span> => <span class=sf-dump-num>INF</span> <span class=sf-dump-key>4</span> => <span class=sf-dump-num>INF</span>
<span class=sf-dump-key>5</span> => <span class=sf-dump-num>-INF</span> <span class=sf-dump-key>5</span> => <span class=sf-dump-num>-INF</span>
<span class=sf-dump-key>6</span> => <span class=sf-dump-num>{$intMax}</span> <span class=sf-dump-key>6</span> => <span class=sf-dump-num>{$intMax}</span>
"<span class=sf-dump-key>str</span>" => "<span class=sf-dump-str title="5 characters">d&%s;j&%s;</span>\\n" "<span class=sf-dump-key>str</span>" => "<span class=sf-dump-str title="5 characters">d&%s;j&%s;<span class=sf-dump-default>\\n</span></span>"
<span class=sf-dump-key>7</span> => b"<span class=sf-dump-str title="2 binary or non-UTF-8 characters">&%s;</span>\\x00" <span class=sf-dump-key>7</span> => b"<span class=sf-dump-str title="2 binary or non-UTF-8 characters">&%s;<span class=sf-dump-default>\\x00</span></span>"
"<span class=sf-dump-key>[]</span>" => [] "<span class=sf-dump-key>[]</span>" => []
"<span class=sf-dump-key>res</span>" => <span class=sf-dump-note>stream resource</span> <a class=sf-dump-ref>@{$res}</a><samp> "<span class=sf-dump-key>res</span>" => <span class=sf-dump-note>stream resource</span> <a class=sf-dump-ref>@{$res}</a><samp>
<span class=sf-dump-meta>wrapper_type</span>: "<span class=sf-dump-str title="9 characters">plainfile</span>" %A <span class=sf-dump-meta>wrapper_type</span>: "<span class=sf-dump-str title="9 characters">plainfile</span>"
<span class=sf-dump-meta>stream_type</span>: "<span class=sf-dump-str title="5 characters">STDIO</span>" <span class=sf-dump-meta>stream_type</span>: "<span class=sf-dump-str title="5 characters">STDIO</span>"
<span class=sf-dump-meta>mode</span>: "<span class=sf-dump-str>r</span>" <span class=sf-dump-meta>mode</span>: "<span class=sf-dump-str>r</span>"
<span class=sf-dump-meta>unread_bytes</span>: <span class=sf-dump-num>0</span> <span class=sf-dump-meta>unread_bytes</span>: <span class=sf-dump-num>0</span>
<span class=sf-dump-meta>seekable</span>: <span class=sf-dump-const>true</span> <span class=sf-dump-meta>seekable</span>: <span class=sf-dump-const>true</span>
<span class=sf-dump-meta>timed_out</span>: <span class=sf-dump-const>false</span> %A <span class=sf-dump-meta>options</span>: []
<span class=sf-dump-meta>blocked</span>: <span class=sf-dump-const>true</span>
<span class=sf-dump-meta>eof</span>: <span class=sf-dump-const>false</span>
<span class=sf-dump-meta>options</span>: []
</samp>} </samp>}
"<span class=sf-dump-key>obj</span>" => <abbr title="Symfony\Component\VarDumper\Tests\Fixture\DumbFoo" class=sf-dump-note>DumbFoo</abbr> {<a class=sf-dump-ref href=#{$dumpId}-ref2%d title="2 occurrences">#%d</a><samp id={$dumpId}-ref2%d> "<span class=sf-dump-key>obj</span>" => <abbr title="Symfony\Component\VarDumper\Tests\Fixture\DumbFoo" class=sf-dump-note>DumbFoo</abbr> {<a class=sf-dump-ref href=#{$dumpId}-ref2%d title="2 occurrences">#%d</a><samp id={$dumpId}-ref2%d>
+<span class=sf-dump-public title="Public property">foo</span>: "<span class=sf-dump-str title="3 characters">foo</span>" +<span class=sf-dump-public title="Public property">foo</span>: "<span class=sf-dump-str title="3 characters">foo</span>"
+"<span class=sf-dump-public title="Runtime added dynamic property">bar</span>": "<span class=sf-dump-str title="3 characters">bar</span>" +"<span class=sf-dump-public title="Runtime added dynamic property">bar</span>": "<span class=sf-dump-str title="3 characters">bar</span>"
</samp>} </samp>}
"<span class=sf-dump-key>closure</span>" => <span class=sf-dump-note>Closure</span> {{$r}<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="48 characters">Symfony\Component\VarDumper\Tests\HtmlDumperTest</span>" <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>this</span>: <abbr title="Symfony\Component\VarDumper\Tests\HtmlDumperTest" class=sf-dump-note>HtmlDumperTest</abbr> {{$r} &%s;}
<span class=sf-dump-meta>parameters</span>: {<samp> <span class=sf-dump-meta>parameters</span>: {<samp>
<span class=sf-dump-meta>\$a</span>: {} <span class=sf-dump-meta>\$a</span>: {}
@@ -87,7 +86,8 @@ class HtmlDumperTest extends \PHPUnit_Framework_TestCase
<span class=sf-dump-meta>default</span>: <span class=sf-dump-const>null</span> <span class=sf-dump-meta>default</span>: <span class=sf-dump-const>null</span>
</samp>} </samp>}
</samp>} </samp>}
<span class=sf-dump-meta>file</span>: "<span class=sf-dump-str title="%d characters">{$var['file']}</span>" <span class=sf-dump-meta>file</span>: "<span class=sf-dump-str title="{$var['file']}
%d characters"><span class=sf-dump-ellipsis>%sTests</span>%eFixtures%edumb-var.php</span>"
<span class=sf-dump-meta>line</span>: "<span class=sf-dump-str title="%d characters">{$var['line']} to {$var['line']}</span>" <span class=sf-dump-meta>line</span>: "<span class=sf-dump-str title="%d characters">{$var['line']} to {$var['line']}</span>"
</samp>} </samp>}
"<span class=sf-dump-key>line</span>" => <span class=sf-dump-num>{$var['line']}</span> "<span class=sf-dump-key>line</span>" => <span class=sf-dump-num>{$var['line']}</span>
@@ -123,19 +123,41 @@ EOTXT
$cloner = new VarCloner(); $cloner = new VarCloner();
$data = $cloner->cloneVar($var); $data = $cloner->cloneVar($var);
$out = fopen('php://memory', 'r+b'); $out = $dumper->dump($data, true);
$dumper->dump($data, $out);
rewind($out);
$out = stream_get_contents($out);
$this->assertStringMatchesFormat( $this->assertStringMatchesFormat(
<<<EOTXT <<<'EOTXT'
<foo></foo><bar>b"<span class=sf-dump-str title="7 binary or non-UTF-8 characters">&#1057;&#1083;&#1086;&#1074;&#1072;&#1088;&#1100;</span>" <foo></foo><bar>b"<span class=sf-dump-str title="7 binary or non-UTF-8 characters">&#1057;&#1083;&#1086;&#1074;&#1072;&#1088;&#1100;</span>"
</bar> </bar>
EOTXT EOTXT
, ,
$out
);
}
public function testAppend()
{
$out = fopen('php://memory', 'r+b');
$dumper = new HtmlDumper();
$dumper->setDumpHeader('<foo></foo>');
$dumper->setDumpBoundaries('<bar>', '</bar>');
$cloner = new VarCloner();
$dumper->dump($cloner->cloneVar(123), $out);
$dumper->dump($cloner->cloneVar(456), $out);
$out = stream_get_contents($out, -1, 0);
$this->assertSame(<<<'EOTXT'
<foo></foo><bar><span class=sf-dump-num>123</span>
</bar>
<bar><span class=sf-dump-num>456</span>
</bar>
EOTXT
,
$out $out
); );
} }

View File

@@ -11,9 +11,10 @@
namespace Symfony\Component\VarDumper\Tests\Test; namespace Symfony\Component\VarDumper\Tests\Test;
use PHPUnit\Framework\TestCase;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait; use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
class VarDumperTestTraitTest extends \PHPUnit_Framework_TestCase class VarDumperTestTraitTest extends TestCase
{ {
use VarDumperTestTrait; use VarDumperTestTrait;

View File

@@ -11,12 +11,13 @@
namespace Symfony\Component\VarDumper\Tests; namespace Symfony\Component\VarDumper\Tests;
use PHPUnit\Framework\TestCase;
use Symfony\Component\VarDumper\Cloner\VarCloner; use Symfony\Component\VarDumper\Cloner\VarCloner;
/** /**
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*/ */
class VarClonerTest extends \PHPUnit_Framework_TestCase class VarClonerTest extends TestCase
{ {
public function testMaxIntBoundary() public function testMaxIntBoundary()
{ {
@@ -41,6 +42,10 @@ Symfony\Component\VarDumper\Cloner\Data Object
[handle] => 0 [handle] => 0
[refCount] => 0 [refCount] => 0
[position] => 1 [position] => 1
[attr] => Array
(
)
) )
) )
@@ -52,6 +57,8 @@ Symfony\Component\VarDumper\Cloner\Data Object
) )
[position:Symfony\Component\VarDumper\Cloner\Data:private] => 0
[key:Symfony\Component\VarDumper\Cloner\Data:private] => 0
[maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20 [maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
[maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1 [maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
[useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1 [useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
@@ -84,6 +91,10 @@ Symfony\Component\VarDumper\Cloner\Data Object
[handle] => %i [handle] => %i
[refCount] => 0 [refCount] => 0
[position] => 1 [position] => 1
[attr] => Array
(
)
) )
) )
@@ -99,6 +110,10 @@ Symfony\Component\VarDumper\Cloner\Data Object
[handle] => %i [handle] => %i
[refCount] => 0 [refCount] => 0
[position] => 2 [position] => 2
[attr] => Array
(
)
) )
[\000+\0002] => Symfony\Component\VarDumper\Cloner\Stub Object [\000+\0002] => Symfony\Component\VarDumper\Cloner\Stub Object
@@ -110,6 +125,10 @@ Symfony\Component\VarDumper\Cloner\Data Object
[handle] => %i [handle] => %i
[refCount] => 0 [refCount] => 0
[position] => 3 [position] => 3
[attr] => Array
(
)
) )
) )
@@ -126,6 +145,8 @@ Symfony\Component\VarDumper\Cloner\Data Object
) )
[position:Symfony\Component\VarDumper\Cloner\Data:private] => 0
[key:Symfony\Component\VarDumper\Cloner\Data:private] => 0
[maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20 [maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
[maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1 [maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
[useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1 [useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
@@ -135,6 +156,86 @@ EOTXT;
$this->assertStringMatchesFormat($expected, print_r($clone, true)); $this->assertStringMatchesFormat($expected, print_r($clone, true));
} }
public function testJsonCast()
{
if (ini_get('xdebug.overload_var_dump') == 2) {
$this->markTestSkipped('xdebug is active');
}
$data = (array) json_decode('{"1":{}}');
$cloner = new VarCloner();
$clone = $cloner->cloneVar($data);
$expected = <<<'EOTXT'
object(Symfony\Component\VarDumper\Cloner\Data)#%i (6) {
["data":"Symfony\Component\VarDumper\Cloner\Data":private]=>
array(2) {
[0]=>
array(1) {
[0]=>
object(Symfony\Component\VarDumper\Cloner\Stub)#%i (8) {
["type"]=>
string(5) "array"
["class"]=>
string(5) "assoc"
["value"]=>
int(1)
["cut"]=>
int(0)
["handle"]=>
int(0)
["refCount"]=>
int(0)
["position"]=>
int(1)
["attr"]=>
array(0) {
}
}
}
[1]=>
array(1) {
["1"]=>
object(Symfony\Component\VarDumper\Cloner\Stub)#%i (8) {
["type"]=>
string(6) "object"
["class"]=>
string(8) "stdClass"
["value"]=>
NULL
["cut"]=>
int(0)
["handle"]=>
int(%i)
["refCount"]=>
int(0)
["position"]=>
int(0)
["attr"]=>
array(0) {
}
}
}
}
["position":"Symfony\Component\VarDumper\Cloner\Data":private]=>
int(0)
["key":"Symfony\Component\VarDumper\Cloner\Data":private]=>
int(0)
["maxDepth":"Symfony\Component\VarDumper\Cloner\Data":private]=>
int(20)
["maxItemsPerDepth":"Symfony\Component\VarDumper\Cloner\Data":private]=>
int(-1)
["useRefHandles":"Symfony\Component\VarDumper\Cloner\Data":private]=>
int(-1)
}
EOTXT;
ob_start();
var_dump($clone);
$this->assertStringMatchesFormat(\PHP_VERSION_ID >= 70200 ? str_replace('"1"', '1', $expected) : $expected, ob_get_clean());
}
public function testCaster() public function testCaster()
{ {
$cloner = new VarCloner(array( $cloner = new VarCloner(array(
@@ -165,6 +266,10 @@ Symfony\Component\VarDumper\Cloner\Data Object
[handle] => %i [handle] => %i
[refCount] => 0 [refCount] => 0
[position] => 1 [position] => 1
[attr] => Array
(
)
) )
) )
@@ -176,6 +281,8 @@ Symfony\Component\VarDumper\Cloner\Data Object
) )
[position:Symfony\Component\VarDumper\Cloner\Data:private] => 0
[key:Symfony\Component\VarDumper\Cloner\Data:private] => 0
[maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20 [maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
[maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1 [maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
[useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1 [useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1

View File

@@ -20,9 +20,14 @@
"symfony/polyfill-mbstring": "~1.0" "symfony/polyfill-mbstring": "~1.0"
}, },
"require-dev": { "require-dev": {
"twig/twig": "~1.20|~2.0" "ext-iconv": "*",
"twig/twig": "~1.34|~2.4"
},
"conflict": {
"phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0"
}, },
"suggest": { "suggest": {
"ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).",
"ext-symfony_debug": "" "ext-symfony_debug": ""
}, },
"autoload": { "autoload": {
@@ -35,7 +40,7 @@
"minimum-stability": "dev", "minimum-stability": "dev",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "3.0-dev" "dev-master": "3.2-dev"
} }
} }
} }

View File

@@ -5,9 +5,13 @@
backupGlobals="false" backupGlobals="false"
colors="true" colors="true"
bootstrap="vendor/autoload.php" bootstrap="vendor/autoload.php"
failOnRisky="true"
failOnWarning="true"
> >
<php> <php>
<ini name="error_reporting" value="-1" /> <ini name="error_reporting" value="-1" />
<env name="DUMP_LIGHT_ARRAY" value="" />
<env name="DUMP_STRING_LENGTH" value="" />
</php> </php>
<testsuites> <testsuites>