debugbar ok with php5.6

This commit is contained in:
Laurent Destailleur 2020-09-08 02:59:49 +02:00
parent 4db561656e
commit 198b8d8d2b
83 changed files with 2742 additions and 5176 deletions

View File

@ -277,7 +277,10 @@ script:
# Ensure we catch errors # Ensure we catch errors
set -e set -e
#parallel-lint --exclude htdocs/includes --blame . #parallel-lint --exclude htdocs/includes --blame .
parallel-lint --exclude dev/namespacemig --exclude htdocs/includes/myclabs --exclude htdocs/includes/webmozart --exclude htdocs/includes/phpspec --exclude dev/initdata/dbf/includes --exclude htdocs/includes/sabre --exclude htdocs/includes/phpoffice/phpexcel/Classes/PHPExcel/Shared --exclude htdocs/includes/phpoffice/PhpSpreadsheet --exclude htdocs/includes/sebastian --exclude htdocs/includes/squizlabs/php_codesniffer --exclude htdocs/includes/jakub-onderka --exclude htdocs/includes/php-parallel-lint --exclude htdocs/includes/mike42/escpos-php/example --exclude htdocs/includes/phpunit/ --exclude htdocs/includes/composer/autoload_static.php --blame . parallel-lint --exclude dev/namespacemig --exclude htdocs/includes/myclabs --exclude htdocs/includes/webmozart --exclude htdocs/includes/phpspec --exclude dev/initdata/dbf/includes \
--exclude htdocs/includes/sabre --exclude htdocs/includes/phpoffice/phpexcel/Classes/PHPExcel/Shared --exclude htdocs/includes/phpoffice/PhpSpreadsheet --exclude htdocs/includes/sebastian \
--exclude htdocs/includes/squizlabs/php_codesniffer --exclude htdocs/includes/jakub-onderka --exclude htdocs/includes/php-parallel-lint --exclude htdocs/includes/symfony --exclude htdocs/includes/mike42/escpos-php/example \
--exclude htdocs/includes/phpunit/ --exclude htdocs/includes/composer/autoload_static.php --blame .
set +e set +e
echo echo

View File

@ -34,7 +34,8 @@
"tecnickcom/tcpdf" : "6.3.2", "tecnickcom/tcpdf" : "6.3.2",
"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"
}, },
"require-dev" : { "require-dev" : {
"php-parallel-lint/php-parallel-lint" : "^0", "php-parallel-lint/php-parallel-lint" : "^0",

356
composer.lock generated
View File

@ -4,7 +4,7 @@
"Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies",
"This file is @generated automatically" "This file is @generated automatically"
], ],
"content-hash": "9a83e3eaefb0843af09f39b0bc958a90", "content-hash": "1dbd2d05cc0836acfca5988f29005cf2",
"packages": [ "packages": [
{ {
"name": "ckeditor/ckeditor", "name": "ckeditor/ckeditor",
@ -555,201 +555,34 @@
], ],
"time": "2020-07-14T12:35:20+00:00" "time": "2020-07-14T12:35:20+00:00"
}, },
{
"name": "symfony/polyfill-php72",
"version": "v1.18.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php72.git",
"reference": "639447d008615574653fb3bc60d1986d7172eaae"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/639447d008615574653fb3bc60d1986d7172eaae",
"reference": "639447d008615574653fb3bc60d1986d7172eaae",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.18-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php72\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-14T12:35:20+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.18.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/d87d5766cbf48d72388a9f6b85f280c8ad51f981",
"reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981",
"shasum": ""
},
"require": {
"php": ">=7.0.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.18-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php80\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ion Bazan",
"email": "ion.bazan@gmail.com"
},
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-14T12:35:20+00:00"
},
{ {
"name": "symfony/var-dumper", "name": "symfony/var-dumper",
"version": "v4.4.13", "version": "v3.0.0",
"source": { "source": {
"type": "git", "type": "git",
"url": "https://github.com/symfony/var-dumper.git", "url": "https://github.com/symfony/var-dumper.git",
"reference": "1bef32329f3166486ab7cb88599cae4875632b99" "reference": "737e07704cca83f9dd0af926d45ce27eedc25657"
}, },
"dist": { "dist": {
"type": "zip", "type": "zip",
"url": "https://api.github.com/repos/symfony/var-dumper/zipball/1bef32329f3166486ab7cb88599cae4875632b99", "url": "https://api.github.com/repos/symfony/var-dumper/zipball/737e07704cca83f9dd0af926d45ce27eedc25657",
"reference": "1bef32329f3166486ab7cb88599cae4875632b99", "reference": "737e07704cca83f9dd0af926d45ce27eedc25657",
"shasum": "" "shasum": ""
}, },
"require": { "require": {
"php": ">=7.1.3", "php": ">=5.5.9",
"symfony/polyfill-mbstring": "~1.0", "symfony/polyfill-mbstring": "~1.0"
"symfony/polyfill-php72": "~1.5",
"symfony/polyfill-php80": "^1.15"
},
"conflict": {
"phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0",
"symfony/console": "<3.4"
}, },
"require-dev": { "require-dev": {
"ext-iconv": "*", "twig/twig": "~1.20|~2.0"
"symfony/console": "^3.4|^4.0|^5.0",
"symfony/process": "^4.4|^5.0",
"twig/twig": "^1.34|^2.4|^3.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-intl": "To show region name in time zone dump",
"symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script"
}, },
"bin": [
"Resources/bin/var-dump-server"
],
"type": "library", "type": "library",
"extra": { "extra": {
"branch-alias": { "branch-alias": {
"dev-master": "4.4-dev" "dev-master": "3.0-dev"
} }
}, },
"autoload": { "autoload": {
@ -783,21 +616,7 @@
"debug", "debug",
"dump" "dump"
], ],
"funding": [ "time": "2015-11-18T13:48:51+00:00"
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-08-17T07:31:35+00:00"
}, },
{ {
"name": "tecnickcom/tcpdf", "name": "tecnickcom/tcpdf",
@ -2218,6 +2037,159 @@
], ],
"time": "2019-08-06T08:03:45+00:00" "time": "2019-08-06T08:03:45+00:00"
}, },
{
"name": "symfony/polyfill-php72",
"version": "v1.18.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php72.git",
"reference": "639447d008615574653fb3bc60d1986d7172eaae"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php72/zipball/639447d008615574653fb3bc60d1986d7172eaae",
"reference": "639447d008615574653fb3bc60d1986d7172eaae",
"shasum": ""
},
"require": {
"php": ">=5.3.3"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.18-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php72\\": ""
},
"files": [
"bootstrap.php"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 7.2+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-14T12:35:20+00:00"
},
{
"name": "symfony/polyfill-php80",
"version": "v1.18.1",
"source": {
"type": "git",
"url": "https://github.com/symfony/polyfill-php80.git",
"reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981"
},
"dist": {
"type": "zip",
"url": "https://api.github.com/repos/symfony/polyfill-php80/zipball/d87d5766cbf48d72388a9f6b85f280c8ad51f981",
"reference": "d87d5766cbf48d72388a9f6b85f280c8ad51f981",
"shasum": ""
},
"require": {
"php": ">=7.0.8"
},
"type": "library",
"extra": {
"branch-alias": {
"dev-master": "1.18-dev"
},
"thanks": {
"name": "symfony/polyfill",
"url": "https://github.com/symfony/polyfill"
}
},
"autoload": {
"psr-4": {
"Symfony\\Polyfill\\Php80\\": ""
},
"files": [
"bootstrap.php"
],
"classmap": [
"Resources/stubs"
]
},
"notification-url": "https://packagist.org/downloads/",
"license": [
"MIT"
],
"authors": [
{
"name": "Ion Bazan",
"email": "ion.bazan@gmail.com"
},
{
"name": "Nicolas Grekas",
"email": "p@tchwork.com"
},
{
"name": "Symfony Community",
"homepage": "https://symfony.com/contributors"
}
],
"description": "Symfony polyfill backporting some PHP 8.0+ features to lower PHP versions",
"homepage": "https://symfony.com",
"keywords": [
"compatibility",
"polyfill",
"portable",
"shim"
],
"funding": [
{
"url": "https://symfony.com/sponsor",
"type": "custom"
},
{
"url": "https://github.com/fabpot",
"type": "github"
},
{
"url": "https://tidelift.com/funding/github/packagist/symfony/symfony",
"type": "tidelift"
}
],
"time": "2020-07-14T12:35:20+00:00"
},
{ {
"name": "symfony/yaml", "name": "symfony/yaml",
"version": "v3.4.32", "version": "v3.4.32",

View File

@ -114,16 +114,29 @@ if (!function_exists("session_id"))
} }
print '</td></tr>'; print '</td></tr>';
print '<tr><td>UTF-8 support</td><td>'; print '<tr><td>UTF-8 support</td><td>';
if (!function_exists("utf8_encode")) if (!function_exists("utf8_encode"))
{ {
print '<img src="'.$WarningPicturePath.'" alt="Warning"> '.$langs->trans("ErrorPHPDoesNotSupportUTF8"); print '<img src="'.$WarningPicturePath.'" alt="Warning"> '.$langs->trans("ErrorPHPDoesNotSupport", "UTF8");
} else { } else {
print '<img src="'.$OkayPicturePath.'" alt="Ok"> '.$langs->trans("PHPSupportUTF8"); print '<img src="'.$OkayPicturePath.'" alt="Ok"> '.$langs->trans("PHPSupport", "UTF8");
} }
print '</td></tr>'; print '</td></tr>';
print '<tr><td>MBString support</td><td>';
if (!function_exists("mb_check_encoding"))
{
print '<img src="'.$WarningPicturePath.'" alt="Warning"> '.$langs->trans("ErrorPHPDoesNotSupport", "mbstring");
} else {
print '<img src="'.$OkayPicturePath.'" alt="Ok"> '.$langs->trans("PHPSupport", "mbstring");
}
print '</td></tr>';
print '</table>'; print '</table>';
print '<br>'; print '<br>';

View File

@ -1,53 +1,7 @@
CHANGELOG CHANGELOG
========= =========
4.4.0
-----
* added `VarDumperTestTrait::setUpVarDumper()` and `VarDumperTestTrait::tearDownVarDumper()`
to configure casters & flags to use in tests
* added `ImagineCaster` and infrastructure to dump images
* added the stamps of a message after it is dispatched in `TraceableMessageBus` and `MessengerDataCollector` collected data
* added `UuidCaster`
* made all casters final
* added support for the `NO_COLOR` env var (https://no-color.org/)
4.3.0
-----
* added `DsCaster` to support dumping the contents of data structures from the Ds extension
4.2.0
-----
* support selecting the format to use by setting the environment variable `VAR_DUMPER_FORMAT` to `html` or `cli`
4.1.0
-----
* added a `ServerDumper` to send serialized Data clones to a server
* added a `ServerDumpCommand` and `DumpServer` to run a server collecting
and displaying dumps on a single place with multiple formats support
* added `CliDescriptor` and `HtmlDescriptor` descriptors for `server:dump` CLI and HTML formats support
4.0.0
-----
* support for passing `\ReflectionClass` instances to the `Caster::castObject()`
method has been dropped, pass class names as strings instead
* the `Data::getRawData()` method has been removed
* the `VarDumperTestTrait::assertDumpEquals()` method expects a 3rd `$filter = 0`
argument and moves `$message = ''` argument at 4th position.
* the `VarDumperTestTrait::assertDumpMatchesFormat()` method expects a 3rd `$filter = 0`
argument and moves `$message = ''` argument at 4th position.
3.4.0
-----
* added `AbstractCloner::setMinDepth()` function to ensure minimum tree depth
* deprecated `MongoCaster`
2.7.0 2.7.0
----- -----
* deprecated `Cloner\Data::getLimitedClone()`. Use `withMaxDepth`, `withMaxItemsPerDepth` or `withRefHandles` instead. * deprecated Cloner\Data::getLimitedClone(). Use withMaxDepth, withMaxItemsPerDepth or withRefHandles instead.

View File

@ -17,12 +17,10 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts Amqp related classes to array representation. * Casts Amqp related classes to array representation.
* *
* @author Grégoire Pineau <lyrixx@lyrixx.info> * @author Grégoire Pineau <lyrixx@lyrixx.info>
*
* @final since Symfony 4.4
*/ */
class AmqpCaster class AmqpCaster
{ {
private static $flags = [ private static $flags = array(
AMQP_DURABLE => 'AMQP_DURABLE', AMQP_DURABLE => 'AMQP_DURABLE',
AMQP_PASSIVE => 'AMQP_PASSIVE', AMQP_PASSIVE => 'AMQP_PASSIVE',
AMQP_EXCLUSIVE => 'AMQP_EXCLUSIVE', AMQP_EXCLUSIVE => 'AMQP_EXCLUSIVE',
@ -37,44 +35,35 @@ class AmqpCaster
AMQP_MULTIPLE => 'AMQP_MULTIPLE', AMQP_MULTIPLE => 'AMQP_MULTIPLE',
AMQP_NOWAIT => 'AMQP_NOWAIT', AMQP_NOWAIT => 'AMQP_NOWAIT',
AMQP_REQUEUE => 'AMQP_REQUEUE', AMQP_REQUEUE => 'AMQP_REQUEUE',
]; );
private static $exchangeTypes = [ private static $exchangeTypes = array(
AMQP_EX_TYPE_DIRECT => 'AMQP_EX_TYPE_DIRECT', AMQP_EX_TYPE_DIRECT => 'AMQP_EX_TYPE_DIRECT',
AMQP_EX_TYPE_FANOUT => 'AMQP_EX_TYPE_FANOUT', AMQP_EX_TYPE_FANOUT => 'AMQP_EX_TYPE_FANOUT',
AMQP_EX_TYPE_TOPIC => 'AMQP_EX_TYPE_TOPIC', AMQP_EX_TYPE_TOPIC => 'AMQP_EX_TYPE_TOPIC',
AMQP_EX_TYPE_HEADERS => 'AMQP_EX_TYPE_HEADERS', AMQP_EX_TYPE_HEADERS => 'AMQP_EX_TYPE_HEADERS',
]; );
public static function castConnection(\AMQPConnection $c, array $a, Stub $stub, $isNested) public static function castConnection(\AMQPConnection $c, array $a, Stub $stub, $isNested)
{ {
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
$a += [ // BC layer in the ampq lib
$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 {
$timeout = $c->getTimeout(); $timeout = $c->getTimeout();
} }
$a += [ $a += array(
$prefix.'is_connected' => $c->isConnected(), $prefix.'isConnected' => $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.'vhost' => $c->getVhost(),
$prefix.'port' => $c->getPort(), $prefix.'port' => $c->getPort(),
$prefix.'read_timeout' => $timeout, $prefix.'vhost' => $c->getVhost(),
]; $prefix.'readTimeout' => $timeout,
);
return $a; return $a;
} }
@ -83,21 +72,13 @@ class AmqpCaster
{ {
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
$a += [ $a += array(
$prefix.'is_connected' => $c->isConnected(), $prefix.'isConnected' => $c->isConnected(),
$prefix.'channel_id' => $c->getChannelId(), $prefix.'channelId' => $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 += [
$prefix.'connection' => $c->getConnection(), $prefix.'connection' => $c->getConnection(),
$prefix.'prefetch_size' => $c->getPrefetchSize(), );
$prefix.'prefetch_count' => $c->getPrefetchCount(),
];
return $a; return $a;
} }
@ -106,21 +87,13 @@ class AmqpCaster
{ {
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
$a += [ $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 += [
$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;
} }
@ -129,26 +102,14 @@ class AmqpCaster
{ {
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
$a += [ $a += array(
$prefix.'flags' => self::extractFlags($c->getFlags()),
];
$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 += [
$prefix.'connection' => $c->getConnection(),
$prefix.'channel' => $c->getChannel(),
$prefix.'name' => $c->getName(), $prefix.'name' => $c->getName(),
$prefix.'type' => $type, $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.'arguments' => $c->getArguments(),
]; $prefix.'channel' => $c->getChannel(),
$prefix.'connection' => $c->getConnection(),
);
return $a; return $a;
} }
@ -157,45 +118,36 @@ 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 += [$prefix.'body' => $c->getBody()]; $a += array($prefix.'body' => $c->getBody());
} }
$a += [ $a += array(
$prefix.'delivery_tag' => $c->getDeliveryTag(), $prefix.'routingKey' => $c->getRoutingKey(),
$prefix.'is_redelivery' => $c->isRedelivery(), $prefix.'deliveryTag' => $c->getDeliveryTag(),
$prefix.'exchange_name' => $c->getExchangeName(), $prefix.'deliveryMode' => new ConstStub($c->getDeliveryMode().(2 === $c->getDeliveryMode() ? ' (persistent)' : ' (non-persistent)'), $c->getDeliveryMode()),
$prefix.'routing_key' => $c->getRoutingKey(), $prefix.'exchangeName' => $c->getExchangeName(),
$prefix.'content_type' => $c->getContentType(), $prefix.'isRedelivery' => $c->isRedelivery(),
$prefix.'content_encoding' => $c->getContentEncoding(), $prefix.'contentType' => $c->getContentType(),
$prefix.'headers' => $c->getHeaders(), $prefix.'contentEncoding' => $c->getContentEncoding(),
$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.'type' => $c->getType(),
$prefix.'user_id' => $c->getUserId(), $prefix.'timestamp' => $c->getTimestamp(),
$prefix.'app_id' => $c->getAppId(), $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(),
);
return $a; return $a;
} }
private static function extractFlags(int $flags): ConstStub private static function extractFlags($flags)
{ {
$flagsArray = []; $flagsArray = array();
foreach (self::$flags as $value => $name) { foreach (self::$flags as $value => $name) {
if ($flags & $value) { if ($flags & $value) {
@ -204,7 +156,7 @@ class AmqpCaster
} }
if (!$flagsArray) { if (!$flagsArray) {
$flagsArray = ['AMQP_NOPARAM']; $flagsArray = array('AMQP_NOPARAM');
} }
return new ConstStub(implode('|', $flagsArray), $flags); return new ConstStub(implode('|', $flagsArray), $flags);

View File

@ -1,80 +0,0 @@
<?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 = [];
public function __construct(array $args, string $function, ?string $class)
{
list($variadic, $params) = self::getParameters($function, $class);
$values = [];
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 (['...'] === $params) {
$this->dumpKeys = false;
$this->value = $values[0]->value;
} else {
$this->value = array_combine($params, $values);
}
}
private static function getParameters(string $function, ?string $class): array
{
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 [null, null];
}
$variadic = '...';
$params = [];
foreach ($r->getParameters() as $v) {
$k = '$'.$v->name;
if ($v->isPassedByReference()) {
$k = '&'.$k;
}
if ($v->isVariadic()) {
$variadic .= $k;
} else {
$params[] = $k;
}
}
return self::$parameters[$k] = [$variadic, $params];
}
}

View File

@ -11,14 +11,10 @@
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.
* *
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*
* @final
*/ */
class Caster class Caster
{ {
@ -40,70 +36,29 @@ 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 bool $hasDebugInfo Whether the __debugInfo method exists on $obj or not * @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, string $class, bool $hasDebugInfo = false, string $debugClass = null): array public static function castObject($obj, \ReflectionClass $reflector)
{ {
if ($hasDebugInfo) { if ($reflector->hasMethod('__debugInfo')) {
try { $a = $obj->__debugInfo();
$debugInfo = $obj->__debugInfo(); } else {
} catch (\Exception $e) { $a = (array) $obj;
// ignore failing __debugInfo()
$hasDebugInfo = false;
}
}
$a = $obj instanceof \Closure ? [] : (array) $obj;
if ($obj instanceof \__PHP_Incomplete_Class) {
return $a;
} }
if ($a) { if ($a) {
static $publicProperties = []; $p = array_keys($a);
$debugClass = $debugClass ?? get_debug_type($obj); foreach ($p as $i => $k) {
if (!isset($k[0]) || ("\0" !== $k[0] && !$reflector->hasProperty($k))) {
$i = 0; $p[$i] = self::PREFIX_DYNAMIC.$k;
$prefixedKeys = []; } elseif (isset($k[16]) && "\0" === $k[16] && 0 === strpos($k, "\0class@anonymous\0")) {
foreach ($a as $k => $v) { $p[$i] = "\0".$reflector->getParentClass().'@anonymous'.strrchr($k, "\0");
if (isset($k[0]) ? "\0" !== $k[0] : \PHP_VERSION_ID >= 70200) {
if (!isset($publicProperties[$class])) {
foreach ((new \ReflectionClass($class))->getProperties(\ReflectionProperty::IS_PUBLIC) as $prop) {
$publicProperties[$class][$prop->name] = true;
}
}
if (!isset($publicProperties[$class][$k])) {
$prefixedKeys[$i] = self::PREFIX_DYNAMIC.$k;
}
} elseif ($debugClass !== $class && 1 === strpos($k, $class)) {
$prefixedKeys[$i] = "\0".$debugClass.strrchr($k, "\0");
} }
++$i;
}
if ($prefixedKeys) {
$keys = array_keys($a);
foreach ($prefixedKeys as $i => $k) {
$keys[$i] = $k;
}
$a = array_combine($keys, $a);
}
}
if ($hasDebugInfo && \is_array($debugInfo)) {
foreach ($debugInfo as $k => $v) {
if (!isset($k[0]) || "\0" !== $k[0]) {
if (\array_key_exists(self::PREFIX_DYNAMIC.$k, $a)) {
continue;
}
$k = self::PREFIX_VIRTUAL.$k;
}
unset($a[$k]);
$a[$k] = $v;
} }
$a = array_combine($p, $a);
} }
return $a; return $a;
@ -115,30 +70,27 @@ 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, int $filter, array $listedProperties = [], ?int &$count = 0): array public static function filter(array $a, $filter, array $listedProperties = array())
{ {
$count = 0;
foreach ($a as $k => $v) { foreach ($a as $k => $v) {
$type = self::EXCLUDE_STRICT & $filter; $type = self::EXCLUDE_STRICT & $filter;
if (null === $v) { if (null === $v) {
$type |= self::EXCLUDE_NULL & $filter; $type |= self::EXCLUDE_NULL & $filter;
$type |= self::EXCLUDE_EMPTY & $filter; }
} elseif (false === $v || '' === $v || '0' === $v || 0 === $v || 0.0 === $v || [] === $v) { if (empty($v)) {
$type |= self::EXCLUDE_EMPTY & $filter; $type |= self::EXCLUDE_EMPTY & $filter;
} }
if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !\in_array($k, $listedProperties, true)) { if ((self::EXCLUDE_NOT_IMPORTANT & $filter) && !in_array($k, $listedProperties, true)) {
$type |= self::EXCLUDE_NOT_IMPORTANT; $type |= self::EXCLUDE_NOT_IMPORTANT;
} }
if ((self::EXCLUDE_VERBOSE & $filter) && \in_array($k, $listedProperties, true)) { if ((self::EXCLUDE_VERBOSE & $filter) && in_array($k, $listedProperties, true)) {
$type |= self::EXCLUDE_VERBOSE; $type |= self::EXCLUDE_VERBOSE;
} }
@ -156,20 +108,9 @@ 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, bool $isNested): array
{
if (isset($a['__PHP_Incomplete_Class_Name'])) {
$stub->class .= '('.$a['__PHP_Incomplete_Class_Name'].')';
unset($a['__PHP_Incomplete_Class_Name']);
}
return $a;
}
} }

View File

@ -1,106 +0,0 @@
<?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 PHP class identifier.
*
* @author Nicolas Grekas <p@tchwork.com>
*/
class ClassStub extends ConstStub
{
/**
* @param string $identifier A PHP identifier, e.g. a class, method, interface, etc. name
* @param callable $callable The callable targeted by the identifier when it is ambiguous or not a real PHP identifier
*/
public function __construct(string $identifier, $callable = null)
{
$this->value = $identifier;
try {
if (null !== $callable) {
if ($callable instanceof \Closure) {
$r = new \ReflectionFunction($callable);
} elseif (\is_object($callable)) {
$r = [$callable, '__invoke'];
} elseif (\is_array($callable)) {
$r = $callable;
} elseif (false !== $i = strpos($callable, '::')) {
$r = [substr($callable, 0, $i), substr($callable, 2 + $i)];
} else {
$r = new \ReflectionFunction($callable);
}
} elseif (0 < $i = strpos($identifier, '::') ?: strpos($identifier, '->')) {
$r = [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]);
}
}
if (false !== strpos($identifier, "@anonymous\0")) {
$this->value = $identifier = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0];
}, $identifier);
}
if (null !== $callable && $r instanceof \ReflectionFunctionAbstract) {
$s = ReflectionCaster::castFunctionAbstract($r, [], new Stub(), true, Caster::EXCLUDE_VERBOSE);
$s = ReflectionCaster::getSignature($s);
if ('()' === substr($identifier, -2)) {
$this->value = substr_replace($identifier, $s, -2);
} else {
$this->value .= $s;
}
}
} catch (\ReflectionException $e) {
return;
} finally {
if (0 < $i = strrpos($this->value, '\\')) {
$this->attr['ellipsis'] = \strlen($this->value) - $i;
$this->attr['ellipsis-type'] = 'class';
$this->attr['ellipsis-tail'] = 1;
}
}
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, $callable);
} elseif (\is_string($callable[0])) {
$callable[0] = new static($callable[0], $callable);
} else {
$callable[1] = new static($callable[1], $callable);
}
return $callable;
}
}

View File

@ -20,17 +20,9 @@ use Symfony\Component\VarDumper\Cloner\Stub;
*/ */
class ConstStub extends Stub class ConstStub extends Stub
{ {
public function __construct(string $name, $value = null) public function __construct($name, $value)
{ {
$this->class = $name; $this->class = $name;
$this->value = 1 < \func_num_args() ? $value : $name; $this->value = $value;
}
/**
* @return string
*/
public function __toString()
{
return (string) $this->value;
} }
} }

View File

@ -25,6 +25,6 @@ class CutArrayStub extends CutStub
parent::__construct($value); parent::__construct($value);
$this->preservedSubset = array_intersect_key($value, array_flip($preservedKeys)); $this->preservedSubset = array_intersect_key($value, array_flip($preservedKeys));
$this->cut -= \count($this->preservedSubset); $this->cut -= count($this->preservedSubset);
} }
} }

View File

@ -24,39 +24,31 @@ class CutStub extends Stub
{ {
$this->value = $value; $this->value = $value;
switch (\gettype($value)) { switch (gettype($value)) {
case 'object': case 'object':
$this->type = self::TYPE_OBJECT; $this->type = self::TYPE_OBJECT;
$this->class = \get_class($value); $this->class = get_class($value);
if ($value instanceof \Closure) {
ReflectionCaster::castClosure($value, [], $this, true, Caster::EXCLUDE_VERBOSE);
}
$this->cut = -1; $this->cut = -1;
break; break;
case 'array': case 'array':
$this->type = self::TYPE_ARRAY; $this->type = self::TYPE_ARRAY;
$this->class = self::ARRAY_ASSOC; $this->class = self::ARRAY_ASSOC;
$this->cut = $this->value = \count($value); $this->cut = $this->value = count($value);
break; break;
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;
if ('Unknown' === $this->class = @get_resource_type($value)) { $this->class = @get_resource_type($value);
$this->class = 'Closed';
}
$this->cut = -1; $this->cut = -1;
break; break;
case 'string': case 'string':
$this->type = self::TYPE_STRING; $this->type = self::TYPE_STRING;
$this->class = preg_match('//u', $value) ? self::STRING_UTF8 : self::STRING_BINARY; $this->class = preg_match('//u', $value) ? self::STRING_UTF8 : self::STRING_BINARY;
$this->cut = self::STRING_BINARY === $this->class ? \strlen($value) : mb_strlen($value, 'UTF-8'); $this->cut = self::STRING_BINARY === $this->class ? strlen($value) : mb_strlen($value, 'UTF-8');
$this->value = ''; $this->value = '';
break; break;
} }

View File

@ -17,12 +17,10 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts DOM related classes to array representation. * Casts DOM related classes to array representation.
* *
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/ */
class DOMCaster class DOMCaster
{ {
private static $errorCodes = [ private static $errorCodes = array(
DOM_PHP_ERR => 'DOM_PHP_ERR', DOM_PHP_ERR => 'DOM_PHP_ERR',
DOM_INDEX_SIZE_ERR => 'DOM_INDEX_SIZE_ERR', DOM_INDEX_SIZE_ERR => 'DOM_INDEX_SIZE_ERR',
DOMSTRING_SIZE_ERR => 'DOMSTRING_SIZE_ERR', DOMSTRING_SIZE_ERR => 'DOMSTRING_SIZE_ERR',
@ -40,9 +38,9 @@ class DOMCaster
DOM_NAMESPACE_ERR => 'DOM_NAMESPACE_ERR', DOM_NAMESPACE_ERR => 'DOM_NAMESPACE_ERR',
DOM_INVALID_ACCESS_ERR => 'DOM_INVALID_ACCESS_ERR', DOM_INVALID_ACCESS_ERR => 'DOM_INVALID_ACCESS_ERR',
DOM_VALIDATION_ERR => 'DOM_VALIDATION_ERR', DOM_VALIDATION_ERR => 'DOM_VALIDATION_ERR',
]; );
private static $nodeTypes = [ private static $nodeTypes = array(
XML_ELEMENT_NODE => 'XML_ELEMENT_NODE', XML_ELEMENT_NODE => 'XML_ELEMENT_NODE',
XML_ATTRIBUTE_NODE => 'XML_ATTRIBUTE_NODE', XML_ATTRIBUTE_NODE => 'XML_ATTRIBUTE_NODE',
XML_TEXT_NODE => 'XML_TEXT_NODE', XML_TEXT_NODE => 'XML_TEXT_NODE',
@ -61,7 +59,7 @@ class DOMCaster
XML_ATTRIBUTE_DECL_NODE => 'XML_ATTRIBUTE_DECL_NODE', XML_ATTRIBUTE_DECL_NODE => 'XML_ATTRIBUTE_DECL_NODE',
XML_ENTITY_DECL_NODE => 'XML_ENTITY_DECL_NODE', XML_ENTITY_DECL_NODE => 'XML_ENTITY_DECL_NODE',
XML_NAMESPACE_DECL_NODE => 'XML_NAMESPACE_DECL_NODE', XML_NAMESPACE_DECL_NODE => 'XML_NAMESPACE_DECL_NODE',
]; );
public static function castException(\DOMException $e, array $a, Stub $stub, $isNested) public static function castException(\DOMException $e, array $a, Stub $stub, $isNested)
{ {
@ -75,26 +73,26 @@ class DOMCaster
public static function castLength($dom, array $a, Stub $stub, $isNested) public static function castLength($dom, array $a, Stub $stub, $isNested)
{ {
$a += [ $a += array(
'length' => $dom->length, 'length' => $dom->length,
]; );
return $a; return $a;
} }
public static function castImplementation($dom, array $a, Stub $stub, $isNested) public static function castImplementation($dom, array $a, Stub $stub, $isNested)
{ {
$a += [ $a += array(
Caster::PREFIX_VIRTUAL.'Core' => '1.0', Caster::PREFIX_VIRTUAL.'Core' => '1.0',
Caster::PREFIX_VIRTUAL.'XML' => '2.0', Caster::PREFIX_VIRTUAL.'XML' => '2.0',
]; );
return $a; return $a;
} }
public static function castNode(\DOMNode $dom, array $a, Stub $stub, $isNested) public static function castNode(\DOMNode $dom, array $a, Stub $stub, $isNested)
{ {
$a += [ $a += array(
'nodeName' => $dom->nodeName, 'nodeName' => $dom->nodeName,
'nodeValue' => new CutStub($dom->nodeValue), 'nodeValue' => new CutStub($dom->nodeValue),
'nodeType' => new ConstStub(self::$nodeTypes[$dom->nodeType], $dom->nodeType), 'nodeType' => new ConstStub(self::$nodeTypes[$dom->nodeType], $dom->nodeType),
@ -109,16 +107,16 @@ class DOMCaster
'namespaceURI' => $dom->namespaceURI, 'namespaceURI' => $dom->namespaceURI,
'prefix' => $dom->prefix, 'prefix' => $dom->prefix,
'localName' => $dom->localName, 'localName' => $dom->localName,
'baseURI' => $dom->baseURI ? new LinkStub($dom->baseURI) : $dom->baseURI, 'baseURI' => $dom->baseURI,
'textContent' => new CutStub($dom->textContent), 'textContent' => new CutStub($dom->textContent),
]; );
return $a; return $a;
} }
public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, Stub $stub, $isNested) public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, Stub $stub, $isNested)
{ {
$a += [ $a += array(
'nodeName' => $dom->nodeName, 'nodeName' => $dom->nodeName,
'nodeValue' => new CutStub($dom->nodeValue), 'nodeValue' => new CutStub($dom->nodeValue),
'nodeType' => new ConstStub(self::$nodeTypes[$dom->nodeType], $dom->nodeType), 'nodeType' => new ConstStub(self::$nodeTypes[$dom->nodeType], $dom->nodeType),
@ -127,14 +125,14 @@ class DOMCaster
'namespaceURI' => $dom->namespaceURI, 'namespaceURI' => $dom->namespaceURI,
'ownerDocument' => new CutStub($dom->ownerDocument), 'ownerDocument' => new CutStub($dom->ownerDocument),
'parentNode' => new CutStub($dom->parentNode), 'parentNode' => new CutStub($dom->parentNode),
]; );
return $a; return $a;
} }
public static function castDocument(\DOMDocument $dom, array $a, Stub $stub, $isNested, $filter = 0) public static function castDocument(\DOMDocument $dom, array $a, Stub $stub, $isNested, $filter = 0)
{ {
$a += [ $a += array(
'doctype' => $dom->doctype, 'doctype' => $dom->doctype,
'implementation' => $dom->implementation, 'implementation' => $dom->implementation,
'documentElement' => new CutStub($dom->documentElement), 'documentElement' => new CutStub($dom->documentElement),
@ -146,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 ? new LinkStub($dom->documentURI) : $dom->documentURI, 'documentURI' => $dom->documentURI,
'config' => $dom->config, 'config' => $dom->config,
'formatOutput' => $dom->formatOutput, 'formatOutput' => $dom->formatOutput,
'validateOnParse' => $dom->validateOnParse, 'validateOnParse' => $dom->validateOnParse,
@ -154,12 +152,12 @@ class DOMCaster
'preserveWhiteSpace' => $dom->preserveWhiteSpace, 'preserveWhiteSpace' => $dom->preserveWhiteSpace,
'recover' => $dom->recover, 'recover' => $dom->recover,
'substituteEntities' => $dom->substituteEntities, 'substituteEntities' => $dom->substituteEntities,
]; );
if (!($filter & Caster::EXCLUDE_VERBOSE)) { if (!($filter & Caster::EXCLUDE_VERBOSE)) {
$formatOutput = $dom->formatOutput; $formatOutput = $dom->formatOutput;
$dom->formatOutput = true; $dom->formatOutput = true;
$a += [Caster::PREFIX_VIRTUAL.'xml' => $dom->saveXML()]; $a += array(Caster::PREFIX_VIRTUAL.'xml' => $dom->saveXML());
$dom->formatOutput = $formatOutput; $dom->formatOutput = $formatOutput;
} }
@ -168,136 +166,136 @@ class DOMCaster
public static function castCharacterData(\DOMCharacterData $dom, array $a, Stub $stub, $isNested) public static function castCharacterData(\DOMCharacterData $dom, array $a, Stub $stub, $isNested)
{ {
$a += [ $a += array(
'data' => $dom->data, 'data' => $dom->data,
'length' => $dom->length, 'length' => $dom->length,
]; );
return $a; return $a;
} }
public static function castAttr(\DOMAttr $dom, array $a, Stub $stub, $isNested) public static function castAttr(\DOMAttr $dom, array $a, Stub $stub, $isNested)
{ {
$a += [ $a += array(
'name' => $dom->name, 'name' => $dom->name,
'specified' => $dom->specified, 'specified' => $dom->specified,
'value' => $dom->value, 'value' => $dom->value,
'ownerElement' => $dom->ownerElement, 'ownerElement' => $dom->ownerElement,
'schemaTypeInfo' => $dom->schemaTypeInfo, 'schemaTypeInfo' => $dom->schemaTypeInfo,
]; );
return $a; return $a;
} }
public static function castElement(\DOMElement $dom, array $a, Stub $stub, $isNested) public static function castElement(\DOMElement $dom, array $a, Stub $stub, $isNested)
{ {
$a += [ $a += array(
'tagName' => $dom->tagName, 'tagName' => $dom->tagName,
'schemaTypeInfo' => $dom->schemaTypeInfo, 'schemaTypeInfo' => $dom->schemaTypeInfo,
]; );
return $a; return $a;
} }
public static function castText(\DOMText $dom, array $a, Stub $stub, $isNested) public static function castText(\DOMText $dom, array $a, Stub $stub, $isNested)
{ {
$a += [ $a += array(
'wholeText' => $dom->wholeText, 'wholeText' => $dom->wholeText,
]; );
return $a; return $a;
} }
public static function castTypeinfo(\DOMTypeinfo $dom, array $a, Stub $stub, $isNested) public static function castTypeinfo(\DOMTypeinfo $dom, array $a, Stub $stub, $isNested)
{ {
$a += [ $a += array(
'typeName' => $dom->typeName, 'typeName' => $dom->typeName,
'typeNamespace' => $dom->typeNamespace, 'typeNamespace' => $dom->typeNamespace,
]; );
return $a; return $a;
} }
public static function castDomError(\DOMDomError $dom, array $a, Stub $stub, $isNested) public static function castDomError(\DOMDomError $dom, array $a, Stub $stub, $isNested)
{ {
$a += [ $a += array(
'severity' => $dom->severity, 'severity' => $dom->severity,
'message' => $dom->message, 'message' => $dom->message,
'type' => $dom->type, 'type' => $dom->type,
'relatedException' => $dom->relatedException, 'relatedException' => $dom->relatedException,
'related_data' => $dom->related_data, 'related_data' => $dom->related_data,
'location' => $dom->location, 'location' => $dom->location,
]; );
return $a; return $a;
} }
public static function castLocator(\DOMLocator $dom, array $a, Stub $stub, $isNested) public static function castLocator(\DOMLocator $dom, array $a, Stub $stub, $isNested)
{ {
$a += [ $a += array(
'lineNumber' => $dom->lineNumber, 'lineNumber' => $dom->lineNumber,
'columnNumber' => $dom->columnNumber, 'columnNumber' => $dom->columnNumber,
'offset' => $dom->offset, 'offset' => $dom->offset,
'relatedNode' => $dom->relatedNode, 'relatedNode' => $dom->relatedNode,
'uri' => $dom->uri ? new LinkStub($dom->uri, $dom->lineNumber) : $dom->uri, 'uri' => $dom->uri,
]; );
return $a; return $a;
} }
public static function castDocumentType(\DOMDocumentType $dom, array $a, Stub $stub, $isNested) public static function castDocumentType(\DOMDocumentType $dom, array $a, Stub $stub, $isNested)
{ {
$a += [ $a += array(
'name' => $dom->name, 'name' => $dom->name,
'entities' => $dom->entities, 'entities' => $dom->entities,
'notations' => $dom->notations, 'notations' => $dom->notations,
'publicId' => $dom->publicId, 'publicId' => $dom->publicId,
'systemId' => $dom->systemId, 'systemId' => $dom->systemId,
'internalSubset' => $dom->internalSubset, 'internalSubset' => $dom->internalSubset,
]; );
return $a; return $a;
} }
public static function castNotation(\DOMNotation $dom, array $a, Stub $stub, $isNested) public static function castNotation(\DOMNotation $dom, array $a, Stub $stub, $isNested)
{ {
$a += [ $a += array(
'publicId' => $dom->publicId, 'publicId' => $dom->publicId,
'systemId' => $dom->systemId, 'systemId' => $dom->systemId,
]; );
return $a; return $a;
} }
public static function castEntity(\DOMEntity $dom, array $a, Stub $stub, $isNested) public static function castEntity(\DOMEntity $dom, array $a, Stub $stub, $isNested)
{ {
$a += [ $a += array(
'publicId' => $dom->publicId, 'publicId' => $dom->publicId,
'systemId' => $dom->systemId, 'systemId' => $dom->systemId,
'notationName' => $dom->notationName, 'notationName' => $dom->notationName,
'actualEncoding' => $dom->actualEncoding, 'actualEncoding' => $dom->actualEncoding,
'encoding' => $dom->encoding, 'encoding' => $dom->encoding,
'version' => $dom->version, 'version' => $dom->version,
]; );
return $a; return $a;
} }
public static function castProcessingInstruction(\DOMProcessingInstruction $dom, array $a, Stub $stub, $isNested) public static function castProcessingInstruction(\DOMProcessingInstruction $dom, array $a, Stub $stub, $isNested)
{ {
$a += [ $a += array(
'target' => $dom->target, 'target' => $dom->target,
'data' => $dom->data, 'data' => $dom->data,
]; );
return $a; return $a;
} }
public static function castXPath(\DOMXPath $dom, array $a, Stub $stub, $isNested) public static function castXPath(\DOMXPath $dom, array $a, Stub $stub, $isNested)
{ {
$a += [ $a += array(
'document' => $dom->document, 'document' => $dom->document,
]; );
return $a; return $a;
} }

View File

@ -1,128 +0,0 @@
<?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 DateTimeInterface related classes to array representation.
*
* @author Dany Maillard <danymaillard93b@gmail.com>
*
* @final since Symfony 4.4
*/
class DateCaster
{
private const PERIOD_LIMIT = 3;
public static function castDateTime(\DateTimeInterface $d, array $a, Stub $stub, $isNested, $filter)
{
$prefix = Caster::PREFIX_VIRTUAL;
$location = $d->getTimezone()->getLocation();
$fromNow = (new \DateTime())->diff($d);
$title = $d->format('l, F j, Y')
."\n".self::formatInterval($fromNow).' from now'
.($location ? ($d->format('I') ? "\nDST On" : "\nDST Off") : '')
;
unset(
$a[Caster::PREFIX_DYNAMIC.'date'],
$a[Caster::PREFIX_DYNAMIC.'timezone'],
$a[Caster::PREFIX_DYNAMIC.'timezone_type']
);
$a[$prefix.'date'] = new ConstStub(self::formatDateTime($d, $location ? ' e (P)' : ' P'), $title);
$stub->class .= $d->format(' @U');
return $a;
}
public static function castInterval(\DateInterval $interval, array $a, Stub $stub, $isNested, $filter)
{
$now = new \DateTimeImmutable();
$numberOfSeconds = $now->add($interval)->getTimestamp() - $now->getTimestamp();
$title = number_format($numberOfSeconds, 0, '.', ' ').'s';
$i = [Caster::PREFIX_VIRTUAL.'interval' => new ConstStub(self::formatInterval($interval), $title)];
return $filter & Caster::EXCLUDE_VERBOSE ? $i : $i + $a;
}
private static function formatInterval(\DateInterval $i): string
{
$format = '%R ';
if (0 === $i->y && 0 === $i->m && ($i->h >= 24 || $i->i >= 60 || $i->s >= 60)) {
$i = date_diff($d = new \DateTime(), date_add(clone $d, $i)); // recalculate carry over points
$format .= 0 < $i->days ? '%ad ' : '';
} else {
$format .= ($i->y ? '%yy ' : '').($i->m ? '%mm ' : '').($i->d ? '%dd ' : '');
}
$format .= $i->h || $i->i || $i->s || $i->f ? '%H:%I:'.self::formatSeconds($i->s, substr($i->f, 2)) : '';
$format = '%R ' === $format ? '0s' : $format;
return $i->format(rtrim($format));
}
public static function castTimeZone(\DateTimeZone $timeZone, array $a, Stub $stub, $isNested, $filter)
{
$location = $timeZone->getLocation();
$formatted = (new \DateTime('now', $timeZone))->format($location ? 'e (P)' : 'P');
$title = $location && \extension_loaded('intl') ? \Locale::getDisplayRegion('-'.$location['country_code']) : '';
$z = [Caster::PREFIX_VIRTUAL.'timezone' => new ConstStub($formatted, $title)];
return $filter & Caster::EXCLUDE_VERBOSE ? $z : $z + $a;
}
public static function castPeriod(\DatePeriod $p, array $a, Stub $stub, $isNested, $filter)
{
$dates = [];
if (\PHP_VERSION_ID >= 70107) { // see https://bugs.php.net/74639
foreach (clone $p as $i => $d) {
if (self::PERIOD_LIMIT === $i) {
$now = new \DateTimeImmutable();
$dates[] = sprintf('%s more', ($end = $p->getEndDate())
? ceil(($end->format('U.u') - $d->format('U.u')) / ((int) $now->add($p->getDateInterval())->format('U.u') - (int) $now->format('U.u')))
: $p->recurrences - $i
);
break;
}
$dates[] = sprintf('%s) %s', $i + 1, self::formatDateTime($d));
}
}
$period = sprintf(
'every %s, from %s (%s) %s',
self::formatInterval($p->getDateInterval()),
self::formatDateTime($p->getStartDate()),
$p->include_start_date ? 'included' : 'excluded',
($end = $p->getEndDate()) ? 'to '.self::formatDateTime($end) : 'recurring '.$p->recurrences.' time/s'
);
$p = [Caster::PREFIX_VIRTUAL.'period' => new ConstStub($period, implode("\n", $dates))];
return $filter & Caster::EXCLUDE_VERBOSE ? $p : $p + $a;
}
private static function formatDateTime(\DateTimeInterface $d, string $extra = ''): string
{
return $d->format('Y-m-d H:i:'.self::formatSeconds($d->format('s'), $d->format('u')).$extra);
}
private static function formatSeconds(string $s, string $us): string
{
return sprintf('%02d.%s', $s, 0 === ($len = \strlen($t = rtrim($us, '0'))) ? '0' : ($len <= 3 ? str_pad($t, 3, '0') : $us));
}
}

View File

@ -12,23 +12,21 @@
namespace Symfony\Component\VarDumper\Caster; namespace Symfony\Component\VarDumper\Caster;
use Doctrine\Common\Proxy\Proxy as CommonProxy; use Doctrine\Common\Proxy\Proxy as CommonProxy;
use Doctrine\ORM\PersistentCollection;
use Doctrine\ORM\Proxy\Proxy as OrmProxy; use Doctrine\ORM\Proxy\Proxy as OrmProxy;
use Doctrine\ORM\PersistentCollection;
use Symfony\Component\VarDumper\Cloner\Stub; use Symfony\Component\VarDumper\Cloner\Stub;
/** /**
* Casts Doctrine related classes to array representation. * Casts Doctrine related classes to array representation.
* *
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/ */
class DoctrineCaster class DoctrineCaster
{ {
public static function castCommonProxy(CommonProxy $proxy, array $a, Stub $stub, $isNested) public static function castCommonProxy(CommonProxy $proxy, array $a, Stub $stub, $isNested)
{ {
foreach (['__cloner__', '__initializer__'] as $k) { foreach (array('__cloner__', '__initializer__') as $k) {
if (\array_key_exists($k, $a)) { if (array_key_exists($k, $a)) {
unset($a[$k]); unset($a[$k]);
++$stub->cut; ++$stub->cut;
} }
@ -39,8 +37,8 @@ class DoctrineCaster
public static function castOrmProxy(OrmProxy $proxy, array $a, Stub $stub, $isNested) public static function castOrmProxy(OrmProxy $proxy, array $a, Stub $stub, $isNested)
{ {
foreach (['_entityPersister', '_identifier'] as $k) { foreach (array('_entityPersister', '_identifier') as $k) {
if (\array_key_exists($k = "\0Doctrine\\ORM\\Proxy\\Proxy\0".$k, $a)) { if (array_key_exists($k = "\0Doctrine\\ORM\\Proxy\\Proxy\0".$k, $a)) {
unset($a[$k]); unset($a[$k]);
++$stub->cut; ++$stub->cut;
} }
@ -51,8 +49,8 @@ class DoctrineCaster
public static function castPersistentCollection(PersistentCollection $coll, array $a, Stub $stub, $isNested) public static function castPersistentCollection(PersistentCollection $coll, array $a, Stub $stub, $isNested)
{ {
foreach (['snapshot', 'association', 'typeClass'] as $k) { foreach (array('snapshot', 'association', 'typeClass') as $k) {
if (\array_key_exists($k = "\0Doctrine\\ORM\\PersistentCollection\0".$k, $a)) { if (array_key_exists($k = "\0Doctrine\\ORM\\PersistentCollection\0".$k, $a)) {
$a[$k] = new CutStub($a[$k]); $a[$k] = new CutStub($a[$k]);
} }
} }

View File

@ -1,70 +0,0 @@
<?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 Ds\Collection;
use Ds\Map;
use Ds\Pair;
use Symfony\Component\VarDumper\Cloner\Stub;
/**
* Casts Ds extension classes to array representation.
*
* @author Jáchym Toušek <enumag@gmail.com>
*
* @final since Symfony 4.4
*/
class DsCaster
{
public static function castCollection(Collection $c, array $a, Stub $stub, bool $isNested): array
{
$a[Caster::PREFIX_VIRTUAL.'count'] = $c->count();
$a[Caster::PREFIX_VIRTUAL.'capacity'] = $c->capacity();
if (!$c instanceof Map) {
$a += $c->toArray();
}
return $a;
}
public static function castMap(Map $c, array $a, Stub $stub, bool $isNested): array
{
foreach ($c as $k => $v) {
$a[] = new DsPairStub($k, $v);
}
return $a;
}
public static function castPair(Pair $c, array $a, Stub $stub, bool $isNested): array
{
foreach ($c->toArray() as $k => $v) {
$a[Caster::PREFIX_VIRTUAL.$k] = $v;
}
return $a;
}
public static function castPairStub(DsPairStub $c, array $a, Stub $stub, bool $isNested): array
{
if ($isNested) {
$stub->class = Pair::class;
$stub->value = null;
$stub->handle = 0;
$a = $c->value;
}
return $a;
}
}

View File

@ -1,28 +0,0 @@
<?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;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class DsPairStub extends Stub
{
public function __construct($key, $value)
{
$this->value = [
Caster::PREFIX_VIRTUAL.'key' => $key,
Caster::PREFIX_VIRTUAL.'value' => $value,
];
}
}

View File

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

View File

@ -11,22 +11,19 @@
namespace Symfony\Component\VarDumper\Caster; namespace Symfony\Component\VarDumper\Caster;
use Symfony\Component\ErrorHandler\Exception\SilencedErrorContext;
use Symfony\Component\VarDumper\Cloner\Stub;
use Symfony\Component\VarDumper\Exception\ThrowingCasterException; use Symfony\Component\VarDumper\Exception\ThrowingCasterException;
use Symfony\Component\VarDumper\Cloner\Stub;
/** /**
* Casts common Exception classes to array representation. * Casts common Exception classes to array representation.
* *
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/ */
class ExceptionCaster class ExceptionCaster
{ {
public static $srcContext = 1; public static $srcContext = 1;
public static $traceArgs = true; public static $traceArgs = true;
public static $errorTypes = [ public static $errorTypes = array(
E_DEPRECATED => 'E_DEPRECATED', E_DEPRECATED => 'E_DEPRECATED',
E_USER_DEPRECATED => 'E_USER_DEPRECATED', E_USER_DEPRECATED => 'E_USER_DEPRECATED',
E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR',
@ -42,9 +39,7 @@ class ExceptionCaster
E_USER_WARNING => 'E_USER_WARNING', E_USER_WARNING => 'E_USER_WARNING',
E_USER_NOTICE => 'E_USER_NOTICE', E_USER_NOTICE => 'E_USER_NOTICE',
E_STRICT => 'E_STRICT', E_STRICT => 'E_STRICT',
]; );
private static $framesCache = [];
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)
{ {
@ -67,15 +62,17 @@ class ExceptionCaster
public static function castThrowingCasterException(ThrowingCasterException $e, array $a, Stub $stub, $isNested) public static function castThrowingCasterException(ThrowingCasterException $e, array $a, Stub $stub, $isNested)
{ {
$trace = Caster::PREFIX_VIRTUAL.'trace';
$prefix = Caster::PREFIX_PROTECTED; $prefix = Caster::PREFIX_PROTECTED;
$xPrefix = "\0Exception\0"; $xPrefix = "\0Exception\0";
if (isset($a[$xPrefix.'previous'], $a[$trace]) && $a[$xPrefix.'previous'] instanceof \Exception) { if (isset($a[$xPrefix.'previous'], $a[$xPrefix.'trace'])) {
$b = (array) $a[$xPrefix.'previous']; $b = (array) $a[$xPrefix.'previous'];
$class = get_debug_type($a[$xPrefix.'previous']); array_unshift($b[$xPrefix.'trace'], array(
self::traceUnshift($b[$xPrefix.'trace'], $class, $b[$prefix.'file'], $b[$prefix.'line']); 'function' => 'new '.get_class($a[$xPrefix.'previous']),
$a[$trace] = new TraceStub($b[$xPrefix.'trace'], false, 0, -\count($a[$trace]->value)); 'file' => $b[$prefix.'file'],
'line' => $b[$prefix.'line'],
));
$a[$xPrefix.'trace'] = new TraceStub($b[$xPrefix.'trace'], false, 0, -1 - count($a[$xPrefix.'trace']->value));
} }
unset($a[$xPrefix.'previous'], $a[$prefix.'code'], $a[$prefix.'file'], $a[$prefix.'line']); unset($a[$xPrefix.'previous'], $a[$prefix.'code'], $a[$prefix.'file'], $a[$prefix.'line']);
@ -83,33 +80,6 @@ class ExceptionCaster
return $a; return $a;
} }
public static function castSilencedErrorContext(SilencedErrorContext $e, array $a, Stub $stub, $isNested)
{
$sPrefix = "\0".SilencedErrorContext::class."\0";
if (!isset($a[$s = $sPrefix.'severity'])) {
return $a;
}
if (isset(self::$errorTypes[$a[$s]])) {
$a[$s] = new ConstStub(self::$errorTypes[$a[$s]], $a[$s]);
}
$trace = [[
'file' => $a[$sPrefix.'file'],
'line' => $a[$sPrefix.'line'],
]];
if (isset($a[$sPrefix.'trace'])) {
$trace = array_merge($trace, $a[$sPrefix.'trace']);
}
unset($a[$sPrefix.'file'], $a[$sPrefix.'line'], $a[$sPrefix.'trace']);
$a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs);
return $a;
}
public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, $isNested) public static function castTraceStub(TraceStub $trace, array $a, Stub $stub, $isNested)
{ {
if (!$isNested) { if (!$isNested) {
@ -118,67 +88,45 @@ class ExceptionCaster
$stub->class = ''; $stub->class = '';
$stub->handle = 0; $stub->handle = 0;
$frames = $trace->value; $frames = $trace->value;
$prefix = Caster::PREFIX_VIRTUAL;
$a = []; $a = array();
$j = \count($frames); $j = count($frames);
if (0 > $i = $trace->sliceOffset) { if (0 > $i = $trace->sliceOffset) {
$i = max(0, $j + $i); $i = max(0, $j + $i);
} }
if (!isset($trace->value[$i])) { if (!isset($trace->value[$i])) {
return []; 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[] = ['function' => ''];
$collapse = false;
for ($j += $trace->numberingOffset - $i++; isset($frames[$i]); ++$i, --$j) { for ($j += $trace->numberingOffset - $i++; isset($frames[$i]); ++$i, --$j) {
$f = $frames[$i]; $call = isset($frames[$i]['function']) ? (isset($frames[$i]['class']) ? $frames[$i]['class'].$frames[$i]['type'] : '').$frames[$i]['function'].'()' : '???';
$call = isset($f['function']) ? (isset($f['class']) ? $f['class'].$f['type'] : '').$f['function'] : '???';
$frame = new FrameStub( $a[Caster::PREFIX_VIRTUAL.$j.'. '.$call.$lastCall] = new FrameStub(
[ array(
'object' => isset($f['object']) ? $f['object'] : null, 'object' => isset($frames[$i]['object']) ? $frames[$i]['object'] : null,
'class' => isset($f['class']) ? $f['class'] : null, 'class' => isset($frames[$i]['class']) ? $frames[$i]['class'] : null,
'type' => isset($f['type']) ? $f['type'] : null, 'type' => isset($frames[$i]['type']) ? $frames[$i]['type'] : null,
'function' => isset($f['function']) ? $f['function'] : null, 'function' => isset($frames[$i]['function']) ? $frames[$i]['function'] : null,
] + $frames[$i - 1], ) + $frames[$i - 1],
false, $trace->keepArgs,
true true
); );
$f = self::castFrameStub($frame, [], $frame, true);
if (isset($f[$prefix.'src'])) {
foreach ($f[$prefix.'src']->value as $label => $frame) {
if (0 === strpos($label, "\0~collapse=0")) {
if ($collapse) {
$label = substr_replace($label, '1', 11, 1);
} else {
$collapse = true;
}
}
$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);
}
} elseif ('???' !== $lastCall) {
$label = new ClassStub($lastCall);
if (isset($label->attr['ellipsis'])) {
$label->attr['ellipsis'] += 2;
$label = substr_replace($prefix, "ellipsis-type=class&ellipsis={$label->attr['ellipsis']}&ellipsis-tail=1&title=Stack level $j.", 2, 0).$label->value.'()';
} else {
$label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$label->value.'()';
}
} else {
$label = substr_replace($prefix, "title=Stack level $j.", 2, 0).$lastCall;
}
$a[substr_replace($label, sprintf('separator=%s&', $frame instanceof EnumStub ? ' ' : ':'), 2, 0)] = $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);
} }
return $a; return $a;
@ -193,56 +141,30 @@ class ExceptionCaster
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
if (isset($f['file'], $f['line'])) { if (isset($f['file'], $f['line'])) {
$cacheKey = $f; if (preg_match('/\((\d+)\)(?:\([\da-f]{32}\))? : (?:eval\(\)\'d code|runtime-created function)$/', $f['file'], $match)) {
unset($cacheKey['object'], $cacheKey['args']); $f['file'] = substr($f['file'], 0, -strlen($match[0]));
$cacheKey[] = self::$srcContext; $f['line'] = (int) $match[1];
$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 (isset(self::$framesCache[$cacheKey])) { if (!empty($f['class']) && is_subclass_of($f['class'], 'Twig_Template') && method_exists($f['class'], 'getDebugInfo')) {
$a[$prefix.'src'] = self::$framesCache[$cacheKey]; $template = isset($f['object']) ? $f['object'] : new $f['class'](new \Twig_Environment(new \Twig_Loader_Filesystem()));
} 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];
}
$src = $f['line'];
$srcKey = $f['file'];
$ellipsis = new LinkStub($srcKey, 0);
$srcAttr = 'collapse='.(int) $ellipsis->inVendor;
$ellipsisTail = isset($ellipsis->attr['ellipsis-tail']) ? $ellipsis->attr['ellipsis-tail'] : 0;
$ellipsis = isset($ellipsis->attr['ellipsis']) ? $ellipsis->attr['ellipsis'] : 0;
if (file_exists($f['file']) && 0 <= self::$srcContext) { try {
if (!empty($f['class']) && (is_subclass_of($f['class'], 'Twig\Template') || is_subclass_of($f['class'], 'Twig_Template')) && method_exists($f['class'], 'getDebugInfo')) { $templateName = $template->getTemplateName();
$template = isset($f['object']) ? $f['object'] : unserialize(sprintf('O:%d:"%s":0:{}', \strlen($f['class']), $f['class'])); $templateSrc = explode("\n", method_exists($template, 'getSource') ? $template->getSource() : $template->getEnvironment()->getLoader()->getSource($templateName));
$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']])) {
if (!method_exists($template, 'getSourceContext') || !file_exists($templatePath = $template->getSourceContext()->getPath())) { $src[$templateName.':'.$templateInfo[$f['line']]] = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext);
$templatePath = null;
}
if ($templateSrc) {
$src = self::extractSource($templateSrc, $templateInfo[$f['line']], self::$srcContext, 'twig', $templatePath, $f);
$srcKey = ($templatePath ?: $template->getTemplateName()).':'.$templateInfo[$f['line']];
}
} }
} catch (\Twig_Error_Loader $e) {
} }
if ($srcKey == $f['file']) {
$src = self::extractSource(file_get_contents($f['file']), $f['line'], self::$srcContext, 'php', $f['file'], $f);
$srcKey .= ':'.$f['line'];
if ($ellipsis) {
$ellipsis += 1 + \strlen($f['line']);
}
}
$srcAttr .= sprintf('&separator= &file=%s&line=%d', rawurlencode($f['file']), $f['line']);
} else {
$srcAttr .= '&separator=:';
} }
$srcAttr .= $ellipsis ? '&ellipsis-type=path&ellipsis='.$ellipsis.'&ellipsis-tail='.$ellipsisTail : ''; } else {
self::$framesCache[$cacheKey] = $a[$prefix.'src'] = new EnumStub(["\0~$srcAttr\0$srcKey" => $src]); $src[$f['file']] = $f['line'];
} }
$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']);
@ -254,129 +176,63 @@ class ExceptionCaster
unset($a[$k]); unset($a[$k]);
} }
} }
if ($frame->keepArgs && !empty($f['args'])) { if ($frame->keepArgs && isset($f['args'])) {
$a[$prefix.'arguments'] = new ArgsStub($f['args'], $f['function'], $f['class']); $a[$prefix.'args'] = $f['args'];
} }
return $a; return $a;
} }
private static function filterExceptionArray(string $xClass, array $a, string $xPrefix, int $filter): array private static function filterExceptionArray($xClass, array $a, $xPrefix, $filter)
{ {
if (isset($a[$xPrefix.'trace'])) { if (isset($a[$xPrefix.'trace'])) {
$trace = $a[$xPrefix.'trace']; $trace = $a[$xPrefix.'trace'];
unset($a[$xPrefix.'trace']); // Ensures the trace is always last unset($a[$xPrefix.'trace']); // Ensures the trace is always last
} else { } else {
$trace = []; $trace = array();
} }
if (!($filter & Caster::EXCLUDE_VERBOSE) && $trace) { if (!($filter & Caster::EXCLUDE_VERBOSE)) {
if (isset($a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line'])) { array_unshift($trace, array(
self::traceUnshift($trace, $xClass, $a[Caster::PREFIX_PROTECTED.'file'], $a[Caster::PREFIX_PROTECTED.'line']); 'function' => $xClass ? 'new '.$xClass : null,
} 'file' => $a[Caster::PREFIX_PROTECTED.'file'],
$a[Caster::PREFIX_VIRTUAL.'trace'] = new TraceStub($trace, self::$traceArgs); 'line' => $a[Caster::PREFIX_PROTECTED.'line'],
));
$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.'message']) && false !== strpos($a[Caster::PREFIX_PROTECTED.'message'], "@anonymous\0")) {
$a[Caster::PREFIX_PROTECTED.'message'] = preg_replace_callback('/[a-zA-Z_\x7f-\xff][\\\\a-zA-Z0-9_\x7f-\xff]*+@anonymous\x00.*?\.php(?:0x?|:[0-9]++\$)[0-9a-fA-F]++/', function ($m) {
return class_exists($m[0], false) ? (get_parent_class($m[0]) ?: key(class_implements($m[0])) ?: 'class').'@anonymous' : $m[0];
}, $a[Caster::PREFIX_PROTECTED.'message']);
}
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 traceUnshift(array &$trace, ?string $class, string $file, int $line): void private static function extractSource(array $srcArray, $line, $srcContext)
{ {
if (isset($trace[0]['file'], $trace[0]['line']) && $trace[0]['file'] === $file && $trace[0]['line'] === $line) { $src = array();
return;
}
array_unshift($trace, [
'function' => $class ? 'new '.$class : null,
'file' => $file,
'line' => $line,
]);
}
private static function extractSource(string $srcLines, int $line, int $srcContext, string $lang, ?string $file, array $frame): EnumStub
{
$srcLines = explode("\n", $srcLines);
$src = [];
for ($i = $line - 1 - $srcContext; $i <= $line - 1 + $srcContext; ++$i) { for ($i = $line - 1 - $srcContext; $i <= $line - 1 + $srcContext; ++$i) {
$src[] = (isset($srcLines[$i]) ? $srcLines[$i] : '')."\n"; $src[] = (isset($srcArray[$i]) ? $srcArray[$i] : '')."\n";
}
if ($frame['function'] ?? false) {
$stub = new CutStub(new \stdClass());
$stub->class = (isset($frame['class']) ? $frame['class'].$frame['type'] : '').$frame['function'];
$stub->type = Stub::TYPE_OBJECT;
$stub->attr['cut_hash'] = true;
$stub->attr['file'] = $frame['file'];
$stub->attr['line'] = $frame['line'];
try {
$caller = isset($frame['class']) ? new \ReflectionMethod($frame['class'], $frame['function']) : new \ReflectionFunction($frame['function']);
$stub->class .= ReflectionCaster::getSignature(ReflectionCaster::castFunctionAbstract($caller, [], $stub, true, Caster::EXCLUDE_VERBOSE));
if ($f = $caller->getFileName()) {
$stub->attr['file'] = $f;
$stub->attr['line'] = $caller->getStartLine();
}
} catch (\ReflectionException $e) {
// ignore fake class/function
}
$srcLines = ["\0~separator=\0" => $stub];
} else {
$stub = null;
$srcLines = [];
} }
$ltrim = 0; $ltrim = 0;
do { while (' ' === $src[0][$ltrim] || "\t" === $src[0][$ltrim]) {
$pad = null; $i = $srcContext << 1;
for ($i = $srcContext << 1; $i >= 0; --$i) { while ($i > 0 && $src[0][$ltrim] === $src[$i][$ltrim]) {
if (isset($src[$i][$ltrim]) && "\r" !== ($c = $src[$i][$ltrim]) && "\n" !== $c) { --$i;
if (null === $pad) { }
$pad = $c; if ($i) {
} break;
if ((' ' !== $c && "\t" !== $c) || $pad !== $c) {
break;
}
}
} }
++$ltrim; ++$ltrim;
} while (0 > $i && null !== $pad); }
if ($ltrim) {
--$ltrim; foreach ($src as $i => $line) {
$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, $stub ? 'in '.$stub->class : '');
if (null !== $file) {
$c->attr['file'] = $file;
$c->attr['line'] = $line;
}
}
$c->attr['lang'] = $lang;
$srcLines[sprintf("\0~separator=› &%d\0", $i + $line - $srcContext)] = $c;
} }
return new EnumStub($srcLines); return implode('', $src);
} }
} }

View File

@ -21,7 +21,7 @@ class FrameStub extends EnumStub
public $keepArgs; public $keepArgs;
public $inTraceStub; public $inTraceStub;
public function __construct(array $frame, bool $keepArgs = true, bool $inTraceStub = false) public function __construct(array $frame, $keepArgs = true, $inTraceStub = false)
{ {
$this->value = $frame; $this->value = $frame;
$this->keepArgs = $keepArgs; $this->keepArgs = $keepArgs;

View File

@ -1,32 +0,0 @@
<?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 GMP objects to array representation.
*
* @author Hamza Amrouche <hamza.simperfit@gmail.com>
* @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/
class GmpCaster
{
public static function castGmp(\GMP $gmp, array $a, Stub $stub, $isNested, $filter): array
{
$a[Caster::PREFIX_VIRTUAL.'value'] = new ConstStub(gmp_strval($gmp), gmp_strval($gmp));
return $a;
}
}

View File

@ -1,37 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarDumper\Caster;
use Imagine\Image\ImageInterface;
use Symfony\Component\VarDumper\Cloner\Stub;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
final class ImagineCaster
{
public static function castImage(ImageInterface $c, array $a, Stub $stub, bool $isNested): array
{
$imgData = $c->get('png');
if (\strlen($imgData) > 1 * 1000 * 1000) {
$a += [
Caster::PREFIX_VIRTUAL.'image' => new ConstStub($c->getSize()),
];
} else {
$a += [
Caster::PREFIX_VIRTUAL.'image' => new ImgStub($imgData, 'image/png', $c->getSize()),
];
}
return $a;
}
}

View File

@ -1,26 +0,0 @@
<?php
/*
* This file is part of the Symfony package.
*
* (c) Fabien Potencier <fabien@symfony.com>
*
* For the full copyright and license information, please view the LICENSE
* file that was distributed with this source code.
*/
namespace Symfony\Component\VarDumper\Caster;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class ImgStub extends ConstStub
{
public function __construct(string $data, string $contentType, string $size)
{
$this->value = '';
$this->attr['img-data'] = $data;
$this->attr['img-size'] = $size;
$this->attr['content-type'] = $contentType;
}
}

View File

@ -1,172 +0,0 @@
<?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;
/**
* @author Nicolas Grekas <p@tchwork.com>
* @author Jan Schädlich <jan.schaedlich@sensiolabs.de>
*
* @final since Symfony 4.4
*/
class IntlCaster
{
public static function castMessageFormatter(\MessageFormatter $c, array $a, Stub $stub, $isNested)
{
$a += [
Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(),
Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(),
];
return self::castError($c, $a);
}
public static function castNumberFormatter(\NumberFormatter $c, array $a, Stub $stub, $isNested, $filter = 0)
{
$a += [
Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(),
Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(),
];
if ($filter & Caster::EXCLUDE_VERBOSE) {
$stub->cut += 3;
return self::castError($c, $a);
}
$a += [
Caster::PREFIX_VIRTUAL.'attributes' => new EnumStub(
[
'PARSE_INT_ONLY' => $c->getAttribute(\NumberFormatter::PARSE_INT_ONLY),
'GROUPING_USED' => $c->getAttribute(\NumberFormatter::GROUPING_USED),
'DECIMAL_ALWAYS_SHOWN' => $c->getAttribute(\NumberFormatter::DECIMAL_ALWAYS_SHOWN),
'MAX_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_INTEGER_DIGITS),
'MIN_INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_INTEGER_DIGITS),
'INTEGER_DIGITS' => $c->getAttribute(\NumberFormatter::INTEGER_DIGITS),
'MAX_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_FRACTION_DIGITS),
'MIN_FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_FRACTION_DIGITS),
'FRACTION_DIGITS' => $c->getAttribute(\NumberFormatter::FRACTION_DIGITS),
'MULTIPLIER' => $c->getAttribute(\NumberFormatter::MULTIPLIER),
'GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::GROUPING_SIZE),
'ROUNDING_MODE' => $c->getAttribute(\NumberFormatter::ROUNDING_MODE),
'ROUNDING_INCREMENT' => $c->getAttribute(\NumberFormatter::ROUNDING_INCREMENT),
'FORMAT_WIDTH' => $c->getAttribute(\NumberFormatter::FORMAT_WIDTH),
'PADDING_POSITION' => $c->getAttribute(\NumberFormatter::PADDING_POSITION),
'SECONDARY_GROUPING_SIZE' => $c->getAttribute(\NumberFormatter::SECONDARY_GROUPING_SIZE),
'SIGNIFICANT_DIGITS_USED' => $c->getAttribute(\NumberFormatter::SIGNIFICANT_DIGITS_USED),
'MIN_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MIN_SIGNIFICANT_DIGITS),
'MAX_SIGNIFICANT_DIGITS' => $c->getAttribute(\NumberFormatter::MAX_SIGNIFICANT_DIGITS),
'LENIENT_PARSE' => $c->getAttribute(\NumberFormatter::LENIENT_PARSE),
]
),
Caster::PREFIX_VIRTUAL.'text_attributes' => new EnumStub(
[
'POSITIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_PREFIX),
'POSITIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::POSITIVE_SUFFIX),
'NEGATIVE_PREFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_PREFIX),
'NEGATIVE_SUFFIX' => $c->getTextAttribute(\NumberFormatter::NEGATIVE_SUFFIX),
'PADDING_CHARACTER' => $c->getTextAttribute(\NumberFormatter::PADDING_CHARACTER),
'CURRENCY_CODE' => $c->getTextAttribute(\NumberFormatter::CURRENCY_CODE),
'DEFAULT_RULESET' => $c->getTextAttribute(\NumberFormatter::DEFAULT_RULESET),
'PUBLIC_RULESETS' => $c->getTextAttribute(\NumberFormatter::PUBLIC_RULESETS),
]
),
Caster::PREFIX_VIRTUAL.'symbols' => new EnumStub(
[
'DECIMAL_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::DECIMAL_SEPARATOR_SYMBOL),
'GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::GROUPING_SEPARATOR_SYMBOL),
'PATTERN_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::PATTERN_SEPARATOR_SYMBOL),
'PERCENT_SYMBOL' => $c->getSymbol(\NumberFormatter::PERCENT_SYMBOL),
'ZERO_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::ZERO_DIGIT_SYMBOL),
'DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::DIGIT_SYMBOL),
'MINUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::MINUS_SIGN_SYMBOL),
'PLUS_SIGN_SYMBOL' => $c->getSymbol(\NumberFormatter::PLUS_SIGN_SYMBOL),
'CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::CURRENCY_SYMBOL),
'INTL_CURRENCY_SYMBOL' => $c->getSymbol(\NumberFormatter::INTL_CURRENCY_SYMBOL),
'MONETARY_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_SEPARATOR_SYMBOL),
'EXPONENTIAL_SYMBOL' => $c->getSymbol(\NumberFormatter::EXPONENTIAL_SYMBOL),
'PERMILL_SYMBOL' => $c->getSymbol(\NumberFormatter::PERMILL_SYMBOL),
'PAD_ESCAPE_SYMBOL' => $c->getSymbol(\NumberFormatter::PAD_ESCAPE_SYMBOL),
'INFINITY_SYMBOL' => $c->getSymbol(\NumberFormatter::INFINITY_SYMBOL),
'NAN_SYMBOL' => $c->getSymbol(\NumberFormatter::NAN_SYMBOL),
'SIGNIFICANT_DIGIT_SYMBOL' => $c->getSymbol(\NumberFormatter::SIGNIFICANT_DIGIT_SYMBOL),
'MONETARY_GROUPING_SEPARATOR_SYMBOL' => $c->getSymbol(\NumberFormatter::MONETARY_GROUPING_SEPARATOR_SYMBOL),
]
),
];
return self::castError($c, $a);
}
public static function castIntlTimeZone(\IntlTimeZone $c, array $a, Stub $stub, $isNested)
{
$a += [
Caster::PREFIX_VIRTUAL.'display_name' => $c->getDisplayName(),
Caster::PREFIX_VIRTUAL.'id' => $c->getID(),
Caster::PREFIX_VIRTUAL.'raw_offset' => $c->getRawOffset(),
];
if ($c->useDaylightTime()) {
$a += [
Caster::PREFIX_VIRTUAL.'dst_savings' => $c->getDSTSavings(),
];
}
return self::castError($c, $a);
}
public static function castIntlCalendar(\IntlCalendar $c, array $a, Stub $stub, $isNested, $filter = 0)
{
$a += [
Caster::PREFIX_VIRTUAL.'type' => $c->getType(),
Caster::PREFIX_VIRTUAL.'first_day_of_week' => $c->getFirstDayOfWeek(),
Caster::PREFIX_VIRTUAL.'minimal_days_in_first_week' => $c->getMinimalDaysInFirstWeek(),
Caster::PREFIX_VIRTUAL.'repeated_wall_time_option' => $c->getRepeatedWallTimeOption(),
Caster::PREFIX_VIRTUAL.'skipped_wall_time_option' => $c->getSkippedWallTimeOption(),
Caster::PREFIX_VIRTUAL.'time' => $c->getTime(),
Caster::PREFIX_VIRTUAL.'in_daylight_time' => $c->inDaylightTime(),
Caster::PREFIX_VIRTUAL.'is_lenient' => $c->isLenient(),
Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(),
];
return self::castError($c, $a);
}
public static function castIntlDateFormatter(\IntlDateFormatter $c, array $a, Stub $stub, $isNested, $filter = 0)
{
$a += [
Caster::PREFIX_VIRTUAL.'locale' => $c->getLocale(),
Caster::PREFIX_VIRTUAL.'pattern' => $c->getPattern(),
Caster::PREFIX_VIRTUAL.'calendar' => $c->getCalendar(),
Caster::PREFIX_VIRTUAL.'time_zone_id' => $c->getTimeZoneId(),
Caster::PREFIX_VIRTUAL.'time_type' => $c->getTimeType(),
Caster::PREFIX_VIRTUAL.'date_type' => $c->getDateType(),
Caster::PREFIX_VIRTUAL.'calendar_object' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getCalendarObject()) : $c->getCalendarObject(),
Caster::PREFIX_VIRTUAL.'time_zone' => ($filter & Caster::EXCLUDE_VERBOSE) ? new CutStub($c->getTimeZone()) : $c->getTimeZone(),
];
return self::castError($c, $a);
}
private static function castError($c, array $a): array
{
if ($errorCode = $c->getErrorCode()) {
$a += [
Caster::PREFIX_VIRTUAL.'error_code' => $errorCode,
Caster::PREFIX_VIRTUAL.'error_message' => $c->getErrorMessage(),
];
}
return $a;
}
}

View File

@ -1,108 +0,0 @@
<?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 $inVendor = false;
private static $vendorRoots;
private static $composerRoots;
public function __construct($label, int $line = 0, $href = null)
{
$this->value = $label;
if (null === $href) {
$href = $label;
}
if (!\is_string($href)) {
return;
}
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)) {
return;
}
if ($line) {
$this->attr['line'] = $line;
}
if ($label !== $this->attr['file'] = realpath($href) ?: $href) {
return;
}
if ($composerRoot = $this->getComposerRoot($href, $this->inVendor)) {
$this->attr['ellipsis'] = \strlen($href) - \strlen($composerRoot) + 1;
$this->attr['ellipsis-type'] = 'path';
$this->attr['ellipsis-tail'] = 1 + ($this->inVendor ? 2 + \strlen(implode('', \array_slice(explode(\DIRECTORY_SEPARATOR, substr($href, 1 - $this->attr['ellipsis'])), 0, 2))) : 0);
} elseif (3 < \count($ellipsis = explode(\DIRECTORY_SEPARATOR, $href))) {
$this->attr['ellipsis'] = 2 + \strlen(implode('', \array_slice($ellipsis, -2)));
$this->attr['ellipsis-type'] = 'path';
$this->attr['ellipsis-tail'] = 1;
}
}
private function getComposerRoot(string $file, bool &$inVendor)
{
if (null === self::$vendorRoots) {
self::$vendorRoots = [];
foreach (get_declared_classes() as $class) {
if ('C' === $class[0] && 0 === strpos($class, 'ComposerAutoloaderInit')) {
$r = new \ReflectionClass($class);
$v = \dirname($r->getFileName(), 2);
if (file_exists($v.'/composer/installed.json')) {
self::$vendorRoots[] = $v.\DIRECTORY_SEPARATOR;
}
}
}
}
$inVendor = false;
if (isset(self::$composerRoots[$dir = \dirname($file)])) {
return self::$composerRoots[$dir];
}
foreach (self::$vendorRoots as $root) {
if ($inVendor = 0 === strpos($file, $root)) {
return $root;
}
}
$parent = $dir;
while (!@file_exists($parent.'/composer.json')) {
if (!@file_exists($parent)) {
// open_basedir restriction in effect
break;
}
if ($parent === \dirname($parent)) {
return self::$composerRoots[$dir] = false;
}
$parent = \dirname($parent);
}
return self::$composerRoots[$dir] = $parent.\DIRECTORY_SEPARATOR;
}
}

View File

@ -1,81 +0,0 @@
<?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;
/**
* @author Jan Schädlich <jan.schaedlich@sensiolabs.de>
*
* @final since Symfony 4.4
*/
class MemcachedCaster
{
private static $optionConstants;
private static $defaultOptions;
public static function castMemcached(\Memcached $c, array $a, Stub $stub, $isNested)
{
$a += [
Caster::PREFIX_VIRTUAL.'servers' => $c->getServerList(),
Caster::PREFIX_VIRTUAL.'options' => new EnumStub(
self::getNonDefaultOptions($c)
),
];
return $a;
}
private static function getNonDefaultOptions(\Memcached $c): array
{
self::$defaultOptions = self::$defaultOptions ?? self::discoverDefaultOptions();
self::$optionConstants = self::$optionConstants ?? self::getOptionConstants();
$nonDefaultOptions = [];
foreach (self::$optionConstants as $constantKey => $value) {
if (self::$defaultOptions[$constantKey] !== $option = $c->getOption($value)) {
$nonDefaultOptions[$constantKey] = $option;
}
}
return $nonDefaultOptions;
}
private static function discoverDefaultOptions(): array
{
$defaultMemcached = new \Memcached();
$defaultMemcached->addServer('127.0.0.1', 11211);
$defaultOptions = [];
self::$optionConstants = self::$optionConstants ?? self::getOptionConstants();
foreach (self::$optionConstants as $constantKey => $value) {
$defaultOptions[$constantKey] = $defaultMemcached->getOption($value);
}
return $defaultOptions;
}
private static function getOptionConstants(): array
{
$reflectedMemcached = new \ReflectionClass(\Memcached::class);
$optionConstants = [];
foreach ($reflectedMemcached->getConstants() as $constantKey => $value) {
if (0 === strpos($constantKey, 'OPT_')) {
$optionConstants[$constantKey] = $value;
}
}
return $optionConstants;
}
}

View File

@ -11,22 +11,23 @@
namespace Symfony\Component\VarDumper\Caster; namespace Symfony\Component\VarDumper\Caster;
use ProxyManager\Proxy\ProxyInterface;
use Symfony\Component\VarDumper\Cloner\Stub; use Symfony\Component\VarDumper\Cloner\Stub;
/** /**
* @author Nicolas Grekas <p@tchwork.com> * Casts classes from the MongoDb extension to array representation.
* *
* @final since Symfony 4.4 * @author Nicolas Grekas <p@tchwork.com>
*/ */
class ProxyManagerCaster class MongoCaster
{ {
public static function castProxy(ProxyInterface $c, array $a, Stub $stub, $isNested) public static function castCursor(\MongoCursorInterface $cursor, array $a, Stub $stub, $isNested)
{ {
if ($parent = get_parent_class($c)) { if ($info = $cursor->info()) {
$stub->class .= ' - '.$parent; foreach ($info as $k => $v) {
$a[Caster::PREFIX_VIRTUAL.$k] = $v;
}
} }
$stub->class .= '@proxy'; $a[Caster::PREFIX_VIRTUAL.'dead'] = $cursor->dead();
return $a; return $a;
} }

View File

@ -17,81 +17,73 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts PDO related classes to array representation. * Casts PDO related classes to array representation.
* *
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/ */
class PdoCaster class PdoCaster
{ {
private static $pdoAttributes = [ private static $pdoAttributes = array(
'CASE' => [ 'CASE' => array(
\PDO::CASE_LOWER => 'LOWER', \PDO::CASE_LOWER => 'LOWER',
\PDO::CASE_NATURAL => 'NATURAL', \PDO::CASE_NATURAL => 'NATURAL',
\PDO::CASE_UPPER => 'UPPER', \PDO::CASE_UPPER => 'UPPER',
], ),
'ERRMODE' => [ 'ERRMODE' => array(
\PDO::ERRMODE_SILENT => 'SILENT', \PDO::ERRMODE_SILENT => 'SILENT',
\PDO::ERRMODE_WARNING => 'WARNING', \PDO::ERRMODE_WARNING => 'WARNING',
\PDO::ERRMODE_EXCEPTION => 'EXCEPTION', \PDO::ERRMODE_EXCEPTION => 'EXCEPTION',
], ),
'TIMEOUT', 'TIMEOUT',
'PREFETCH', 'PREFETCH',
'AUTOCOMMIT', 'AUTOCOMMIT',
'PERSISTENT', 'PERSISTENT',
'DRIVER_NAME', 'DRIVER_NAME',
'SERVER_INFO', 'SERVER_INFO',
'ORACLE_NULLS' => [ 'ORACLE_NULLS' => array(
\PDO::NULL_NATURAL => 'NATURAL', \PDO::NULL_NATURAL => 'NATURAL',
\PDO::NULL_EMPTY_STRING => 'EMPTY_STRING', \PDO::NULL_EMPTY_STRING => 'EMPTY_STRING',
\PDO::NULL_TO_STRING => 'TO_STRING', \PDO::NULL_TO_STRING => 'TO_STRING',
], ),
'CLIENT_VERSION', 'CLIENT_VERSION',
'SERVER_VERSION', 'SERVER_VERSION',
'STATEMENT_CLASS', 'STATEMENT_CLASS',
'EMULATE_PREPARES', 'EMULATE_PREPARES',
'CONNECTION_STATUS', 'CONNECTION_STATUS',
'STRINGIFY_FETCHES', 'STRINGIFY_FETCHES',
'DEFAULT_FETCH_MODE' => [ 'DEFAULT_FETCH_MODE' => array(
\PDO::FETCH_ASSOC => 'ASSOC', \PDO::FETCH_ASSOC => 'ASSOC',
\PDO::FETCH_BOTH => 'BOTH', \PDO::FETCH_BOTH => 'BOTH',
\PDO::FETCH_LAZY => 'LAZY', \PDO::FETCH_LAZY => 'LAZY',
\PDO::FETCH_NUM => 'NUM', \PDO::FETCH_NUM => 'NUM',
\PDO::FETCH_OBJ => 'OBJ', \PDO::FETCH_OBJ => 'OBJ',
], ),
]; );
public static function castPdo(\PDO $c, array $a, Stub $stub, $isNested) public static function castPdo(\PDO $c, array $a, Stub $stub, $isNested)
{ {
$attr = []; $attr = array();
$errmode = $c->getAttribute(\PDO::ATTR_ERRMODE); $errmode = $c->getAttribute(\PDO::ATTR_ERRMODE);
$c->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); $c->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
foreach (self::$pdoAttributes as $k => $v) { foreach (self::$pdoAttributes as $k => $v) {
if (!isset($k[0])) { if (!isset($k[0])) {
$k = $v; $k = $v;
$v = []; $v = array();
} }
try { try {
$attr[$k] = 'ERRMODE' === $k ? $errmode : $c->getAttribute(\constant('PDO::ATTR_'.$k)); $attr[$k] = 'ERRMODE' === $k ? $errmode : $c->getAttribute(constant('PDO::ATTR_'.$k));
if ($v && isset($v[$attr[$k]])) { if ($v && isset($v[$attr[$k]])) {
$attr[$k] = new ConstStub($v[$attr[$k]], $attr[$k]); $attr[$k] = new ConstStub($v[$attr[$k]], $attr[$k]);
} }
} 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 += [ $a += array(
$prefix.'inTransaction' => method_exists($c, 'inTransaction'), $prefix.'inTransaction' => method_exists($c, 'inTransaction'),
$prefix.'errorInfo' => $c->errorInfo(), $prefix.'errorInfo' => $c->errorInfo(),
$prefix.'attributes' => new EnumStub($attr), $prefix.'attributes' => new EnumStub($attr),
]; );
if ($a[$prefix.'inTransaction']) { if ($a[$prefix.'inTransaction']) {
$a[$prefix.'inTransaction'] = $c->inTransaction(); $a[$prefix.'inTransaction'] = $c->inTransaction();

View File

@ -17,12 +17,10 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts pqsql resources to array representation. * Casts pqsql resources to array representation.
* *
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/ */
class PgSqlCaster class PgSqlCaster
{ {
private static $paramCodes = [ private static $paramCodes = array(
'server_encoding', 'server_encoding',
'client_encoding', 'client_encoding',
'is_superuser', 'is_superuser',
@ -33,17 +31,17 @@ class PgSqlCaster
'integer_datetimes', 'integer_datetimes',
'application_name', 'application_name',
'standard_conforming_strings', 'standard_conforming_strings',
]; );
private static $transactionStatus = [ private static $transactionStatus = array(
PGSQL_TRANSACTION_IDLE => 'PGSQL_TRANSACTION_IDLE', PGSQL_TRANSACTION_IDLE => 'PGSQL_TRANSACTION_IDLE',
PGSQL_TRANSACTION_ACTIVE => 'PGSQL_TRANSACTION_ACTIVE', PGSQL_TRANSACTION_ACTIVE => 'PGSQL_TRANSACTION_ACTIVE',
PGSQL_TRANSACTION_INTRANS => 'PGSQL_TRANSACTION_INTRANS', PGSQL_TRANSACTION_INTRANS => 'PGSQL_TRANSACTION_INTRANS',
PGSQL_TRANSACTION_INERROR => 'PGSQL_TRANSACTION_INERROR', PGSQL_TRANSACTION_INERROR => 'PGSQL_TRANSACTION_INERROR',
PGSQL_TRANSACTION_UNKNOWN => 'PGSQL_TRANSACTION_UNKNOWN', PGSQL_TRANSACTION_UNKNOWN => 'PGSQL_TRANSACTION_UNKNOWN',
]; );
private static $resultStatus = [ private static $resultStatus = array(
PGSQL_EMPTY_QUERY => 'PGSQL_EMPTY_QUERY', PGSQL_EMPTY_QUERY => 'PGSQL_EMPTY_QUERY',
PGSQL_COMMAND_OK => 'PGSQL_COMMAND_OK', PGSQL_COMMAND_OK => 'PGSQL_COMMAND_OK',
PGSQL_TUPLES_OK => 'PGSQL_TUPLES_OK', PGSQL_TUPLES_OK => 'PGSQL_TUPLES_OK',
@ -52,9 +50,9 @@ class PgSqlCaster
PGSQL_BAD_RESPONSE => 'PGSQL_BAD_RESPONSE', PGSQL_BAD_RESPONSE => 'PGSQL_BAD_RESPONSE',
PGSQL_NONFATAL_ERROR => 'PGSQL_NONFATAL_ERROR', PGSQL_NONFATAL_ERROR => 'PGSQL_NONFATAL_ERROR',
PGSQL_FATAL_ERROR => 'PGSQL_FATAL_ERROR', PGSQL_FATAL_ERROR => 'PGSQL_FATAL_ERROR',
]; );
private static $diagCodes = [ private static $diagCodes = array(
'severity' => PGSQL_DIAG_SEVERITY, 'severity' => PGSQL_DIAG_SEVERITY,
'sqlstate' => PGSQL_DIAG_SQLSTATE, 'sqlstate' => PGSQL_DIAG_SQLSTATE,
'message' => PGSQL_DIAG_MESSAGE_PRIMARY, 'message' => PGSQL_DIAG_MESSAGE_PRIMARY,
@ -67,7 +65,7 @@ class PgSqlCaster
'file' => PGSQL_DIAG_SOURCE_FILE, 'file' => PGSQL_DIAG_SOURCE_FILE,
'line' => PGSQL_DIAG_SOURCE_LINE, 'line' => PGSQL_DIAG_SOURCE_LINE,
'function' => PGSQL_DIAG_SOURCE_FUNCTION, 'function' => PGSQL_DIAG_SOURCE_FUNCTION,
]; );
public static function castLargeObject($lo, array $a, Stub $stub, $isNested) public static function castLargeObject($lo, array $a, Stub $stub, $isNested)
{ {
@ -129,14 +127,14 @@ class PgSqlCaster
$fields = pg_num_fields($result); $fields = pg_num_fields($result);
for ($i = 0; $i < $fields; ++$i) { for ($i = 0; $i < $fields; ++$i) {
$field = [ $field = array(
'name' => pg_field_name($result, $i), 'name' => pg_field_name($result, $i),
'table' => sprintf('%s (OID: %s)', pg_field_table($result, $i), pg_field_table($result, $i, true)), 'table' => sprintf('%s (OID: %s)', pg_field_table($result, $i), pg_field_table($result, $i, true)),
'type' => sprintf('%s (OID: %s)', pg_field_type($result, $i), pg_field_type_oid($result, $i)), 'type' => sprintf('%s (OID: %s)', pg_field_type($result, $i), pg_field_type_oid($result, $i)),
'nullable' => (bool) pg_field_is_null($result, $i), 'nullable' => (bool) pg_field_is_null($result, $i),
'storage' => pg_field_size($result, $i).' bytes', 'storage' => pg_field_size($result, $i).' bytes',
'display' => pg_field_prtlen($result, $i).' chars', 'display' => pg_field_prtlen($result, $i).' chars',
]; );
if (' (OID: )' === $field['table']) { if (' (OID: )' === $field['table']) {
$field['table'] = null; $field['table'] = null;
} }

View File

@ -1,152 +0,0 @@
<?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>
*
* @final since Symfony 4.4
*/
class RedisCaster
{
private static $serializer = [
\Redis::SERIALIZER_NONE => 'NONE',
\Redis::SERIALIZER_PHP => 'PHP',
2 => 'IGBINARY', // Optional Redis::SERIALIZER_IGBINARY
];
private static $mode = [
\Redis::ATOMIC => 'ATOMIC',
\Redis::MULTI => 'MULTI',
\Redis::PIPELINE => 'PIPELINE',
];
private static $compression = [
0 => 'NONE', // Redis::COMPRESSION_NONE
1 => 'LZF', // Redis::COMPRESSION_LZF
];
private static $failover = [
\RedisCluster::FAILOVER_NONE => 'NONE',
\RedisCluster::FAILOVER_ERROR => 'ERROR',
\RedisCluster::FAILOVER_DISTRIBUTE => 'DISTRIBUTE',
\RedisCluster::FAILOVER_DISTRIBUTE_SLAVES => 'DISTRIBUTE_SLAVES',
];
public static function castRedis(\Redis $c, array $a, Stub $stub, $isNested)
{
$prefix = Caster::PREFIX_VIRTUAL;
if (!$connected = $c->isConnected()) {
return $a + [
$prefix.'isConnected' => $connected,
];
}
$mode = $c->getMode();
return $a + [
$prefix.'isConnected' => $connected,
$prefix.'host' => $c->getHost(),
$prefix.'port' => $c->getPort(),
$prefix.'auth' => $c->getAuth(),
$prefix.'mode' => isset(self::$mode[$mode]) ? new ConstStub(self::$mode[$mode], $mode) : $mode,
$prefix.'dbNum' => $c->getDbNum(),
$prefix.'timeout' => $c->getTimeout(),
$prefix.'lastError' => $c->getLastError(),
$prefix.'persistentId' => $c->getPersistentID(),
$prefix.'options' => self::getRedisOptions($c),
];
}
public static function castRedisArray(\RedisArray $c, array $a, Stub $stub, $isNested)
{
$prefix = Caster::PREFIX_VIRTUAL;
return $a + [
$prefix.'hosts' => $c->_hosts(),
$prefix.'function' => ClassStub::wrapCallable($c->_function()),
$prefix.'lastError' => $c->getLastError(),
$prefix.'options' => self::getRedisOptions($c),
];
}
public static function castRedisCluster(\RedisCluster $c, array $a, Stub $stub, $isNested)
{
$prefix = Caster::PREFIX_VIRTUAL;
$failover = $c->getOption(\RedisCluster::OPT_SLAVE_FAILOVER);
$a += [
$prefix.'_masters' => $c->_masters(),
$prefix.'_redir' => $c->_redir(),
$prefix.'mode' => new ConstStub($c->getMode() ? 'MULTI' : 'ATOMIC', $c->getMode()),
$prefix.'lastError' => $c->getLastError(),
$prefix.'options' => self::getRedisOptions($c, [
'SLAVE_FAILOVER' => isset(self::$failover[$failover]) ? new ConstStub(self::$failover[$failover], $failover) : $failover,
]),
];
return $a;
}
/**
* @param \Redis|\RedisArray|\RedisCluster $redis
*/
private static function getRedisOptions($redis, array $options = []): EnumStub
{
$serializer = $redis->getOption(\Redis::OPT_SERIALIZER);
if (\is_array($serializer)) {
foreach ($serializer as &$v) {
if (isset(self::$serializer[$v])) {
$v = new ConstStub(self::$serializer[$v], $v);
}
}
} elseif (isset(self::$serializer[$serializer])) {
$serializer = new ConstStub(self::$serializer[$serializer], $serializer);
}
$compression = \defined('Redis::OPT_COMPRESSION') ? $redis->getOption(\Redis::OPT_COMPRESSION) : 0;
if (\is_array($compression)) {
foreach ($compression as &$v) {
if (isset(self::$compression[$v])) {
$v = new ConstStub(self::$compression[$v], $v);
}
}
} elseif (isset(self::$compression[$compression])) {
$compression = new ConstStub(self::$compression[$compression], $compression);
}
$retry = \defined('Redis::OPT_SCAN') ? $redis->getOption(\Redis::OPT_SCAN) : 0;
if (\is_array($retry)) {
foreach ($retry as &$v) {
$v = new ConstStub($v ? 'RETRY' : 'NORETRY', $v);
}
} else {
$retry = new ConstStub($retry ? 'RETRY' : 'NORETRY', $retry);
}
$options += [
'TCP_KEEPALIVE' => \defined('Redis::OPT_TCP_KEEPALIVE') ? $redis->getOption(\Redis::OPT_TCP_KEEPALIVE) : 0,
'READ_TIMEOUT' => $redis->getOption(\Redis::OPT_READ_TIMEOUT),
'COMPRESSION' => $compression,
'SERIALIZER' => $serializer,
'PREFIX' => $redis->getOption(\Redis::OPT_PREFIX),
'SCAN' => $retry,
];
return new EnumStub($options);
}
}

View File

@ -17,14 +17,10 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts Reflector related classes to array representation. * Casts Reflector related classes to array representation.
* *
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/ */
class ReflectionCaster class ReflectionCaster
{ {
const UNSET_CLOSURE_FILE_INFO = ['Closure' => __CLASS__.'::unsetClosureFileInfo']; private static $extraMap = array(
private static $extraMap = [
'docComment' => 'getDocComment', 'docComment' => 'getDocComment',
'extension' => 'getExtensionName', 'extension' => 'getExtensionName',
'isDisabled' => 'isDisabled', 'isDisabled' => 'isDisabled',
@ -33,74 +29,54 @@ class ReflectionCaster
'isUserDefined' => 'isUserDefined', 'isUserDefined' => 'isUserDefined',
'isGenerator' => 'isGenerator', 'isGenerator' => 'isGenerator',
'isVariadic' => 'isVariadic', 'isVariadic' => 'isVariadic',
]; );
public static function castClosure(\Closure $c, array $a, Stub $stub, $isNested, $filter = 0) public static function castClosure(\Closure $c, array $a, Stub $stub, $isNested)
{ {
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
$c = new \ReflectionFunction($c); $c = new \ReflectionFunction($c);
$a = static::castFunctionAbstract($c, $a, $stub, $isNested, $filter); $stub->class = 'Closure'; // HHVM generates unique class names for closures
$a = static::castFunctionAbstract($c, $a, $stub, $isNested);
if (false === strpos($c->name, '{closure}')) { if (isset($a[$prefix.'parameters'])) {
$stub->class = isset($a[$prefix.'class']) ? $a[$prefix.'class']->value.'::'.$c->name : $c->name; foreach ($a[$prefix.'parameters']->value as &$v) {
unset($a[$prefix.'class']); $param = $v;
$v = new EnumStub(array());
foreach (static::castParameter($param, array(), $stub, true) as $k => $param) {
if ("\0" === $k[0]) {
$v->value[substr($k, 3)] = $param;
}
}
unset($v->value['position'], $v->value['isVariadic'], $v->value['byReference'], $v);
}
} }
unset($a[$prefix.'extra']);
$stub->class .= self::getSignature($a);
if ($f = $c->getFileName()) { if ($f = $c->getFileName()) {
$stub->attr['file'] = $f; $a[$prefix.'file'] = $f;
$stub->attr['line'] = $c->getStartLine();
}
unset($a[$prefix.'parameters']);
if ($filter & Caster::EXCLUDE_VERBOSE) {
$stub->cut += ($c->getFileName() ? 2 : 0) + \count($a);
return [];
}
if ($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();
} }
return $a; $prefix = Caster::PREFIX_DYNAMIC;
} unset($a['name'], $a[$prefix.'0'], $a[$prefix.'this'], $a[$prefix.'parameter'], $a[Caster::PREFIX_VIRTUAL.'extra']);
public static function unsetClosureFileInfo(\Closure $c, array $a)
{
unset($a[Caster::PREFIX_VIRTUAL.'file'], $a[Caster::PREFIX_VIRTUAL.'line']);
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)
{ {
// Cannot create ReflectionGenerator based on a terminated Generator return class_exists('ReflectionGenerator', false) ? self::castReflectionGenerator(new \ReflectionGenerator($c), $a, $stub, $isNested) : $a;
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)
{ {
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
$a += [ $a += array(
$prefix.'name' => $c instanceof \ReflectionNamedType ? $c->getName() : (string) $c, $prefix.'type' => $c->__toString(),
$prefix.'allowsNull' => $c->allowsNull(), $prefix.'allowsNull' => $c->allowsNull(),
$prefix.'isBuiltin' => $c->isBuiltin(), $prefix.'isBuiltin' => $c->isBuiltin(),
]; );
return $a; return $a;
} }
@ -112,31 +88,31 @@ class ReflectionCaster
if ($c->getThis()) { if ($c->getThis()) {
$a[$prefix.'this'] = new CutStub($c->getThis()); $a[$prefix.'this'] = new CutStub($c->getThis());
} }
$function = $c->getFunction(); $x = $c->getFunction();
$frame = [ $frame = array(
'class' => isset($function->class) ? $function->class : null, 'class' => isset($x->class) ? $x->class : null,
'type' => isset($function->class) ? ($function->isStatic() ? '::' : '->') : null, 'type' => isset($x->class) ? ($x->isStatic() ? '::' : '->') : null,
'function' => $function->name, 'function' => $x->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)) {
$function = new \ReflectionGenerator($c->getExecutingGenerator()); $x = new \ReflectionGenerator($c->getExecutingGenerator());
array_unshift($trace, [ array_unshift($trace, array(
'function' => 'yield', 'function' => 'yield',
'file' => $function->getExecutingFile(), 'file' => $x->getExecutingFile(),
'line' => $function->getExecutingLine() - 1, 'line' => $x->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 {
$function = new FrameStub($frame, false, true); $x = new FrameStub($frame, false, true);
$function = ExceptionCaster::castFrameStub($function, [], $function, true); $x = ExceptionCaster::castFrameStub($x, array(), $x, true);
$a[$prefix.'executing'] = $function[$prefix.'src']; $a[$prefix.'executing'] = new EnumStub(array(
$frame['class'].$frame['type'].$frame['function'].'()' => $x[$prefix.'src'],
));
} }
$a[Caster::PREFIX_VIRTUAL.'closed'] = false;
return $a; return $a;
} }
@ -148,11 +124,11 @@ class ReflectionCaster
$a[$prefix.'modifiers'] = implode(' ', $n); $a[$prefix.'modifiers'] = implode(' ', $n);
} }
self::addMap($a, $c, [ self::addMap($a, $c, array(
'extends' => 'getParentClass', 'extends' => 'getParentClass',
'implements' => 'getInterfaceNames', 'implements' => 'getInterfaceNames',
'constants' => 'getConstants', 'constants' => 'getConstants',
]); ));
foreach ($c->getProperties() as $n) { foreach ($c->getProperties() as $n) {
$a[$prefix.'properties'][$n->name] = $n; $a[$prefix.'properties'][$n->name] = $n;
@ -173,20 +149,15 @@ class ReflectionCaster
{ {
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
self::addMap($a, $c, [ self::addMap($a, $c, array(
'returnsReference' => 'returnsReference', 'returnsReference' => 'returnsReference',
'returnType' => 'getReturnType', 'returnType' => 'getReturnType',
'class' => 'getClosureScopeClass', 'class' => 'getClosureScopeClass',
'this' => 'getClosureThis', 'this' => 'getClosureThis',
]); ));
if (isset($a[$prefix.'returnType'])) { if (isset($a[$prefix.'returnType'])) {
$v = $a[$prefix.'returnType']; $a[$prefix.'returnType'] = (string) $a[$prefix.'returnType'];
$v = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v;
$a[$prefix.'returnType'] = new ClassStub($a[$prefix.'returnType']->allowsNull() ? '?'.$v : $v, [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']);
@ -194,25 +165,21 @@ class ReflectionCaster
foreach ($c->getParameters() as $v) { foreach ($c->getParameters() as $v) {
$k = '$'.$v->name; $k = '$'.$v->name;
if ($v->isVariadic()) {
$k = '...'.$k;
}
if ($v->isPassedByReference()) { if ($v->isPassedByReference()) {
$k = '&'.$k; $k = '&'.$k;
} }
if (method_exists($v, 'isVariadic') && $v->isVariadic()) {
$k = '...'.$k;
}
$a[$prefix.'parameters'][$k] = $v; $a[$prefix.'parameters'][$k] = $v;
} }
if (isset($a[$prefix.'parameters'])) { if (isset($a[$prefix.'parameters'])) {
$a[$prefix.'parameters'] = new EnumStub($a[$prefix.'parameters']); $a[$prefix.'parameters'] = new EnumStub($a[$prefix.'parameters']);
} }
if (!($filter & Caster::EXCLUDE_VERBOSE) && $v = $c->getStaticVariables()) { if ($v = $c->getStaticVariables()) {
foreach ($v as $k => &$v) { foreach ($v as $k => &$v) {
if (\is_object($v)) { $a[$prefix.'use']['$'.$k] = &$v;
$a[$prefix.'use']['$'.$k] = new CutStub($v);
} else {
$a[$prefix.'use']['$'.$k] = &$v;
}
} }
unset($v); unset($v);
$a[$prefix.'use'] = new EnumStub($a[$prefix.'use']); $a[$prefix.'use'] = new EnumStub($a[$prefix.'use']);
@ -222,6 +189,9 @@ class ReflectionCaster
self::addExtra($a, $c); self::addExtra($a, $c);
} }
// Added by HHVM
unset($a[Caster::PREFIX_DYNAMIC.'static']);
return $a; return $a;
} }
@ -236,33 +206,42 @@ class ReflectionCaster
{ {
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
self::addMap($a, $c, [ // Added by HHVM
unset($a['info']);
self::addMap($a, $c, array(
'position' => 'getPosition', 'position' => 'getPosition',
'isVariadic' => 'isVariadic', 'isVariadic' => 'isVariadic',
'byReference' => 'isPassedByReference', 'byReference' => 'isPassedByReference',
'allowsNull' => 'allowsNull', ));
]);
if ($v = $c->getType()) { try {
$a[$prefix.'typeHint'] = $v instanceof \ReflectionNamedType ? $v->getName() : (string) $v; if (method_exists($c, 'hasType')) {
} if ($c->hasType()) {
$a[$prefix.'typeHint'] = $c->getType()->__toString();
if (isset($a[$prefix.'typeHint'])) { }
$v = $a[$prefix.'typeHint']; } elseif ($c->isArray()) {
$a[$prefix.'typeHint'] = new ClassStub($v, [class_exists($v, false) || interface_exists($v, false) || trait_exists($v, false) ? $v : '', '']); $a[$prefix.'typeHint'] = 'array';
} else { } elseif (method_exists($c, 'isCallable') && $c->isCallable()) {
unset($a[$prefix.'allowsNull']); $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];
}
} }
try { try {
$a[$prefix.'default'] = $v = $c->getDefaultValue(); $a[$prefix.'default'] = $v = $c->getDefaultValue();
if ($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()) {
$a[$prefix.'default'] = null;
}
} }
return $a; return $a;
@ -276,16 +255,9 @@ class ReflectionCaster
return $a; return $a;
} }
public static function castReference(\ReflectionReference $c, array $a, Stub $stub, $isNested)
{
$a[Caster::PREFIX_VIRTUAL.'id'] = $c->getId();
return $a;
}
public static function castExtension(\ReflectionExtension $c, array $a, Stub $stub, $isNested) public static function castExtension(\ReflectionExtension $c, array $a, Stub $stub, $isNested)
{ {
self::addMap($a, $c, [ self::addMap($a, $c, array(
'version' => 'getVersion', 'version' => 'getVersion',
'dependencies' => 'getDependencies', 'dependencies' => 'getDependencies',
'iniEntries' => 'getIniEntries', 'iniEntries' => 'getIniEntries',
@ -294,79 +266,29 @@ class ReflectionCaster
'constants' => 'getConstants', 'constants' => 'getConstants',
'functions' => 'getFunctions', 'functions' => 'getFunctions',
'classes' => 'getClasses', 'classes' => 'getClasses',
]); ));
return $a; return $a;
} }
public static function castZendExtension(\ReflectionZendExtension $c, array $a, Stub $stub, $isNested) public static function castZendExtension(\ReflectionZendExtension $c, array $a, Stub $stub, $isNested)
{ {
self::addMap($a, $c, [ self::addMap($a, $c, array(
'version' => 'getVersion', 'version' => 'getVersion',
'author' => 'getAuthor', 'author' => 'getAuthor',
'copyright' => 'getCopyright', 'copyright' => 'getCopyright',
'url' => 'getURL', 'url' => 'getURL',
]); ));
return $a; return $a;
} }
public static function getSignature(array $a) private static function addExtra(&$a, \Reflector $c)
{ {
$prefix = Caster::PREFIX_VIRTUAL; $x = isset($a[Caster::PREFIX_VIRTUAL.'extra']) ? $a[Caster::PREFIX_VIRTUAL.'extra']->value : array();
$signature = '';
if (isset($a[$prefix.'parameters'])) {
foreach ($a[$prefix.'parameters']->value as $k => $param) {
$signature .= ', ';
if ($type = $param->getType()) {
if (!$type instanceof \ReflectionNamedType) {
$signature .= $type.' ';
} else {
if (!$param->isOptional() && $param->allowsNull()) {
$signature .= '?';
}
$signature .= substr(strrchr('\\'.$type->getName(), '\\'), 1).' ';
}
}
$signature .= $k;
if (!$param->isDefaultValueAvailable()) {
continue;
}
$v = $param->getDefaultValue();
$signature .= ' = ';
if ($param->isDefaultValueConstant()) {
$signature .= substr(strrchr('\\'.$param->getDefaultValueConstantName(), '\\'), 1);
} elseif (null === $v) {
$signature .= 'null';
} elseif (\is_array($v)) {
$signature .= $v ? '[…'.\count($v).']' : '[]';
} elseif (\is_string($v)) {
$signature .= 10 > \strlen($v) && false === strpos($v, '\\') ? "'{$v}'" : "'…".\strlen($v)."'";
} elseif (\is_bool($v)) {
$signature .= $v ? 'true' : 'false';
} else {
$signature .= $v;
}
}
}
$signature = (empty($a[$prefix.'returnsReference']) ? '' : '&').'('.substr($signature, 2).')';
if (isset($a[$prefix.'returnType'])) {
$signature .= ': '.substr(strrchr('\\'.$a[$prefix.'returnType'], '\\'), 1);
}
return $signature;
}
private static function addExtra(array &$a, \Reflector $c)
{
$x = isset($a[Caster::PREFIX_VIRTUAL.'extra']) ? $a[Caster::PREFIX_VIRTUAL.'extra']->value : [];
if (method_exists($c, 'getFileName') && $m = $c->getFileName()) { if (method_exists($c, 'getFileName') && $m = $c->getFileName()) {
$x['file'] = new LinkStub($m, $c->getStartLine()); $x['file'] = $m;
$x['line'] = $c->getStartLine().' to '.$c->getEndLine(); $x['line'] = $c->getStartLine().' to '.$c->getEndLine();
} }
@ -377,13 +299,9 @@ class ReflectionCaster
} }
} }
private static function addMap(array &$a, \Reflector $c, array $map, string $prefix = Caster::PREFIX_VIRTUAL) private static function addMap(&$a, \Reflector $c, $map, $prefix = Caster::PREFIX_VIRTUAL)
{ {
foreach ($map as $k => $m) { foreach ($map as $k => $m) {
if (\PHP_VERSION_ID >= 80000 && 'isDisabled' === $k) {
continue;
}
if (method_exists($c, $m) && false !== ($m = $c->$m()) && null !== $m) { if (method_exists($c, $m) && false !== ($m = $c->$m()) && null !== $m) {
$a[$prefix.$k] = $m instanceof \Reflector ? $m->name : $m; $a[$prefix.$k] = $m instanceof \Reflector ? $m->name : $m;
} }

View File

@ -17,16 +17,9 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts common resource types to array representation. * Casts common resource types to array representation.
* *
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/ */
class ResourceCaster class ResourceCaster
{ {
/**
* @param \CurlHandle|resource $h
*
* @return array
*/
public static function castCurl($h, array $a, Stub $stub, $isNested) public static function castCurl($h, array $a, Stub $stub, $isNested)
{ {
return curl_getinfo($h); return curl_getinfo($h);
@ -47,17 +40,12 @@ class ResourceCaster
public static function castStream($stream, array $a, Stub $stub, $isNested) public static function castStream($stream, array $a, Stub $stub, $isNested)
{ {
$a = stream_get_meta_data($stream) + static::castStreamContext($stream, $a, $stub, $isNested); return 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) ?: $a; return stream_context_get_params($stream);
} }
public static function castGd($gd, array $a, Stub $stub, $isNested) public static function castGd($gd, array $a, Stub $stub, $isNested)
@ -76,30 +64,4 @@ class ResourceCaster
return $a; return $a;
} }
public static function castOpensslX509($h, array $a, Stub $stub, $isNested)
{
$stub->cut = -1;
$info = openssl_x509_parse($h, false);
$pin = openssl_pkey_get_public($h);
$pin = openssl_pkey_get_details($pin)['key'];
$pin = \array_slice(explode("\n", $pin), 1, -2);
$pin = base64_decode(implode('', $pin));
$pin = base64_encode(hash('sha256', $pin, true));
$a += [
'subject' => new EnumStub(array_intersect_key($info['subject'], ['organizationName' => true, 'commonName' => true])),
'issuer' => new EnumStub(array_intersect_key($info['issuer'], ['organizationName' => true, 'commonName' => true])),
'expiry' => new ConstStub(date(\DateTime::ISO8601, $info['validTo_time_t']), $info['validTo_time_t']),
'fingerprint' => new EnumStub([
'md5' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'md5')), 2, ':', true)),
'sha1' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha1')), 2, ':', true)),
'sha256' => new ConstStub(wordwrap(strtoupper(openssl_x509_fingerprint($h, 'sha256')), 2, ':', true)),
'pin-sha256' => new ConstStub($pin),
]),
];
return $a;
}
} }

View File

@ -17,33 +17,49 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts SPL related classes to array representation. * Casts SPL related classes to array representation.
* *
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/ */
class SplCaster class SplCaster
{ {
private static $splFileObjectFlags = [ private static $splFileObjectFlags = array(
\SplFileObject::DROP_NEW_LINE => 'DROP_NEW_LINE', \SplFileObject::DROP_NEW_LINE => 'DROP_NEW_LINE',
\SplFileObject::READ_AHEAD => 'READ_AHEAD', \SplFileObject::READ_AHEAD => 'READ_AHEAD',
\SplFileObject::SKIP_EMPTY => 'SKIP_EMPTY', \SplFileObject::SKIP_EMPTY => 'SKIP_EMPTY',
\SplFileObject::READ_CSV => 'READ_CSV', \SplFileObject::READ_CSV => 'READ_CSV',
]; );
public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, $isNested) public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, $isNested)
{ {
return self::castSplArray($c, $a, $stub, $isNested); $prefix = Caster::PREFIX_VIRTUAL;
} $class = $stub->class;
$flags = $c->getFlags();
public static function castArrayIterator(\ArrayIterator $c, array $a, Stub $stub, $isNested) $b = array(
{ $prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST),
return self::castSplArray($c, $a, $stub, $isNested); $prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS),
$prefix.'iteratorClass' => $c->getIteratorClass(),
$prefix.'storage' => $c->getArrayCopy(),
);
if ($class === 'ArrayObject') {
$a = $b;
} else {
if (!($flags & \ArrayObject::STD_PROP_LIST)) {
$c->setFlags(\ArrayObject::STD_PROP_LIST);
$a = Caster::castObject($c, new \ReflectionClass($class));
$c->setFlags($flags);
}
$a += $b;
}
return $a;
} }
public static function castHeap(\Iterator $c, array $a, Stub $stub, $isNested) public static function castHeap(\Iterator $c, array $a, Stub $stub, $isNested)
{ {
$a += [ $a += array(
Caster::PREFIX_VIRTUAL.'heap' => iterator_to_array(clone $c), Caster::PREFIX_VIRTUAL.'heap' => iterator_to_array(clone $c),
]; );
return $a; return $a;
} }
@ -54,10 +70,10 @@ class SplCaster
$mode = $c->getIteratorMode(); $mode = $c->getIteratorMode();
$c->setIteratorMode(\SplDoublyLinkedList::IT_MODE_KEEP | $mode & ~\SplDoublyLinkedList::IT_MODE_DELETE); $c->setIteratorMode(\SplDoublyLinkedList::IT_MODE_KEEP | $mode & ~\SplDoublyLinkedList::IT_MODE_DELETE);
$a += [ $a += array(
$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.'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.'dllist' => iterator_to_array($c), $prefix.'dllist' => iterator_to_array($c),
]; );
$c->setIteratorMode($mode); $c->setIteratorMode($mode);
return $a; return $a;
@ -65,7 +81,7 @@ class SplCaster
public static function castFileInfo(\SplFileInfo $c, array $a, Stub $stub, $isNested) public static function castFileInfo(\SplFileInfo $c, array $a, Stub $stub, $isNested)
{ {
static $map = [ static $map = array(
'path' => 'getPath', 'path' => 'getPath',
'filename' => 'getFilename', 'filename' => 'getFilename',
'basename' => 'getBasename', 'basename' => 'getBasename',
@ -88,17 +104,9 @@ class SplCaster
'dir' => 'isDir', 'dir' => 'isDir',
'link' => 'isLink', 'link' => 'isLink',
'linkTarget' => 'getLinkTarget', 'linkTarget' => 'getLinkTarget',
]; );
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
unset($a["\0SplFileInfo\0fileName"]);
unset($a["\0SplFileInfo\0pathName"]);
if (false === $c->getPathname()) {
$a[$prefix.'⚠'] = 'The parent constructor was not called: the object is in an invalid state';
return $a;
}
foreach ($map as $key => $accessor) { foreach ($map as $key => $accessor) {
try { try {
@ -107,15 +115,11 @@ 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']);
} }
static $mapDate = ['aTime', 'mTime', 'cTime']; static $mapDate = array('aTime', 'mTime', 'cTime');
foreach ($mapDate as $key) { foreach ($mapDate as $key) {
if (isset($a[$prefix.$key])) { if (isset($a[$prefix.$key])) {
$a[$prefix.$key] = new ConstStub(date('Y-m-d H:i:s', $a[$prefix.$key]), $a[$prefix.$key]); $a[$prefix.$key] = new ConstStub(date('Y-m-d H:i:s', $a[$prefix.$key]), $a[$prefix.$key]);
@ -127,14 +131,14 @@ class SplCaster
public static function castFileObject(\SplFileObject $c, array $a, Stub $stub, $isNested) public static function castFileObject(\SplFileObject $c, array $a, Stub $stub, $isNested)
{ {
static $map = [ static $map = array(
'csvControl' => 'getCsvControl', 'csvControl' => 'getCsvControl',
'flags' => 'getFlags', 'flags' => 'getFlags',
'maxLineLen' => 'getMaxLineLen', 'maxLineLen' => 'getMaxLineLen',
'fstat' => 'fstat', 'fstat' => 'fstat',
'eof' => 'eof', 'eof' => 'eof',
'key' => 'key', 'key' => 'key',
]; );
$prefix = Caster::PREFIX_VIRTUAL; $prefix = Caster::PREFIX_VIRTUAL;
@ -146,7 +150,7 @@ class SplCaster
} }
if (isset($a[$prefix.'flags'])) { if (isset($a[$prefix.'flags'])) {
$flagsArray = []; $flagsArray = array();
foreach (self::$splFileObjectFlags as $value => $name) { foreach (self::$splFileObjectFlags as $value => $name) {
if ($a[$prefix.'flags'] & $value) { if ($a[$prefix.'flags'] & $value) {
$flagsArray[] = $name; $flagsArray[] = $name;
@ -156,29 +160,36 @@ class SplCaster
} }
if (isset($a[$prefix.'fstat'])) { if (isset($a[$prefix.'fstat'])) {
$a[$prefix.'fstat'] = new CutArrayStub($a[$prefix.'fstat'], ['dev', 'ino', 'nlink', 'rdev', 'blksize', 'blocks']); $a[$prefix.'fstat'] = new CutArrayStub($a[$prefix.'fstat'], array('dev', 'ino', 'nlink', 'rdev', 'blksize', 'blocks'));
} }
return $a; return $a;
} }
public static function castFixedArray(\SplFixedArray $c, array $a, Stub $stub, $isNested)
{
$a += array(
Caster::PREFIX_VIRTUAL.'storage' => $c->toArray(),
);
return $a;
}
public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $stub, $isNested) public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $stub, $isNested)
{ {
$storage = []; $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
unset($a["\0SplObjectStorage\0storage"]);
$clone = clone $c; foreach ($c as $obj) {
foreach ($clone as $obj) { $storage[spl_object_hash($obj)] = array(
$storage[] = [
'object' => $obj, 'object' => $obj,
'info' => $clone->getInfo(), 'info' => $c->getInfo(),
]; );
} }
$a += [ $a += array(
Caster::PREFIX_VIRTUAL.'storage' => $storage, Caster::PREFIX_VIRTUAL.'storage' => $storage,
]; );
return $a; return $a;
} }
@ -189,35 +200,4 @@ class SplCaster
return $a; return $a;
} }
public static function castWeakReference(\WeakReference $c, array $a, Stub $stub, $isNested)
{
$a[Caster::PREFIX_VIRTUAL.'object'] = $c->get();
return $a;
}
private static function castSplArray($c, array $a, Stub $stub, bool $isNested): array
{
$prefix = Caster::PREFIX_VIRTUAL;
$flags = $c->getFlags();
if (!($flags & \ArrayObject::STD_PROP_LIST)) {
$c->setFlags(\ArrayObject::STD_PROP_LIST);
$a = Caster::castObject($c, \get_class($c), method_exists($c, '__debugInfo'), $stub->class);
$c->setFlags($flags);
}
if (\PHP_VERSION_ID < 70400) {
$a[$prefix.'storage'] = $c->getArrayCopy();
}
$a += [
$prefix.'flag::STD_PROP_LIST' => (bool) ($flags & \ArrayObject::STD_PROP_LIST),
$prefix.'flag::ARRAY_AS_PROPS' => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS),
];
if ($c instanceof \ArrayObject) {
$a[$prefix.'iteratorClass'] = new ClassStub($c->getIteratorClass());
}
return $a;
}
} }

View File

@ -17,8 +17,6 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts a caster's Stub. * Casts a caster's Stub.
* *
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/ */
class StubCaster class StubCaster
{ {
@ -30,17 +28,9 @@ 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;
if (Stub::TYPE_REF === $c->type && !$c->class && \is_string($c->value) && !preg_match('//u', $c->value)) { return array();
$stub->type = Stub::TYPE_STRING;
$stub->class = Stub::STRING_BINARY;
}
$a = [];
} }
return $a;
} }
public static function castCutArray(CutArrayStub $c, array $a, Stub $stub, $isNested) public static function castCutArray(CutArrayStub $c, array $a, Stub $stub, $isNested)
@ -51,9 +41,9 @@ class StubCaster
public static function cutInternals($obj, array $a, Stub $stub, $isNested) public static function cutInternals($obj, array $a, Stub $stub, $isNested)
{ {
if ($isNested) { if ($isNested) {
$stub->cut += \count($a); $stub->cut += count($a);
return []; return array();
} }
return $a; return $a;
@ -62,17 +52,15 @@ 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 = $c->dumpKeys ? '' : null; $stub->class = '';
$stub->handle = 0; $stub->handle = 0;
$stub->value = null; $stub->value = null;
$stub->cut = $c->cut;
$stub->attr = $c->attr;
$a = []; $a = array();
if ($c->value) { if ($c->value) {
foreach (array_keys($c->value) as $k) { foreach (array_keys($c->value) as $k) {
$keys[] = !isset($k[0]) || "\0" !== $k[0] ? Caster::PREFIX_VIRTUAL.$k : $k; $keys[] = Caster::PREFIX_VIRTUAL.$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

@ -1,69 +0,0 @@
<?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\HttpFoundation\Request;
use Symfony\Component\VarDumper\Cloner\Stub;
/**
* @final since Symfony 4.4
*/
class SymfonyCaster
{
private static $requestGetters = [
'pathInfo' => 'getPathInfo',
'requestUri' => 'getRequestUri',
'baseUrl' => 'getBaseUrl',
'basePath' => 'getBasePath',
'method' => 'getMethod',
'format' => 'getRequestFormat',
];
public static function castRequest(Request $request, array $a, Stub $stub, $isNested)
{
$clone = null;
foreach (self::$requestGetters as $prop => $getter) {
$key = Caster::PREFIX_PROTECTED.$prop;
if (\array_key_exists($key, $a) && null === $a[$key]) {
if (null === $clone) {
$clone = clone $request;
}
$a[Caster::PREFIX_VIRTUAL.$prop] = $clone->{$getter}();
}
}
return $a;
}
public static function castHttpClient($client, array $a, Stub $stub, $isNested)
{
$multiKey = sprintf("\0%s\0multi", \get_class($client));
if (isset($a[$multiKey])) {
$a[$multiKey] = new CutStub($a[$multiKey]);
}
return $a;
}
public static function castHttpClientResponse($response, array $a, Stub $stub, $isNested)
{
$stub->cut += \count($a);
$a = [];
foreach ($response->getInfo() as $k => $v) {
$a[Caster::PREFIX_VIRTUAL.$k] = $v;
}
return $a;
}
}

View File

@ -25,7 +25,7 @@ class TraceStub extends Stub
public $sliceLength; public $sliceLength;
public $numberingOffset; public $numberingOffset;
public function __construct(array $trace, bool $keepArgs = true, int $sliceOffset = 0, int $sliceLength = null, int $numberingOffset = 0) public function __construct(array $trace, $keepArgs = true, $sliceOffset = 0, $sliceLength = null, $numberingOffset = 0)
{ {
$this->value = $trace; $this->value = $trace;
$this->keepArgs = $keepArgs; $this->keepArgs = $keepArgs;

View File

@ -1,30 +0,0 @@
<?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 Ramsey\Uuid\UuidInterface;
use Symfony\Component\VarDumper\Cloner\Stub;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
final class UuidCaster
{
public static function castRamseyUuid(UuidInterface $c, array $a, Stub $stub, bool $isNested): array
{
$a += [
Caster::PREFIX_VIRTUAL.'uuid' => (string) $c,
];
return $a;
}
}

View File

@ -1,79 +0,0 @@
<?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>
*
* @final since Symfony 4.4
*/
class XmlReaderCaster
{
private static $nodeTypes = [
\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 = [
'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 => [
'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, [], $count)) {
$info[$props] = new EnumStub($info[$props]);
$info[$props]->cut = $count;
}
$info = Caster::filter($info, Caster::EXCLUDE_EMPTY, [], $count);
// +2 because hasValue and hasAttributes are always filtered
$stub->cut += $count + 2;
return $a + $info;
}
}

View File

@ -17,12 +17,10 @@ use Symfony\Component\VarDumper\Cloner\Stub;
* Casts XML resources to array representation. * Casts XML resources to array representation.
* *
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*
* @final since Symfony 4.4
*/ */
class XmlResourceCaster class XmlResourceCaster
{ {
private static $xmlErrors = [ private static $xmlErrors = array(
XML_ERROR_NONE => 'XML_ERROR_NONE', XML_ERROR_NONE => 'XML_ERROR_NONE',
XML_ERROR_NO_MEMORY => 'XML_ERROR_NO_MEMORY', XML_ERROR_NO_MEMORY => 'XML_ERROR_NO_MEMORY',
XML_ERROR_SYNTAX => 'XML_ERROR_SYNTAX', XML_ERROR_SYNTAX => 'XML_ERROR_SYNTAX',
@ -45,7 +43,7 @@ class XmlResourceCaster
XML_ERROR_INCORRECT_ENCODING => 'XML_ERROR_INCORRECT_ENCODING', XML_ERROR_INCORRECT_ENCODING => 'XML_ERROR_INCORRECT_ENCODING',
XML_ERROR_UNCLOSED_CDATA_SECTION => 'XML_ERROR_UNCLOSED_CDATA_SECTION', XML_ERROR_UNCLOSED_CDATA_SECTION => 'XML_ERROR_UNCLOSED_CDATA_SECTION',
XML_ERROR_EXTERNAL_ENTITY_HANDLING => 'XML_ERROR_EXTERNAL_ENTITY_HANDLING', XML_ERROR_EXTERNAL_ENTITY_HANDLING => 'XML_ERROR_EXTERNAL_ENTITY_HANDLING',
]; );
public static function castXml($h, array $a, Stub $stub, $isNested) public static function castXml($h, array $a, Stub $stub, $isNested)
{ {

View File

@ -21,159 +21,111 @@ use Symfony\Component\VarDumper\Exception\ThrowingCasterException;
*/ */
abstract class AbstractCloner implements ClonerInterface abstract class AbstractCloner implements ClonerInterface
{ {
public static $defaultCasters = [ 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\CutArrayStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castCutArray',
'Symfony\Component\VarDumper\Caster\ConstStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castStub',
'Symfony\Component\VarDumper\Caster\EnumStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castEnum',
'Symfony\Component\VarDumper\Caster\CutStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], 'Closure' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castClosure',
'Symfony\Component\VarDumper\Caster\CutArrayStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castCutArray'], 'Generator' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castGenerator',
'Symfony\Component\VarDumper\Caster\ConstStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castStub'], 'ReflectionType' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castType',
'Symfony\Component\VarDumper\Caster\EnumStub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'castEnum'], 'ReflectionGenerator' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castReflectionGenerator',
'ReflectionClass' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castClass',
'ReflectionFunctionAbstract' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castFunctionAbstract',
'ReflectionMethod' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castMethod',
'ReflectionParameter' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castParameter',
'ReflectionProperty' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castProperty',
'ReflectionExtension' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castExtension',
'ReflectionZendExtension' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castZendExtension',
'Closure' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClosure'], 'Doctrine\Common\Persistence\ObjectManager' => 'Symfony\Component\VarDumper\Caster\StubCaster::cutInternals',
'Generator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castGenerator'], 'Doctrine\Common\Proxy\Proxy' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castCommonProxy',
'ReflectionType' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castType'], 'Doctrine\ORM\Proxy\Proxy' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castOrmProxy',
'ReflectionGenerator' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReflectionGenerator'], 'Doctrine\ORM\PersistentCollection' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castPersistentCollection',
'ReflectionClass' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castClass'],
'ReflectionFunctionAbstract' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castFunctionAbstract'],
'ReflectionMethod' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castMethod'],
'ReflectionParameter' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castParameter'],
'ReflectionProperty' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castProperty'],
'ReflectionReference' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castReference'],
'ReflectionExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castExtension'],
'ReflectionZendExtension' => ['Symfony\Component\VarDumper\Caster\ReflectionCaster', 'castZendExtension'],
'Doctrine\Common\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], 'DOMException' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castException',
'Doctrine\Common\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castCommonProxy'], 'DOMStringList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
'Doctrine\ORM\Proxy\Proxy' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castOrmProxy'], 'DOMNameList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
'Doctrine\ORM\PersistentCollection' => ['Symfony\Component\VarDumper\Caster\DoctrineCaster', 'castPersistentCollection'], 'DOMImplementation' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castImplementation',
'Doctrine\Persistence\ObjectManager' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'], 'DOMImplementationList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
'DOMNode' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNode',
'DOMNameSpaceNode' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNameSpaceNode',
'DOMDocument' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDocument',
'DOMNodeList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
'DOMNamedNodeMap' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
'DOMCharacterData' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castCharacterData',
'DOMAttr' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castAttr',
'DOMElement' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castElement',
'DOMText' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castText',
'DOMTypeinfo' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castTypeinfo',
'DOMDomError' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDomError',
'DOMLocator' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLocator',
'DOMDocumentType' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDocumentType',
'DOMNotation' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNotation',
'DOMEntity' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castEntity',
'DOMProcessingInstruction' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castProcessingInstruction',
'DOMXPath' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castXPath',
'DOMException' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castException'], 'ErrorException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castErrorException',
'DOMStringList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], 'Exception' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castException',
'DOMNameList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], 'Error' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castError',
'DOMImplementation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castImplementation'], 'Symfony\Component\DependencyInjection\ContainerInterface' => 'Symfony\Component\VarDumper\Caster\StubCaster::cutInternals',
'DOMImplementationList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'], 'Symfony\Component\VarDumper\Exception\ThrowingCasterException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castThrowingCasterException',
'DOMNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNode'], 'Symfony\Component\VarDumper\Caster\TraceStub' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castTraceStub',
'DOMNameSpaceNode' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNameSpaceNode'], 'Symfony\Component\VarDumper\Caster\FrameStub' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castFrameStub',
'DOMDocument' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocument'],
'DOMNodeList' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'],
'DOMNamedNodeMap' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLength'],
'DOMCharacterData' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castCharacterData'],
'DOMAttr' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castAttr'],
'DOMElement' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castElement'],
'DOMText' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castText'],
'DOMTypeinfo' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castTypeinfo'],
'DOMDomError' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDomError'],
'DOMLocator' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castLocator'],
'DOMDocumentType' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castDocumentType'],
'DOMNotation' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castNotation'],
'DOMEntity' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castEntity'],
'DOMProcessingInstruction' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castProcessingInstruction'],
'DOMXPath' => ['Symfony\Component\VarDumper\Caster\DOMCaster', 'castXPath'],
'XMLReader' => ['Symfony\Component\VarDumper\Caster\XmlReaderCaster', 'castXmlReader'], 'PHPUnit_Framework_MockObject_MockObject' => 'Symfony\Component\VarDumper\Caster\StubCaster::cutInternals',
'Prophecy\Prophecy\ProphecySubjectInterface' => 'Symfony\Component\VarDumper\Caster\StubCaster::cutInternals',
'Mockery\MockInterface' => 'Symfony\Component\VarDumper\Caster\StubCaster::cutInternals',
'ErrorException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castErrorException'], 'PDO' => 'Symfony\Component\VarDumper\Caster\PdoCaster::castPdo',
'Exception' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castException'], 'PDOStatement' => 'Symfony\Component\VarDumper\Caster\PdoCaster::castPdoStatement',
'Error' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castError'],
'Symfony\Component\DependencyInjection\ContainerInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
'Symfony\Component\EventDispatcher\EventDispatcherInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
'Symfony\Component\HttpClient\CurlHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'],
'Symfony\Component\HttpClient\NativeHttpClient' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClient'],
'Symfony\Component\HttpClient\Response\CurlResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'],
'Symfony\Component\HttpClient\Response\NativeResponse' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castHttpClientResponse'],
'Symfony\Component\HttpFoundation\Request' => ['Symfony\Component\VarDumper\Caster\SymfonyCaster', 'castRequest'],
'Symfony\Component\VarDumper\Exception\ThrowingCasterException' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castThrowingCasterException'],
'Symfony\Component\VarDumper\Caster\TraceStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castTraceStub'],
'Symfony\Component\VarDumper\Caster\FrameStub' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castFrameStub'],
'Symfony\Component\VarDumper\Cloner\AbstractCloner' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
'Symfony\Component\ErrorHandler\Exception\SilencedErrorContext' => ['Symfony\Component\VarDumper\Caster\ExceptionCaster', 'castSilencedErrorContext'],
'Imagine\Image\ImageInterface' => ['Symfony\Component\VarDumper\Caster\ImagineCaster', 'castImage'], 'AMQPConnection' => 'Symfony\Component\VarDumper\Caster\AmqpCaster::castConnection',
'AMQPChannel' => 'Symfony\Component\VarDumper\Caster\AmqpCaster::castChannel',
'AMQPQueue' => 'Symfony\Component\VarDumper\Caster\AmqpCaster::castQueue',
'AMQPExchange' => 'Symfony\Component\VarDumper\Caster\AmqpCaster::castExchange',
'AMQPEnvelope' => 'Symfony\Component\VarDumper\Caster\AmqpCaster::castEnvelope',
'Ramsey\Uuid\UuidInterface' => ['Symfony\Component\VarDumper\Caster\UuidCaster', 'castRamseyUuid'], 'ArrayObject' => 'Symfony\Component\VarDumper\Caster\SplCaster::castArrayObject',
'SplDoublyLinkedList' => 'Symfony\Component\VarDumper\Caster\SplCaster::castDoublyLinkedList',
'SplFileInfo' => 'Symfony\Component\VarDumper\Caster\SplCaster::castFileInfo',
'SplFileObject' => 'Symfony\Component\VarDumper\Caster\SplCaster::castFileObject',
'SplFixedArray' => 'Symfony\Component\VarDumper\Caster\SplCaster::castFixedArray',
'SplHeap' => 'Symfony\Component\VarDumper\Caster\SplCaster::castHeap',
'SplObjectStorage' => 'Symfony\Component\VarDumper\Caster\SplCaster::castObjectStorage',
'SplPriorityQueue' => 'Symfony\Component\VarDumper\Caster\SplCaster::castHeap',
'OuterIterator' => 'Symfony\Component\VarDumper\Caster\SplCaster::castOuterIterator',
'ProxyManager\Proxy\ProxyInterface' => ['Symfony\Component\VarDumper\Caster\ProxyManagerCaster', 'castProxy'], 'MongoCursorInterface' => 'Symfony\Component\VarDumper\Caster\MongoCaster::castCursor',
'PHPUnit_Framework_MockObject_MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
'PHPUnit\Framework\MockObject\MockObject' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
'PHPUnit\Framework\MockObject\Stub' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
'Prophecy\Prophecy\ProphecySubjectInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
'Mockery\MockInterface' => ['Symfony\Component\VarDumper\Caster\StubCaster', 'cutInternals'],
'PDO' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdo'], ':curl' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castCurl',
'PDOStatement' => ['Symfony\Component\VarDumper\Caster\PdoCaster', 'castPdoStatement'], ':dba' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba',
':dba persistent' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba',
'AMQPConnection' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castConnection'], ':gd' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castGd',
'AMQPChannel' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castChannel'], ':mysql link' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castMysqlLink',
'AMQPQueue' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castQueue'], ':pgsql large object' => 'Symfony\Component\VarDumper\Caster\PgSqlCaster::castLargeObject',
'AMQPExchange' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castExchange'], ':pgsql link' => 'Symfony\Component\VarDumper\Caster\PgSqlCaster::castLink',
'AMQPEnvelope' => ['Symfony\Component\VarDumper\Caster\AmqpCaster', 'castEnvelope'], ':pgsql link persistent' => 'Symfony\Component\VarDumper\Caster\PgSqlCaster::castLink',
':pgsql result' => 'Symfony\Component\VarDumper\Caster\PgSqlCaster::castResult',
'ArrayObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayObject'], ':process' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castProcess',
'ArrayIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castArrayIterator'], ':stream' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStream',
'SplDoublyLinkedList' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castDoublyLinkedList'], ':stream-context' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStreamContext',
'SplFileInfo' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileInfo'], ':xml' => 'Symfony\Component\VarDumper\Caster\XmlResourceCaster::castXml',
'SplFileObject' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castFileObject'], );
'SplHeap' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'],
'SplObjectStorage' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castObjectStorage'],
'SplPriorityQueue' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castHeap'],
'OuterIterator' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castOuterIterator'],
'WeakReference' => ['Symfony\Component\VarDumper\Caster\SplCaster', 'castWeakReference'],
'Redis' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedis'],
'RedisArray' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisArray'],
'RedisCluster' => ['Symfony\Component\VarDumper\Caster\RedisCaster', 'castRedisCluster'],
'DateTimeInterface' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castDateTime'],
'DateInterval' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castInterval'],
'DateTimeZone' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castTimeZone'],
'DatePeriod' => ['Symfony\Component\VarDumper\Caster\DateCaster', 'castPeriod'],
'GMP' => ['Symfony\Component\VarDumper\Caster\GmpCaster', 'castGmp'],
'MessageFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castMessageFormatter'],
'NumberFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castNumberFormatter'],
'IntlTimeZone' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlTimeZone'],
'IntlCalendar' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlCalendar'],
'IntlDateFormatter' => ['Symfony\Component\VarDumper\Caster\IntlCaster', 'castIntlDateFormatter'],
'Memcached' => ['Symfony\Component\VarDumper\Caster\MemcachedCaster', 'castMemcached'],
'Ds\Collection' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castCollection'],
'Ds\Map' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castMap'],
'Ds\Pair' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPair'],
'Symfony\Component\VarDumper\Caster\DsPairStub' => ['Symfony\Component\VarDumper\Caster\DsCaster', 'castPairStub'],
'CurlHandle' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'],
':curl' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castCurl'],
':dba' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'],
':dba persistent' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castDba'],
':gd' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castGd'],
':mysql link' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castMysqlLink'],
':pgsql large object' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLargeObject'],
':pgsql link' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'],
':pgsql link persistent' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castLink'],
':pgsql result' => ['Symfony\Component\VarDumper\Caster\PgSqlCaster', 'castResult'],
':process' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castProcess'],
':stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'],
':OpenSSL X.509' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castOpensslX509'],
':persistent stream' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStream'],
':stream-context' => ['Symfony\Component\VarDumper\Caster\ResourceCaster', 'castStreamContext'],
':xml' => ['Symfony\Component\VarDumper\Caster\XmlResourceCaster', 'castXml'],
];
protected $maxItems = 2500; protected $maxItems = 2500;
protected $maxString = -1; protected $maxString = -1;
protected $minDepth = 1; protected $useExt;
private $casters = []; private $casters = array();
private $prevErrorHandler; private $prevErrorHandler;
private $classInfo = []; private $classInfo = array();
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
*/ */
@ -183,6 +135,7 @@ abstract class AbstractCloner implements ClonerInterface
$casters = static::$defaultCasters; $casters = static::$defaultCasters;
} }
$this->addCasters($casters); $this->addCasters($casters);
$this->useExt = extension_loaded('symfony_debug');
} }
/** /**
@ -193,17 +146,17 @@ 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)
{ {
foreach ($casters as $type => $callback) { foreach ($casters as $type => $callback) {
$this->casters[$type][] = $callback; $this->casters[strtolower($type)][] = $callback;
} }
} }
/** /**
* Sets the maximum number of items to clone past the minimum depth in nested structures. * Sets the maximum number of items to clone past the first level in nested structures.
* *
* @param int $maxItems * @param int $maxItems
*/ */
@ -222,118 +175,88 @@ abstract class AbstractCloner implements ClonerInterface
$this->maxString = (int) $maxString; $this->maxString = (int) $maxString;
} }
/**
* Sets the minimum tree depth where we are guaranteed to clone all the items. After this
* depth is reached, only setMaxItems items will be cloned.
*
* @param int $minDepth
*/
public function setMinDepth($minDepth)
{
$this->minDepth = (int) $minDepth;
}
/** /**
* 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)
{ {
$this->prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context = []) { $this->prevErrorHandler = set_error_handler(function ($type, $msg, $file, $line, $context) {
if (E_RECOVERABLE_ERROR === $type || E_USER_ERROR === $type) { if (E_RECOVERABLE_ERROR === $type || E_USER_ERROR === $type) {
// Cloner never dies // Cloner never dies
throw new \ErrorException($msg, 0, $type, $file, $line); throw new \ErrorException($msg, 0, $type, $file, $line);
} }
if ($this->prevErrorHandler) { if ($this->prevErrorHandler) {
return ($this->prevErrorHandler)($type, $msg, $file, $line, $context); return call_user_func($this->prevErrorHandler, $type, $msg, $file, $line, $context);
} }
return false; return false;
}); });
$this->filter = $filter; $this->filter = $filter;
if ($gc = gc_enabled()) {
gc_disable();
}
try { try {
return new Data($this->doClone($var)); $data = $this->doClone($var);
} finally { } catch (\Exception $e) {
if ($gc) {
gc_enable();
}
restore_error_handler();
$this->prevErrorHandler = null;
} }
restore_error_handler();
$this->prevErrorHandler = null;
if (isset($e)) {
throw $e;
}
return new Data($data);
} }
/** /**
* 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 bool $isNested True if the object is nested in the dumped structure * @param Stub $stub The Stub for the casted object.
* @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)
{ {
$obj = $stub->value; $obj = $stub->value;
$class = $stub->class; $class = $stub->class;
if (\PHP_VERSION_ID < 80000 ? "\0" === ($class[15] ?? null) : false !== strpos($class, "@anonymous\0")) { if (isset($class[15]) && "\0" === $class[15] && 0 === strpos($class, "class@anonymous\x00")) {
$stub->class = get_debug_type($obj); $stub->class = get_parent_class($class).'@anonymous';
} }
if (isset($this->classInfo[$class])) { if (isset($this->classInfo[$class])) {
list($i, $parents, $hasDebugInfo, $fileInfo) = $this->classInfo[$class]; $classInfo = $this->classInfo[$class];
} else { } else {
$i = 2; $classInfo = array(
$parents = [$class]; new \ReflectionClass($class),
$hasDebugInfo = method_exists($class, '__debugInfo'); array_reverse(array($class => $class) + class_parents($class) + class_implements($class) + array('*' => '*')),
);
foreach (class_parents($class) as $p) { $this->classInfo[$class] = $classInfo;
$parents[] = $p;
++$i;
}
foreach (class_implements($class) as $p) {
$parents[] = $p;
++$i;
}
$parents[] = '*';
$r = new \ReflectionClass($class);
$fileInfo = $r->isInternal() || $r->isSubclassOf(Stub::class) ? [] : [
'file' => $r->getFileName(),
'line' => $r->getStartLine(),
];
$this->classInfo[$class] = [$i, $parents, $hasDebugInfo, $fileInfo];
} }
$stub->attr += $fileInfo; $a = $this->callCaster('Symfony\Component\VarDumper\Caster\Caster::castObject', $obj, $classInfo[0], null, $isNested);
$a = Caster::castObject($obj, $class, $hasDebugInfo, $stub->class);
try { foreach ($classInfo[1] as $p) {
while ($i--) { if (!empty($this->casters[$p = strtolower($p)])) {
if (!empty($this->casters[$p = $parents[$i]])) { foreach ($this->casters[$p] as $p) {
foreach ($this->casters[$p] as $callback) { $a = $this->callCaster($p, $obj, $a, $stub, $isNested);
$a = $callback($obj, $a, $stub, $isNested, $this->filter);
}
} }
} }
} catch (\Exception $e) {
$a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a;
} }
return $a; return $a;
@ -342,24 +265,47 @@ abstract class AbstractCloner implements ClonerInterface
/** /**
* Casts a resource to an array representation. * Casts a resource to an array representation.
* *
* @param bool $isNested True if the object is nested in the dumped structure * @param Stub $stub The Stub for the casted resource.
* @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)
{ {
$a = []; $a = array();
$res = $stub->value; $res = $stub->value;
$type = $stub->class; $type = $stub->class;
if (!empty($this->casters[':'.$type])) {
foreach ($this->casters[':'.$type] as $c) {
$a = $this->callCaster($c, $res, $a, $stub, $isNested);
}
}
return $a;
}
/**
* Calls a custom caster.
*
* @param callable $callback The caster.
* @param object|resource $obj The object/resource being casted.
* @param array $a The result of the previous cast for chained casters.
* @param Stub $stub The Stub for the casted object/resource.
* @param bool $isNested True if $obj is nested in the dumped structure.
*
* @return array The casted object/resource.
*/
private function callCaster($callback, $obj, $a, $stub, $isNested)
{
try { try {
if (!empty($this->casters[':'.$type])) { $cast = call_user_func($callback, $obj, $a, $stub, $isNested, $this->filter);
foreach ($this->casters[':'.$type] as $callback) {
$a = $callback($res, $a, $stub, $isNested, $this->filter); if (is_array($cast)) {
} $a = $cast;
} }
} catch (\Exception $e) { } catch (\Exception $e) {
$a = [(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠' => new ThrowingCasterException($e)] + $a; $a[(Stub::TYPE_OBJECT === $stub->type ? Caster::PREFIX_VIRTUAL : '').'⚠'] = new ThrowingCasterException($e);
} }
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,6 +38,4 @@ class Cursor
public $hashLength = 0; public $hashLength = 0;
public $hashCut = 0; public $hashCut = 0;
public $stop = false; public $stop = false;
public $attr = [];
public $skipChildren = false;
} }

View File

@ -11,24 +11,18 @@
namespace Symfony\Component\VarDumper\Cloner; namespace Symfony\Component\VarDumper\Cloner;
use Symfony\Component\VarDumper\Caster\Caster;
use Symfony\Component\VarDumper\Dumper\ContextProvider\SourceContextProvider;
/** /**
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*/ */
class Data implements \ArrayAccess, \Countable, \IteratorAggregate 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;
private $context = [];
/** /**
* @param array $data An 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)
{ {
@ -36,160 +30,19 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate
} }
/** /**
* @return string|null The type of the value * @return array The raw data structure.
*/ */
public function getType() public function getRawData()
{ {
$item = $this->data[$this->position][$this->key]; return $this->data;
if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) {
$item = $item->value;
}
if (!$item instanceof Stub) {
return \gettype($item);
}
if (Stub::TYPE_STRING === $item->type) {
return 'string';
}
if (Stub::TYPE_ARRAY === $item->type) {
return 'array';
}
if (Stub::TYPE_OBJECT === $item->type) {
return $item->class;
}
if (Stub::TYPE_RESOURCE === $item->type) {
return $item->class.' resource';
}
return null;
}
/**
* @param array|bool $recursive Whether values should be resolved recursively or not
*
* @return string|int|float|bool|array|Data[]|null A native representation of the original value
*/
public function getValue($recursive = false)
{
$item = $this->data[$this->position][$this->key];
if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) {
$item = $item->value;
}
if (!($item = $this->getStub($item)) instanceof Stub) {
return $item;
}
if (Stub::TYPE_STRING === $item->type) {
return $item->value;
}
$children = $item->position ? $this->data[$item->position] : [];
foreach ($children as $k => $v) {
if ($recursive && !($v = $this->getStub($v)) instanceof Stub) {
continue;
}
$children[$k] = clone $this;
$children[$k]->key = $k;
$children[$k]->position = $item->position;
if ($recursive) {
if (Stub::TYPE_REF === $v->type && ($v = $this->getStub($v->value)) instanceof Stub) {
$recursive = (array) $recursive;
if (isset($recursive[$v->position])) {
continue;
}
$recursive[$v->position] = true;
}
$children[$k] = $children[$k]->getValue($recursive);
}
}
return $children;
}
/**
* @return int
*/
public function count()
{
return \count($this->getValue());
}
/**
* @return \Traversable
*/
public function getIterator()
{
if (!\is_array($value = $this->getValue())) {
throw new \LogicException(sprintf('"%s" object holds non-iterable type "%s".', self::class, \gettype($value)));
}
yield from $value;
}
public function __get($key)
{
if (null !== $data = $this->seek($key)) {
$item = $this->getStub($data->data[$data->position][$data->key]);
return $item instanceof Stub || [] === $item ? $data : $item;
}
return null;
}
/**
* @return bool
*/
public function __isset($key)
{
return null !== $this->seek($key);
}
/**
* @return bool
*/
public function offsetExists($key)
{
return $this->__isset($key);
}
public function offsetGet($key)
{
return $this->__get($key);
}
public function offsetSet($key, $value)
{
throw new \BadMethodCallException(self::class.' objects are immutable.');
}
public function offsetUnset($key)
{
throw new \BadMethodCallException(self::class.' objects are immutable.');
}
/**
* @return string
*/
public function __toString()
{
$value = $this->getValue();
if (!\is_array($value)) {
return (string) $value;
}
return sprintf('%s (count=%d)', $this->getType(), \count($value));
} }
/** /**
* 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 static * @return self A clone of $this.
*/ */
public function withMaxDepth($maxDepth) public function withMaxDepth($maxDepth)
{ {
@ -200,11 +53,11 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate
} }
/** /**
* Limits the number of elements per depth level. * Limits the numbers 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 static * @return self A clone of $this.
*/ */
public function withMaxItemsPerDepth($maxItemsPerDepth) public function withMaxItemsPerDepth($maxItemsPerDepth)
{ {
@ -217,9 +70,9 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate
/** /**
* 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 static * @return self A clone of $this.
*/ */
public function withRefHandles($useRefHandles) public function withRefHandles($useRefHandles)
{ {
@ -229,91 +82,24 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate
return $data; return $data;
} }
/**
* @return static
*/
public function withContext(array $context)
{
$data = clone $this;
$data->context = $context;
return $data;
}
/**
* Seeks to a specific key in nested data structures.
*
* @param string|int $key The key to seek to
*
* @return static|null Null if the key is not set
*/
public function seek($key)
{
$item = $this->data[$this->position][$this->key];
if ($item instanceof Stub && Stub::TYPE_REF === $item->type && !$item->position) {
$item = $item->value;
}
if (!($item = $this->getStub($item)) instanceof Stub || !$item->position) {
return null;
}
$keys = [$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";
// no break
case Stub::TYPE_ARRAY:
case Stub::TYPE_RESOURCE:
break;
default:
return null;
}
$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 = [0]; $refs = array(0);
$cursor = new Cursor(); $this->dumpItem($dumper, new Cursor(), $refs, $this->data[0][0]);
if ($cursor->attr = $this->context[SourceContextProvider::class] ?? []) {
$cursor->attr['if_links'] = true;
$cursor->hashType = -1;
$dumper->dumpScalar($cursor, 'default', '^');
$cursor->attr = ['if_links' => true];
$dumper->dumpScalar($cursor, 'default', ' ');
$cursor->hashType = 0;
}
$this->dumpItem($dumper, $cursor, $refs, $this->data[$this->position][$this->key]);
} }
/** /**
* Depth-first dumping of items. * Depth-first dumping of items.
* *
* @param mixed $item A Stub object or the original value being dumped * @param DumperInterface $dumper The dumper being used for dumping.
* @param Cursor $cursor A cursor used for tracking dumper state position.
* @param array &$refs A map of all references discovered while dumping.
* @param mixed $item A Stub object or the original value being dumped.
*/ */
private function dumpItem(DumperInterface $dumper, Cursor $cursor, array &$refs, $item) private function dumpItem($dumper, $cursor, &$refs, $item)
{ {
$cursor->refIndex = 0; $cursor->refIndex = 0;
$cursor->softRefTo = $cursor->softRefHandle = $cursor->softRefCount = 0; $cursor->softRefTo = $cursor->softRefHandle = $cursor->softRefCount = 0;
@ -321,11 +107,7 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate
$firstSeen = true; $firstSeen = true;
if (!$item instanceof Stub) { if (!$item instanceof Stub) {
$cursor->attr = []; $type = gettype($item);
$type = \gettype($item);
if ($item && 'array' === $type) {
$item = $this->getStub($item);
}
} elseif (Stub::TYPE_REF === $item->type) { } elseif (Stub::TYPE_REF === $item->type) {
if ($item->handle) { if ($item->handle) {
if (!isset($refs[$r = $item->handle - (PHP_INT_MAX >> 1)])) { if (!isset($refs[$r = $item->handle - (PHP_INT_MAX >> 1)])) {
@ -337,9 +119,8 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate
$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 = $this->getStub($item->value);
} }
if ($item instanceof Stub) { if ($item instanceof Stub) {
if ($item->refCount) { if ($item->refCount) {
@ -352,7 +133,6 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate
} }
$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) {
@ -360,12 +140,12 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate
if ($cursor->stop) { if ($cursor->stop) {
if ($cut >= 0) { if ($cut >= 0) {
$cut += \count($children); $cut += count($children);
} }
$children = []; $children = array();
} }
} else { } else {
$children = []; $children = array();
} }
switch ($item->type) { switch ($item->type) {
case Stub::TYPE_STRING: case Stub::TYPE_STRING:
@ -376,27 +156,21 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate
$item = clone $item; $item = clone $item;
$item->type = $item->class; $item->type = $item->class;
$item->class = $item->value; $item->class = $item->value;
// no break // No break;
case Stub::TYPE_OBJECT: case Stub::TYPE_OBJECT:
case Stub::TYPE_RESOURCE: case Stub::TYPE_RESOURCE:
$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) {
if ($cursor->skipChildren) { $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->type);
$withChildren = false;
$cut = -1;
} else {
$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);
} }
$cursor->skipChildren = false;
$dumper->leaveHash($cursor, $item->type, $item->class, $withChildren, $cut); $dumper->leaveHash($cursor, $item->type, $item->class, $withChildren, $cut);
break; break;
default: default:
throw new \RuntimeException(sprintf('Unexpected Stub type: "%s".', $item->type)); throw new \RuntimeException(sprintf('Unexpected Stub type: %s', $item->type));
} }
} elseif ('array' === $type) { } elseif ('array' === $type) {
$dumper->enterHash($cursor, Cursor::HASH_INDEXED, 0, false); $dumper->enterHash($cursor, Cursor::HASH_INDEXED, 0, false);
@ -411,19 +185,26 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate
/** /**
* Dumps children of hash structures. * Dumps children of hash structures.
* *
* @return int The final number of removed items * @param DumperInterface $dumper
* @param Cursor $parentCursor The cursor of the parent hash.
* @param array &$refs A map of all references discovered while dumping.
* @param array $children The children to dump.
* @param int $hashCut The number of items removed from the original hash.
* @param string $hashType A Cursor::HASH_* const.
*
* @return int The final number of removed items.
*/ */
private function dumpChildren(DumperInterface $dumper, Cursor $parentCursor, array &$refs, array $children, int $hashCut, int $hashType, bool $dumpKeys): int private function dumpChildren($dumper, $parentCursor, &$refs, $children, $hashCut, $hashType)
{ {
$cursor = clone $parentCursor; $cursor = clone $parentCursor;
++$cursor->depth; ++$cursor->depth;
$cursor->hashType = $hashType; $cursor->hashType = $hashType;
$cursor->hashIndex = 0; $cursor->hashIndex = 0;
$cursor->hashLength = \count($children); $cursor->hashLength = count($children);
$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 = $dumpKeys ? $key : null; $cursor->hashKey = $key;
$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;
@ -434,22 +215,4 @@ class Data implements \ArrayAccess, \Countable, \IteratorAggregate
return $hashCut; return $hashCut;
} }
private function getStub($item)
{
if (!$item || !\is_array($item)) {
return $item;
}
$stub = new Stub();
$stub->type = Stub::TYPE_ARRAY;
foreach ($item as $stub->class => $stub->position) {
}
if (isset($item[0])) {
$stub->cut = $item[0];
}
$stub->value = $stub->cut + ($stub->position ? \count($this->data[$stub->position]) : 0);
return $stub;
}
} }

View File

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

View File

@ -18,17 +18,17 @@ namespace Symfony\Component\VarDumper\Cloner;
*/ */
class Stub class Stub
{ {
const TYPE_REF = 1; const TYPE_REF = 'ref';
const TYPE_STRING = 2; const TYPE_STRING = 'string';
const TYPE_ARRAY = 3; const TYPE_ARRAY = 'array';
const TYPE_OBJECT = 4; const TYPE_OBJECT = 'object';
const TYPE_RESOURCE = 5; const TYPE_RESOURCE = 'resource';
const STRING_BINARY = 1; const STRING_BINARY = 'bin';
const STRING_UTF8 = 2; const STRING_UTF8 = 'utf8';
const ARRAY_ASSOC = 1; const ARRAY_ASSOC = 'assoc';
const ARRAY_INDEXED = 2; const ARRAY_INDEXED = 'indexed';
public $type = self::TYPE_REF; public $type = self::TYPE_REF;
public $class = ''; public $class = '';
@ -37,31 +37,4 @@ class Stub
public $handle = 0; public $handle = 0;
public $refCount = 0; public $refCount = 0;
public $position = 0; public $position = 0;
public $attr = [];
private static $defaultProperties = [];
/**
* @internal
*/
public function __sleep(): array
{
$properties = [];
if (!isset(self::$defaultProperties[$c = static::class])) {
self::$defaultProperties[$c] = get_class_vars($c);
foreach ((new \ReflectionClass($c))->getStaticProperties() as $k => $v) {
unset(self::$defaultProperties[$c][$k]);
}
}
foreach (self::$defaultProperties[$c] as $k => $v) {
if ($this->$k !== $v) {
$properties[] = $k;
}
}
return $properties;
}
} }

View File

@ -16,173 +16,135 @@ namespace Symfony\Component\VarDumper\Cloner;
*/ */
class VarCloner extends AbstractCloner class VarCloner extends AbstractCloner
{ {
private static $gid; private static $hashMask = 0;
private static $arrayCache = []; private static $hashOffset = 0;
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected function doClone($var) protected function doClone($var)
{ {
$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 minimum depth $pos = 0; // Number of cloned items past the first level
$refsCounter = 0; // Hard references counter $refs = 0; // Hard references counter
$queue = [[$var]]; // This breadth-first queue is the return value $queue = array(array($var)); // This breadth-first queue is the return value
$indexedArrays = []; // Map of queue indexes that hold numerically indexed arrays $arrayRefs = array(); // Map of queue indexes to stub array objects
$hardRefs = []; // Map of original zval ids to stub objects $hardRefs = array(); // Map of original zval hashes to stub objects
$objRefs = []; // Map of original object handles to their stub object counterpart $objRefs = array(); // Map of original object handles to their stub object couterpart
$objects = []; // Keep a ref to objects to ensure their handle cannot be reused while cloning $resRefs = array(); // Map of original resource handles to their stub object couterpart
$resRefs = []; // Map of original resource handles to their stub object counterpart $values = array(); // Map of stub objects' hashes to original values
$values = []; // Map of stub objects' ids to original values
$maxItems = $this->maxItems; $maxItems = $this->maxItems;
$maxString = $this->maxString; $maxString = $this->maxString;
$minDepth = $this->minDepth; $cookie = (object) array(); // Unique object used to detect hard references
$currentDepth = 0; // Current tree depth $gid = uniqid(mt_rand(), true); // Unique string used to detect the special $GLOBALS variable
$currentDepthFinalIndex = 0; // Final $queue index for current tree depth
$minimumDepthReached = 0 === $minDepth; // Becomes true when minimum tree depth has been reached
$cookie = (object) []; // Unique object used to detect hard references
$a = null; // Array cast for nested structures $a = null; // Array cast for nested structures
$stub = null; // Stub capturing the main properties of an original item value $stub = null; // Stub capturing the main properties of an original item value
// or null if the original value is used directly // or null if the original value is used directly
$zval = array( // Main properties of the current value
if (!$gid = self::$gid) { 'type' => null,
$gid = self::$gid = md5(random_bytes(6)); // Unique string used to detect the special $GLOBALS variable 'zval_isref' => null,
'zval_hash' => null,
'array_count' => null,
'object_class' => null,
'object_handle' => null,
'resource_type' => null,
);
if (!self::$hashMask) {
self::initHashMask();
} }
$arrayStub = new Stub(); $hashMask = self::$hashMask;
$arrayStub->type = Stub::TYPE_ARRAY; $hashOffset = self::$hashOffset;
$fromObjCast = false;
for ($i = 0; $i < $len; ++$i) { for ($i = 0; $i < $len; ++$i) {
// Detect when we move on to the next tree depth $indexed = true; // Whether the currently iterated array is numerically indexed or not
if ($i > $currentDepthFinalIndex) { $j = -1; // Position in the currently iterated array
++$currentDepth; $step = $queue[$i]; // Copy of the currently iterated array used for hard references detection
$currentDepthFinalIndex = $len - 1; foreach ($step as $k => $v) {
if ($currentDepth >= $minDepth) { // $k is the original key
$minimumDepthReached = true;
}
}
$refs = $vals = $queue[$i];
if (\PHP_VERSION_ID < 70200 && empty($indexedArrays[$i])) {
// see https://wiki.php.net/rfc/convert_numeric_keys_in_object_array_casts
foreach ($vals as $k => $v) {
if (\is_int($k)) {
continue;
}
foreach ([$k => true] as $gk => $gv) {
}
if ($gk !== $k) {
$fromObjCast = true;
$refs = $vals = array_values($queue[$i]);
break;
}
}
}
foreach ($vals as $k => $v) {
// $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 (\PHP_VERSION_ID >= 70400) { $indexed = false;
$zvalIsRef = null !== \ReflectionReference::fromArrayElement($vals, $k);
} else {
$refs[$k] = $cookie;
$zvalIsRef = $vals[$k] === $cookie;
} }
if ($useExt) {
if ($zvalIsRef) { $zval = symfony_zval_info($k, $step);
$vals[$k] = &$stub; // Break hard references to make $queue completely } else {
$step[$k] = $cookie;
if ($zval['zval_isref'] = $queue[$i][$k] === $cookie) {
$zval['zval_hash'] = $v instanceof Stub ? spl_object_hash($v) : null;
}
$zval['type'] = gettype($v);
}
if ($zval['zval_isref']) {
$queue[$i][$k] = &$stub; // Break hard references to make $queue completely
unset($stub); // independent from the original structure unset($stub); // independent from the original structure
if ($v instanceof Stub && isset($hardRefs[spl_object_id($v)])) { if (isset($hardRefs[$zval['zval_hash']])) {
$vals[$k] = $refs[$k] = $v; $queue[$i][$k] = $useExt ? ($v = $hardRefs[$zval['zval_hash']]) : ($step[$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;
} }
++$v->refCount; ++$v->refCount;
continue; continue;
} }
$refs[$k] = $vals[$k] = new Stub();
$refs[$k]->value = $v;
$h = spl_object_id($refs[$k]);
$hardRefs[$h] = &$refs[$k];
$values[$h] = $v;
$vals[$k]->handle = ++$refsCounter;
} }
// Create $stub when the original value $v can not be used directly // Create $stub when the original value $v can not be used directly
// If $v is a nested structure, put that structure in array $a // If $v is a nested structure, put that structure in array $a
switch (true) { switch ($zval['type']) {
case null === $v: case 'string':
case \is_bool($v): if (isset($v[0]) && !preg_match('//u', $v)) {
case \is_int($v):
case \is_float($v):
continue 2;
case \is_string($v):
if ('' === $v) {
continue 2;
}
if (!preg_match('//u', $v)) {
$stub = new Stub(); $stub = new Stub();
$stub->type = Stub::TYPE_STRING; $stub->type = Stub::TYPE_STRING;
$stub->class = Stub::STRING_BINARY; $stub->class = Stub::STRING_BINARY;
if (0 <= $maxString && 0 < $cut = \strlen($v) - $maxString) { if (0 <= $maxString && 0 < $cut = strlen($v) - $maxString) {
$stub->cut = $cut; $stub->cut = $cut;
$stub->value = substr($v, 0, -$cut); $stub->value = substr($v, 0, -$cut);
} else { } else {
$stub->value = $v; $stub->value = $v;
} }
} elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = mb_strlen($v, 'UTF-8') - $maxString) { } elseif (0 <= $maxString && isset($v[1 + ($maxString >> 2)]) && 0 < $cut = iconv_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 = mb_substr($v, 0, $maxString, 'UTF-8'); $stub->value = iconv_substr($v, 0, $maxString, 'UTF-8');
} else {
continue 2;
} }
$a = null;
break; break;
case \is_array($v): case 'integer':
if (!$v) { break;
continue 2;
}
$stub = $arrayStub;
$stub->class = Stub::ARRAY_INDEXED;
$j = -1; case 'array':
foreach ($v as $gk => $gv) { if ($v) {
if ($gk !== ++$j) { $stub = $arrayRefs[$len] = new Stub();
$stub->class = Stub::ARRAY_ASSOC; $stub->type = Stub::TYPE_ARRAY;
break; $stub->class = Stub::ARRAY_ASSOC;
}
}
$a = $v;
if (Stub::ARRAY_ASSOC === $stub->class) {
// Copies of $GLOBALS have very strange behavior, // Copies of $GLOBALS have very strange behavior,
// let's detect them with some black magic // let's detect them with some black magic
$a = $v;
$a[$gid] = true; $a[$gid] = true;
// Happens with copies of $GLOBALS // Happens with copies of $GLOBALS
if (isset($v[$gid])) { if (isset($v[$gid])) {
unset($v[$gid]); unset($v[$gid]);
$a = []; $a = array();
foreach ($v as $gk => &$gv) { foreach ($v as $gk => &$gv) {
$a[$gk] = &$gv; $a[$gk] = &$gv;
} }
unset($gv);
} else { } else {
$a = $v; $a = $v;
} }
} elseif (\PHP_VERSION_ID < 70200) {
$indexedArrays[$len] = true; $stub->value = $zval['array_count'] ?: count($a);
} }
break; break;
case \is_object($v): case 'object':
case $v instanceof \__PHP_Incomplete_Class: if (empty($objRefs[$h = $zval['object_handle'] ?: ($hashMask ^ hexdec(substr(spl_object_hash($v), $hashOffset, PHP_INT_SIZE)))])) {
if (empty($objRefs[$h = spl_object_id($v)])) {
$stub = new Stub(); $stub = new Stub();
$stub->type = Stub::TYPE_OBJECT; $stub->type = Stub::TYPE_OBJECT;
$stub->class = \get_class($v); $stub->class = $zval['object_class'] ?: get_class($v);
$stub->value = $v; $stub->value = $v;
$stub->handle = $h; $stub->handle = $h;
$a = $this->castObject($stub, 0 < $i); $a = $this->castObject($stub, 0 < $i);
@ -190,17 +152,23 @@ class VarCloner extends AbstractCloner
if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) { if (Stub::TYPE_OBJECT !== $stub->type || null === $stub->value) {
break; break;
} }
$stub->handle = $h = spl_object_id($stub->value); if ($useExt) {
$zval['type'] = $stub->value;
$zval = symfony_zval_info('type', $zval);
$h = $zval['object_handle'];
} else {
$h = $hashMask ^ hexdec(substr(spl_object_hash($stub->value), $hashOffset, PHP_INT_SIZE));
}
$stub->handle = $h;
} }
$stub->value = null; $stub->value = null;
if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { if (0 <= $maxItems && $maxItems <= $pos) {
$stub->cut = \count($a); $stub->cut = count($a);
$a = null; $a = null;
} }
} }
if (empty($objRefs[$h])) { if (empty($objRefs[$h])) {
$objRefs[$h] = $stub; $objRefs[$h] = $stub;
$objects[] = $v;
} else { } else {
$stub = $objRefs[$h]; $stub = $objRefs[$h];
++$stub->refCount; ++$stub->refCount;
@ -208,19 +176,18 @@ class VarCloner extends AbstractCloner
} }
break; break;
default: // resource case 'resource':
case 'unknown type':
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;
if ('Unknown' === $stub->class = @get_resource_type($v)) { $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);
$stub->value = null; $stub->value = null;
if (0 <= $maxItems && $maxItems <= $pos && $minimumDepthReached) { if (0 <= $maxItems && $maxItems <= $pos) {
$stub->cut = \count($a); $stub->cut = count($a);
$a = null; $a = null;
} }
} }
@ -234,62 +201,67 @@ class VarCloner extends AbstractCloner
break; break;
} }
if ($a) { if (isset($stub)) {
if (!$minimumDepthReached || 0 > $maxItems) { if ($zval['zval_isref']) {
$queue[$len] = $a; if ($useExt) {
$stub->position = $len++; $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = $v = new Stub();
} elseif ($pos < $maxItems) { $v->value = $stub;
if ($maxItems < $pos += \count($a)) { } else {
$a = \array_slice($a, 0, $maxItems - $pos); $step[$k] = new Stub();
if ($stub->cut >= 0) { $step[$k]->value = $stub;
$stub->cut += $pos - $maxItems; $h = spl_object_hash($step[$k]);
$queue[$i][$k] = $hardRefs[$h] = &$step[$k];
$values[$h] = $v;
}
$queue[$i][$k]->handle = ++$refs;
} else {
$queue[$i][$k] = $stub;
}
if ($a) {
if ($i && 0 <= $maxItems) {
$k = count($a);
if ($pos < $maxItems) {
if ($maxItems < $pos += $k) {
$a = array_slice($a, 0, $maxItems - $pos);
if ($stub->cut >= 0) {
$stub->cut += $pos - $maxItems;
}
}
} else {
if ($stub->cut >= 0) {
$stub->cut += $k;
}
$stub = $a = null;
unset($arrayRefs[$len]);
continue;
} }
} }
$queue[$len] = $a; $queue[$len] = $a;
$stub->position = $len++; $stub->position = $len++;
} elseif ($stub->cut >= 0) {
$stub->cut += \count($a);
$stub->position = 0;
} }
} $stub = $a = null;
} elseif ($zval['zval_isref']) {
if ($arrayStub === $stub) { if ($useExt) {
if ($arrayStub->cut) { $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = new Stub();
$stub = [$arrayStub->cut, $arrayStub->class => $arrayStub->position]; $queue[$i][$k]->value = $v;
$arrayStub->cut = 0;
} elseif (isset(self::$arrayCache[$arrayStub->class][$arrayStub->position])) {
$stub = self::$arrayCache[$arrayStub->class][$arrayStub->position];
} else { } else {
self::$arrayCache[$arrayStub->class][$arrayStub->position] = $stub = [$arrayStub->class => $arrayStub->position]; $step[$k] = $queue[$i][$k] = new Stub();
$step[$k]->value = $v;
$h = spl_object_hash($step[$k]);
$hardRefs[$h] = &$step[$k];
$values[$h] = $v;
} }
} $queue[$i][$k]->handle = ++$refs;
if ($zvalIsRef) {
$refs[$k]->value = $stub;
} else {
$vals[$k] = $stub;
} }
} }
if ($fromObjCast) { if (isset($arrayRefs[$i])) {
$fromObjCast = false; if ($indexed) {
$refs = $vals; $arrayRefs[$i]->class = Stub::ARRAY_INDEXED;
$vals = [];
$j = -1;
foreach ($queue[$i] as $k => $v) {
foreach ([$k => true] as $gk => $gv) {
}
if ($gk !== $k) {
$vals = (object) $vals;
$vals->{$k} = $refs[++$j];
$vals = (array) $vals;
} else {
$vals[$k] = $refs[++$j];
}
} }
unset($arrayRefs[$i]);
} }
$queue[$i] = $vals;
} }
foreach ($values as $h => $v) { foreach ($values as $h => $v) {
@ -298,4 +270,31 @@ class VarCloner extends AbstractCloner
return $queue; return $queue;
} }
private static function initHashMask()
{
$obj = (object) array();
self::$hashOffset = 16 - PHP_INT_SIZE;
self::$hashMask = -1;
if (defined('HHVM_VERSION')) {
self::$hashOffset += 16;
} else {
// check if we are nested in an output buffering handler to prevent a fatal error with ob_start() below
$obFuncs = array('ob_clean', 'ob_end_clean', 'ob_flush', 'ob_end_flush', 'ob_get_contents', 'ob_get_flush');
foreach (debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS) as $frame) {
if (isset($frame['function'][0]) && !isset($frame['class']) && 'o' === $frame['function'][0] && in_array($frame['function'], $obFuncs)) {
$frame['line'] = 0;
break;
}
}
if (!empty($frame['line'])) {
ob_start();
debug_zval_dump($obj);
self::$hashMask = substr(ob_get_clean(), 17);
}
}
self::$hashMask ^= hexdec(substr(spl_object_hash($obj), self::$hashOffset, PHP_INT_SIZE));
}
} }

View File

@ -1,88 +0,0 @@
<?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\Command\Descriptor;
use Symfony\Component\Console\Formatter\OutputFormatterStyle;
use Symfony\Component\Console\Input\ArrayInput;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Dumper\CliDumper;
/**
* Describe collected data clones for cli output.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*
* @final
*/
class CliDescriptor implements DumpDescriptorInterface
{
private $dumper;
private $lastIdentifier;
private $supportsHref;
public function __construct(CliDumper $dumper)
{
$this->dumper = $dumper;
$this->supportsHref = method_exists(OutputFormatterStyle::class, 'setHref');
}
public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void
{
$io = $output instanceof SymfonyStyle ? $output : new SymfonyStyle(new ArrayInput([]), $output);
$this->dumper->setColors($output->isDecorated());
$rows = [['date', date('r', $context['timestamp'])]];
$lastIdentifier = $this->lastIdentifier;
$this->lastIdentifier = $clientId;
$section = "Received from client #$clientId";
if (isset($context['request'])) {
$request = $context['request'];
$this->lastIdentifier = $request['identifier'];
$section = sprintf('%s %s', $request['method'], $request['uri']);
if ($controller = $request['controller']) {
$rows[] = ['controller', rtrim($this->dumper->dump($controller, true), "\n")];
}
} elseif (isset($context['cli'])) {
$this->lastIdentifier = $context['cli']['identifier'];
$section = '$ '.$context['cli']['command_line'];
}
if ($this->lastIdentifier !== $lastIdentifier) {
$io->section($section);
}
if (isset($context['source'])) {
$source = $context['source'];
$sourceInfo = sprintf('%s on line %d', $source['name'], $source['line']);
$fileLink = $source['file_link'] ?? null;
if ($this->supportsHref && $fileLink) {
$sourceInfo = sprintf('<href=%s>%s</>', $fileLink, $sourceInfo);
}
$rows[] = ['source', $sourceInfo];
$file = $source['file_relative'] ?? $source['file'];
$rows[] = ['file', $file];
}
$io->table([], $rows);
if (!$this->supportsHref && isset($fileLink)) {
$io->writeln(['<info>Open source in your IDE/browser:</info>', $fileLink]);
$io->newLine();
}
$this->dumper->dump($data);
$io->newLine();
}
}

View File

@ -1,23 +0,0 @@
<?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\Command\Descriptor;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\VarDumper\Cloner\Data;
/**
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
interface DumpDescriptorInterface
{
public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void;
}

View File

@ -1,119 +0,0 @@
<?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\Command\Descriptor;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
/**
* Describe collected data clones for html output.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*
* @final
*/
class HtmlDescriptor implements DumpDescriptorInterface
{
private $dumper;
private $initialized = false;
public function __construct(HtmlDumper $dumper)
{
$this->dumper = $dumper;
}
public function describe(OutputInterface $output, Data $data, array $context, int $clientId): void
{
if (!$this->initialized) {
$styles = file_get_contents(__DIR__.'/../../Resources/css/htmlDescriptor.css');
$scripts = file_get_contents(__DIR__.'/../../Resources/js/htmlDescriptor.js');
$output->writeln("<style>$styles</style><script>$scripts</script>");
$this->initialized = true;
}
$title = '-';
if (isset($context['request'])) {
$request = $context['request'];
$controller = "<span class='dumped-tag'>{$this->dumper->dump($request['controller'], true, ['maxDepth' => 0])}</span>";
$title = sprintf('<code>%s</code> <a href="%s">%s</a>', $request['method'], $uri = $request['uri'], $uri);
$dedupIdentifier = $request['identifier'];
} elseif (isset($context['cli'])) {
$title = '<code>$ </code>'.$context['cli']['command_line'];
$dedupIdentifier = $context['cli']['identifier'];
} else {
$dedupIdentifier = uniqid('', true);
}
$sourceDescription = '';
if (isset($context['source'])) {
$source = $context['source'];
$projectDir = $source['project_dir'] ?? null;
$sourceDescription = sprintf('%s on line %d', $source['name'], $source['line']);
if (isset($source['file_link'])) {
$sourceDescription = sprintf('<a href="%s">%s</a>', $source['file_link'], $sourceDescription);
}
}
$isoDate = $this->extractDate($context, 'c');
$tags = array_filter([
'controller' => $controller ?? null,
'project dir' => $projectDir ?? null,
]);
$output->writeln(<<<HTML
<article data-dedup-id="$dedupIdentifier">
<header>
<div class="row">
<h2 class="col">$title</h2>
<time class="col text-small" title="$isoDate" datetime="$isoDate">
{$this->extractDate($context)}
</time>
</div>
{$this->renderTags($tags)}
</header>
<section class="body">
<p class="text-small">
$sourceDescription
</p>
{$this->dumper->dump($data, true)}
</section>
</article>
HTML
);
}
private function extractDate(array $context, string $format = 'r'): string
{
return date($format, $context['timestamp']);
}
private function renderTags(array $tags): string
{
if (!$tags) {
return '';
}
$renderedTags = '';
foreach ($tags as $key => $value) {
$renderedTags .= sprintf('<li><span class="badge">%s</span>%s</li>', $key, $value);
}
return <<<HTML
<div class="row">
<ul class="tags">
$renderedTags
</ul>
</div>
HTML;
}
}

View File

@ -1,99 +0,0 @@
<?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\Command;
use Symfony\Component\Console\Command\Command;
use Symfony\Component\Console\Exception\InvalidArgumentException;
use Symfony\Component\Console\Input\InputInterface;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Output\OutputInterface;
use Symfony\Component\Console\Style\SymfonyStyle;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Command\Descriptor\CliDescriptor;
use Symfony\Component\VarDumper\Command\Descriptor\DumpDescriptorInterface;
use Symfony\Component\VarDumper\Command\Descriptor\HtmlDescriptor;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
use Symfony\Component\VarDumper\Server\DumpServer;
/**
* Starts a dump server to collect and output dumps on a single place with multiple formats support.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*
* @final
*/
class ServerDumpCommand extends Command
{
protected static $defaultName = 'server:dump';
private $server;
/** @var DumpDescriptorInterface[] */
private $descriptors;
public function __construct(DumpServer $server, array $descriptors = [])
{
$this->server = $server;
$this->descriptors = $descriptors + [
'cli' => new CliDescriptor(new CliDumper()),
'html' => new HtmlDescriptor(new HtmlDumper()),
];
parent::__construct();
}
protected function configure()
{
$availableFormats = implode(', ', array_keys($this->descriptors));
$this
->addOption('format', null, InputOption::VALUE_REQUIRED, sprintf('The output format (%s)', $availableFormats), 'cli')
->setDescription('Starts a dump server that collects and displays dumps in a single place')
->setHelp(<<<'EOF'
<info>%command.name%</info> starts a dump server that collects and displays
dumps in a single place for debugging you application:
<info>php %command.full_name%</info>
You can consult dumped data in HTML format in your browser by providing the <comment>--format=html</comment> option
and redirecting the output to a file:
<info>php %command.full_name% --format="html" > dump.html</info>
EOF
)
;
}
protected function execute(InputInterface $input, OutputInterface $output): int
{
$io = new SymfonyStyle($input, $output);
$format = $input->getOption('format');
if (!$descriptor = $this->descriptors[$format] ?? null) {
throw new InvalidArgumentException(sprintf('Unsupported format "%s".', $format));
}
$errorIo = $io->getErrorStyle();
$errorIo->title('Symfony Var Dumper Server');
$this->server->start();
$errorIo->success(sprintf('Server listening on %s', $this->server->getHost()));
$errorIo->comment('Quit the server with CONTROL-C.');
$this->server->listen(function (Data $data, array $context, int $clientId) use ($descriptor, $io) {
$descriptor->describe($io, $data, $context, $clientId);
});
}
}

View File

@ -21,11 +21,6 @@ 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;
const DUMP_COMMA_SEPARATOR = 4;
const DUMP_TRAILING_COMMA = 8;
public static $defaultOutput = 'php://output'; public static $defaultOutput = 'php://output';
protected $line = ''; protected $line = '';
@ -33,23 +28,20 @@ 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|null $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, string $charset = null, int $flags = 0) public function __construct($output = null, $charset = null)
{ {
$this->flags = $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 = localeconv(); $this->decimalPoint = (string) 0.5;
$this->decimalPoint = $this->decimalPoint['decimal_point']; $this->decimalPoint = $this->decimalPoint[1];
$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;
} }
} }
@ -57,23 +49,23 @@ 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)
{ {
$prev = null !== $this->outputStream ? $this->outputStream : $this->lineDumper; $prev = null !== $this->outputStream ? $this->outputStream : $this->lineDumper;
if (\is_callable($output)) { if (is_callable($output)) {
$this->outputStream = null; $this->outputStream = null;
$this->lineDumper = $output; $this->lineDumper = $output;
} else { } else {
if (\is_string($output)) { if (is_string($output)) {
$output = fopen($output, 'wb'); $output = fopen($output, 'wb');
} }
$this->outputStream = $output; $this->outputStream = $output;
$this->lineDumper = [$this, 'echoLine']; $this->lineDumper = array($this, 'echoLine');
} }
return $prev; return $prev;
@ -82,9 +74,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)
{ {
@ -101,9 +93,9 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
/** /**
* Sets the indentation pad string. * Sets the indentation pad string.
* *
* @param string $pad A string that 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 previous indent pad * @return string The indent pad.
*/ */
public function setIndentPad($pad) public function setIndentPad($pad)
{ {
@ -116,65 +108,45 @@ abstract class AbstractDumper implements DataDumperInterface, DumperInterface
/** /**
* Dumps a Data object. * Dumps a Data object.
* *
* @param callable|resource|string|true|null $output A line dumper callable, an opened stream, an output path or true to return the dump * @param Data $data A Data object.
* * @param callable|resource|string|null $output A line dumper callable, an opened stream or an output path.
* @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)
{ {
$this->decimalPoint = localeconv(); $exception = null;
$this->decimalPoint = $this->decimalPoint['decimal_point'];
if ($locale = $this->flags & (self::DUMP_COMMA_SEPARATOR | self::DUMP_TRAILING_COMMA) ? setlocale(LC_NUMERIC, 0) : null) {
setlocale(LC_NUMERIC, 'C');
}
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) {
if ($returnDump) { // Re-thrown below
$result = stream_get_contents($output, -1, 0); }
fclose($output); if ($output) {
$this->setOutput($prevOutput);
return $result; }
} if (null !== $exception) {
} finally { throw $exception;
if ($output) {
$this->setOutput($prevOutput);
}
if ($locale) {
setlocale(LC_NUMERIC, $locale);
}
} }
return null;
} }
/** /**
* 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.
* or -1 to signal the end-of-dump to the line dumper callable
*/ */
protected function dumpLine($depth) protected function dumpLine($depth)
{ {
($this->lineDumper)($this->line, $depth, $this->indentPad); call_user_func($this->lineDumper, $this->line, $depth, $this->indentPad);
$this->line = ''; $this->line = '';
} }
/** /**
* 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)
{ {
@ -186,20 +158,12 @@ 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|null $s The non-UTF-8 string to convert * @param string $s The non-UTF-8 string to convert.
* *
* @return string|null The string converted to UTF-8 * @return string The string converted to UTF-8.
*/ */
protected function utf8Encode($s) protected function utf8Encode($s)
{ {
if (null === $s || 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

@ -12,7 +12,6 @@
namespace Symfony\Component\VarDumper\Dumper; namespace Symfony\Component\VarDumper\Dumper;
use Symfony\Component\VarDumper\Cloner\Cursor; use Symfony\Component\VarDumper\Cloner\Cursor;
use Symfony\Component\VarDumper\Cloner\Stub;
/** /**
* CliDumper dumps variables for command line output. * CliDumper dumps variables for command line output.
@ -26,9 +25,9 @@ class CliDumper extends AbstractDumper
protected $colors; protected $colors;
protected $maxStringWidth = 0; protected $maxStringWidth = 0;
protected $styles = [ protected $styles = array(
// See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics
'default' => '0;38;5;208', 'default' => '38;5;208',
'num' => '1;38;5;38', 'num' => '1;38;5;38',
'const' => '1;38;5;208', 'const' => '1;38;5;208',
'str' => '1;38;5;113', 'str' => '1;38;5;113',
@ -40,37 +39,28 @@ class CliDumper extends AbstractDumper
'meta' => '38;5;170', 'meta' => '38;5;170',
'key' => '38;5;113', 'key' => '38;5;113',
'index' => '38;5;38', 'index' => '38;5;38',
]; );
protected static $controlCharsRx = '/[\x00-\x1F\x7F]+/'; protected static $controlCharsRx = '/[\x00-\x1F\x7F]+/';
protected static $controlCharsMap = [ protected static $controlCharsMap = array(
"\t" => '\t', "\t" => '\t',
"\n" => '\n', "\n" => '\n',
"\v" => '\v', "\v" => '\v',
"\f" => '\f', "\f" => '\f',
"\r" => '\r', "\r" => '\r',
"\033" => '\e', "\033" => '\e',
]; );
protected $collapseNextHash = false;
protected $expandNextHash = false;
private $displayOptions = [
'fileLinkFormat' => null,
];
private $handlesHrefGracefully;
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function __construct($output = null, string $charset = null, int $flags = 0) public function __construct($output = null, $charset = null)
{ {
parent::__construct($output, $charset, $flags); parent::__construct($output, $charset);
if ('\\' === \DIRECTORY_SEPARATOR && !$this->isWindowsTrueColor()) { if ('\\' === DIRECTORY_SEPARATOR && false !== @getenv('ANSICON')) {
// Use only the base 16 xterm colors when using ANSICON or standard Windows 10 CLI // Use only the base 16 xterm colors when using ANSICON
$this->setStyles([ $this->setStyles(array(
'default' => '31', 'default' => '31',
'num' => '1;34', 'num' => '1;34',
'const' => '1;31', 'const' => '1;31',
@ -80,10 +70,8 @@ class CliDumper extends AbstractDumper
'meta' => '35', 'meta' => '35',
'key' => '32', 'key' => '32',
'index' => '34', 'index' => '34',
]); ));
} }
$this->displayOptions['fileLinkFormat'] = ini_get('xdebug.file_link_format') ?: get_cfg_var('xdebug.file_link_format') ?: 'file://%f#L%l';
} }
/** /**
@ -109,23 +97,13 @@ 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)
{ {
$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->displayOptions = $displayOptions + $this->displayOptions;
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
@ -134,13 +112,9 @@ class CliDumper extends AbstractDumper
$this->dumpKey($cursor); $this->dumpKey($cursor);
$style = 'const'; $style = 'const';
$attr = $cursor->attr; $attr = array();
switch ($type) { switch ($type) {
case 'default':
$style = 'default';
break;
case 'integer': case 'integer':
$style = 'num'; $style = 'num';
break; break;
@ -149,9 +123,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)) {
@ -170,14 +144,14 @@ class CliDumper extends AbstractDumper
break; break;
default: default:
$attr += ['value' => $this->utf8Encode($value)]; $attr['value'] = isset($value[0]) && !preg_match('//u', $value) ? $this->utf8Encode($value) : $value;
$value = $this->utf8Encode($type); $value = isset($type[0]) && !preg_match('//u', $type) ? $this->utf8Encode($type) : $type;
break; break;
} }
$this->line .= $this->style($style, $value, $attr); $this->line .= $this->style($style, $value, $attr);
$this->endValue($cursor); $this->dumpLine($cursor->depth, true);
} }
/** /**
@ -186,30 +160,26 @@ 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);
} }
if ('' === $str) { if ('' === $str) {
$this->line .= '""'; $this->line .= '""';
$this->endValue($cursor); $this->dumpLine($cursor->depth, true);
} else { } else {
$attr += [ $attr = array(
'length' => 0 <= $cut ? mb_strlen($str, 'UTF-8') + $cut : 0, 'length' => 0 <= $cut ? iconv_strlen($str, 'UTF-8') + $cut : 0,
'binary' => $bin, 'binary' => $bin,
]; );
$str = explode("\n", $str); $str = explode("\n", $str);
if (isset($str[1]) && !isset($str[2]) && !isset($str[1][0])) { if (isset($str[1]) && !isset($str[2]) && !isset($str[1][0])) {
unset($str[1]); unset($str[1]);
$str[0] .= "\n"; $str[0] .= "\n";
} }
$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';
} }
@ -225,8 +195,8 @@ class CliDumper extends AbstractDumper
if ($i < $m) { if ($i < $m) {
$str .= "\n"; $str .= "\n";
} }
if (0 < $this->maxStringWidth && $this->maxStringWidth < $len = mb_strlen($str, 'UTF-8')) { if (0 < $this->maxStringWidth && $this->maxStringWidth < $len = iconv_strlen($str, 'UTF-8')) {
$str = mb_substr($str, 0, $this->maxStringWidth, 'UTF-8'); $str = iconv_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) {
@ -259,11 +229,7 @@ class CliDumper extends AbstractDumper
$lineCut = 0; $lineCut = 0;
} }
if ($i > $m) { $this->dumpLine($cursor->depth, $i > $m);
$this->endValue($cursor);
} else {
$this->dumpLine($cursor->depth);
}
} }
} }
} }
@ -273,31 +239,23 @@ class CliDumper extends AbstractDumper
*/ */
public function enterHash(Cursor $cursor, $type, $class, $hasChild) public function enterHash(Cursor $cursor, $type, $class, $hasChild)
{ {
if (null === $this->colors) {
$this->colors = $this->supportsColors();
}
$this->dumpKey($cursor); $this->dumpKey($cursor);
$attr = $cursor->attr;
if ($this->collapseNextHash) { if (!preg_match('//u', $class)) {
$cursor->skipChildren = true; $class = $this->utf8Encode($class);
$this->collapseNextHash = $hasChild = false;
} }
$class = $this->utf8Encode($class);
if (Cursor::HASH_OBJECT === $type) { if (Cursor::HASH_OBJECT === $type) {
$prefix = $class && 'stdClass' !== $class ? $this->style('note', $class, $attr).(empty($attr['cut_hash']) ? ' {' : '') : '{'; $prefix = $class && 'stdClass' !== $class ? $this->style('note', $class).' {' : '{';
} elseif (Cursor::HASH_RESOURCE === $type) { } elseif (Cursor::HASH_RESOURCE === $type) {
$prefix = $this->style('note', $class.' resource', $attr).($hasChild ? ' {' : ' '); $prefix = $this->style('note', $class.' resource').($hasChild ? ' {' : ' ');
} else { } else {
$prefix = $class && !(self::DUMP_LIGHT_ARRAY & $this->flags) ? $this->style('note', 'array:'.$class).' [' : '['; $prefix = $class ? $this->style('note', 'array:'.$class).' [' : '[';
} }
if (($cursor->softRefCount || 0 < $cursor->softRefHandle) && empty($attr['cut_hash'])) { if ($cursor->softRefCount || 0 < $cursor->softRefHandle) {
$prefix .= $this->style('ref', (Cursor::HASH_RESOURCE === $type ? '@' : '#').(0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->softRefTo), ['count' => $cursor->softRefCount]); $prefix .= $this->style('ref', (Cursor::HASH_RESOURCE === $type ? '@' : '#').(0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->softRefTo), array('count' => $cursor->softRefCount));
} elseif ($cursor->hardRefTo && !$cursor->refIndex && $class) { } elseif ($cursor->hardRefTo && !$cursor->refIndex && $class) {
$prefix .= $this->style('ref', '&'.$cursor->hardRefTo, ['count' => $cursor->hardRefCount]); $prefix .= $this->style('ref', '&'.$cursor->hardRefTo, array('count' => $cursor->hardRefCount));
} elseif (!$hasChild && Cursor::HASH_RESOURCE === $type) { } elseif (!$hasChild && Cursor::HASH_RESOURCE === $type) {
$prefix = substr($prefix, 0, -1); $prefix = substr($prefix, 0, -1);
} }
@ -314,19 +272,17 @@ class CliDumper extends AbstractDumper
*/ */
public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut) public function leaveHash(Cursor $cursor, $type, $class, $hasChild, $cut)
{ {
if (empty($cursor->attr['cut_hash'])) { $this->dumpEllipsis($cursor, $hasChild, $cut);
$this->dumpEllipsis($cursor, $hasChild, $cut); $this->line .= Cursor::HASH_OBJECT === $type ? '}' : (Cursor::HASH_RESOURCE !== $type ? ']' : ($hasChild ? '}' : ''));
$this->line .= Cursor::HASH_OBJECT === $type ? '}' : (Cursor::HASH_RESOURCE !== $type ? ']' : ($hasChild ? '}' : '')); $this->dumpLine($cursor->depth, true);
}
$this->endValue($cursor);
} }
/** /**
* Dumps an ellipsis for cut children. * Dumps an ellipsis for cut children.
* *
* @param bool $hasChild When the dump of the hash has child item * @param Cursor $cursor The Cursor position in the dump.
* @param int $cut The number of items the hash has been cut by * @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.
*/ */
protected function dumpEllipsis(Cursor $cursor, $hasChild, $cut) protected function dumpEllipsis(Cursor $cursor, $hasChild, $cut)
{ {
@ -343,6 +299,8 @@ 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.
*/ */
protected function dumpKey(Cursor $cursor) protected function dumpKey(Cursor $cursor)
{ {
@ -350,19 +308,15 @@ class CliDumper extends AbstractDumper
if ($cursor->hashKeyIsBinary) { if ($cursor->hashKeyIsBinary) {
$key = $this->utf8Encode($key); $key = $this->utf8Encode($key);
} }
$attr = ['binary' => $cursor->hashKeyIsBinary]; $attr = array('binary' => $cursor->hashKeyIsBinary);
$bin = $cursor->hashKeyIsBinary ? 'b' : ''; $bin = $cursor->hashKeyIsBinary ? 'b' : '';
$style = 'key'; $style = 'key';
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';
// no break
case Cursor::HASH_ASSOC: case Cursor::HASH_ASSOC:
if (\is_int($key)) { if (is_int($key)) {
$this->line .= $this->style($style, $key).' => '; $this->line .= $this->style($style, $key).' => ';
} else { } else {
$this->line .= $bin.'"'.$this->style($style, $key).'" => '; $this->line .= $bin.'"'.$this->style($style, $key).'" => ';
@ -371,24 +325,20 @@ class CliDumper extends AbstractDumper
case Cursor::HASH_RESOURCE: case Cursor::HASH_RESOURCE:
$key = "\0~\0".$key; $key = "\0~\0".$key;
// no break // No break;
case Cursor::HASH_OBJECT: case Cursor::HASH_OBJECT:
if (!isset($key[0]) || "\0" !== $key[0]) { if (!isset($key[0]) || "\0" !== $key[0]) {
$this->line .= '+'.$bin.$this->style('public', $key).': '; $this->line .= '+'.$bin.$this->style('public', $key).': ';
} 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][0]) { switch ($key[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 += ['binary' => $cursor->hashKeyIsBinary];
}
break; break;
case '*': case '*':
$style = 'protected'; $style = 'protected';
@ -401,24 +351,16 @@ class CliDumper extends AbstractDumper
break; break;
} }
if (isset($attr['collapse'])) { $this->line .= $bin.$this->style($style, $key[1], $attr).': ';
if ($attr['collapse']) {
$this->collapseNextHash = true;
} else {
$this->expandNextHash = true;
}
}
$this->line .= $bin.$this->style($style, $key[1], $attr).(isset($attr['separator']) ? $attr['separator'] : ': ');
} else { } else {
// This case should not happen // This case should not happen
$this->line .= '-'.$bin.'"'.$this->style('private', $key, ['class' => '']).'": '; $this->line .= '-'.$bin.'"'.$this->style('private', $key, array('class' => '')).'": ';
} }
break; break;
} }
if ($cursor->hardRefTo) { if ($cursor->hardRefTo) {
$this->line .= $this->style('ref', '&'.($cursor->hardRefCount ? $cursor->hardRefTo : ''), ['count' => $cursor->hardRefCount]).' '; $this->line .= $this->style('ref', '&'.($cursor->hardRefCount ? $cursor->hardRefTo : ''), array('count' => $cursor->hardRefCount)).' ';
} }
} }
} }
@ -426,47 +368,28 @@ 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 = []) protected function style($style, $value, $attr = array())
{ {
if (null === $this->colors) { if (null === $this->colors) {
$this->colors = $this->supportsColors(); $this->colors = $this->supportsColors();
} }
if (null === $this->handlesHrefGracefully) { $style = $this->styles[$style];
$this->handlesHrefGracefully = 'JetBrains-JediTerm' !== getenv('TERMINAL_EMULATOR') && !getenv('KONSOLE_VERSION');
}
if (isset($attr['ellipsis'], $attr['ellipsis-type'])) {
$prefix = substr($value, 0, -$attr['ellipsis']);
if ('cli' === \PHP_SAPI && 'path' === $attr['ellipsis-type'] && isset($_SERVER[$pwd = '\\' === \DIRECTORY_SEPARATOR ? 'CD' : 'PWD']) && 0 === strpos($prefix, $_SERVER[$pwd])) {
$prefix = '.'.substr($prefix, \strlen($_SERVER[$pwd]));
}
if (!empty($attr['ellipsis-tail'])) {
$prefix .= substr($value, -$attr['ellipsis'], $attr['ellipsis-tail']);
$value = substr($value, -$attr['ellipsis'] + $attr['ellipsis-tail']);
} else {
$value = substr($value, -$attr['ellipsis']);
}
$value = $this->style('default', $prefix).$this->style($style, $value);
goto href;
}
$map = static::$controlCharsMap; $map = static::$controlCharsMap;
$startCchr = $this->colors ? "\033[m\033[{$this->styles['default']}m" : ''; $startCchr = $this->colors ? "\033[m\033[{$this->styles['default']}m" : '';
$endCchr = $this->colors ? "\033[m\033[{$this->styles[$style]}m" : ''; $endCchr = $this->colors ? "\033[m\033[{$style}m" : '';
$value = preg_replace_callback(static::$controlCharsRx, function ($c) use ($map, $startCchr, $endCchr) { $value = preg_replace_callback(static::$controlCharsRx, function ($c) use ($map, $startCchr, $endCchr) {
$s = $startCchr; $s = $startCchr;
$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.$endCchr; return $s.$endCchr;
@ -474,50 +397,34 @@ class CliDumper extends AbstractDumper
if ($this->colors) { if ($this->colors) {
if ($cchrCount && "\033" === $value[0]) { if ($cchrCount && "\033" === $value[0]) {
$value = substr($value, \strlen($startCchr)); $value = substr($value, strlen($startCchr));
} else { } else {
$value = "\033[{$this->styles[$style]}m".$value; $value = "\033[{$style}m".$value;
} }
if ($cchrCount && $endCchr === substr($value, -\strlen($endCchr))) { if ($cchrCount && $endCchr === substr($value, -strlen($endCchr))) {
$value = substr($value, 0, -\strlen($endCchr)); $value = substr($value, 0, -strlen($endCchr));
} else { } else {
$value .= "\033[{$this->styles['default']}m"; $value .= "\033[{$this->styles['default']}m";
} }
} }
href:
if ($this->colors && $this->handlesHrefGracefully) {
if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], isset($attr['line']) ? $attr['line'] : 0)) {
if ('note' === $style) {
$value .= "\033]8;;{$href}\033\\^\033]8;;\033\\";
} else {
$attr['href'] = $href;
}
}
if (isset($attr['href'])) {
$value = "\033]8;;{$attr['href']}\033\\{$value}\033]8;;\033\\";
}
} elseif ($attr['if_links'] ?? false) {
return '';
}
return $value; return $value;
} }
/** /**
* @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()
{ {
if ($this->outputStream !== static::$defaultOutput) { if ($this->outputStream !== static::$defaultOutput) {
return $this->hasColorSupport($this->outputStream); return @(is_resource($this->outputStream) && function_exists('posix_isatty') && posix_isatty($this->outputStream));
} }
if (null !== static::$defaultColors) { if (null !== static::$defaultColors) {
return static::$defaultColors; return static::$defaultColors;
} }
if (isset($_SERVER['argv'][1])) { if (isset($_SERVER['argv'][1])) {
$colors = $_SERVER['argv']; $colors = $_SERVER['argv'];
$i = \count($colors); $i = count($colors);
while (--$i > 0) { while (--$i > 0) {
if (isset($colors[$i][5])) { if (isset($colors[$i][5])) {
switch ($colors[$i]) { switch ($colors[$i]) {
@ -538,10 +445,17 @@ class CliDumper extends AbstractDumper
} }
} }
$h = stream_get_meta_data($this->outputStream) + ['wrapper_type' => null]; if ('\\' === DIRECTORY_SEPARATOR) {
$h = 'Output' === $h['stream_type'] && 'PHP' === $h['wrapper_type'] ? fopen('php://stdout', 'wb') : $this->outputStream; static::$defaultColors = @(false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI') || 'xterm' === getenv('TERM'));
} elseif (function_exists('posix_isatty')) {
$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;
static::$defaultColors = @posix_isatty($h);
} else {
static::$defaultColors = false;
}
return static::$defaultColors = $this->hasColorSupport($h); return static::$defaultColors;
} }
/** /**
@ -554,101 +468,4 @@ class CliDumper extends AbstractDumper
} }
parent::dumpLine($depth); parent::dumpLine($depth);
} }
protected function endValue(Cursor $cursor)
{
if (-1 === $cursor->hashType) {
return;
}
if (Stub::ARRAY_INDEXED === $cursor->hashType || Stub::ARRAY_ASSOC === $cursor->hashType) {
if (self::DUMP_TRAILING_COMMA & $this->flags && 0 < $cursor->depth) {
$this->line .= ',';
} elseif (self::DUMP_COMMA_SEPARATOR & $this->flags && 1 < $cursor->hashLength - $cursor->hashIndex) {
$this->line .= ',';
}
}
$this->dumpLine($cursor->depth, true);
}
/**
* Returns true if the stream supports colorization.
*
* Reference: Composer\XdebugHandler\Process::supportsColor
* https://github.com/composer/xdebug-handler
*
* @param mixed $stream A CLI output stream
*/
private function hasColorSupport($stream): bool
{
if (!\is_resource($stream) || 'stream' !== get_resource_type($stream)) {
return false;
}
// Follow https://no-color.org/
if (isset($_SERVER['NO_COLOR']) || false !== getenv('NO_COLOR')) {
return false;
}
if ('Hyper' === getenv('TERM_PROGRAM')) {
return true;
}
if (\DIRECTORY_SEPARATOR === '\\') {
return (\function_exists('sapi_windows_vt100_support')
&& @sapi_windows_vt100_support($stream))
|| false !== getenv('ANSICON')
|| 'ON' === getenv('ConEmuANSI')
|| 'xterm' === getenv('TERM');
}
if (\function_exists('stream_isatty')) {
return @stream_isatty($stream);
}
if (\function_exists('posix_isatty')) {
return @posix_isatty($stream);
}
$stat = @fstat($stream);
// Check if formatted mode is S_IFCHR
return $stat ? 0020000 === ($stat['mode'] & 0170000) : false;
}
/**
* Returns true if the Windows terminal supports true color.
*
* Note that this does not check an output stream, but relies on environment
* variables from known implementations, or a PHP and Windows version that
* supports true color.
*/
private function isWindowsTrueColor(): bool
{
$result = 183 <= getenv('ANSICON_VER')
|| 'ON' === getenv('ConEmuANSI')
|| 'xterm' === getenv('TERM')
|| 'Hyper' === getenv('TERM_PROGRAM');
if (!$result && \PHP_VERSION_ID >= 70200) {
$version = sprintf(
'%s.%s.%s',
PHP_WINDOWS_VERSION_MAJOR,
PHP_WINDOWS_VERSION_MINOR,
PHP_WINDOWS_VERSION_BUILD
);
$result = $version >= '10.0.15063';
}
return $result;
}
private function getSourceLink(string $file, int $line)
{
if ($fmt = $this->displayOptions['fileLinkFormat']) {
return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : ($fmt->format($file, $line) ?: 'file://'.$file.'#L'.$line);
}
return false;
}
} }

View File

@ -1,32 +0,0 @@
<?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\Dumper\ContextProvider;
/**
* Tries to provide context on CLI.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
final class CliContextProvider implements ContextProviderInterface
{
public function getContext(): ?array
{
if ('cli' !== \PHP_SAPI) {
return null;
}
return [
'command_line' => $commandLine = implode(' ', $_SERVER['argv'] ?? []),
'identifier' => hash('crc32b', $commandLine.$_SERVER['REQUEST_TIME_FLOAT']),
];
}
}

View File

@ -1,25 +0,0 @@
<?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\Dumper\ContextProvider;
/**
* Interface to provide contextual data about dump data clones sent to a server.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
interface ContextProviderInterface
{
/**
* @return array|null Context data or null if unable to provide any context
*/
public function getContext(): ?array;
}

View File

@ -1,51 +0,0 @@
<?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\Dumper\ContextProvider;
use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\VarDumper\Caster\ReflectionCaster;
use Symfony\Component\VarDumper\Cloner\VarCloner;
/**
* Tries to provide context from a request.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
final class RequestContextProvider implements ContextProviderInterface
{
private $requestStack;
private $cloner;
public function __construct(RequestStack $requestStack)
{
$this->requestStack = $requestStack;
$this->cloner = new VarCloner();
$this->cloner->setMaxItems(0);
$this->cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO);
}
public function getContext(): ?array
{
if (null === $request = $this->requestStack->getCurrentRequest()) {
return null;
}
$controller = $request->attributes->get('_controller');
return [
'uri' => $request->getUri(),
'method' => $request->getMethod(),
'controller' => $controller ? $this->cloner->cloneVar($controller) : $controller,
'identifier' => spl_object_hash($request),
];
}
}

View File

@ -1,126 +0,0 @@
<?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\Dumper\ContextProvider;
use Symfony\Component\HttpKernel\Debug\FileLinkFormatter;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
use Symfony\Component\VarDumper\VarDumper;
use Twig\Template;
/**
* Tries to provide context from sources (class name, file, line, code excerpt, ...).
*
* @author Nicolas Grekas <p@tchwork.com>
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
final class SourceContextProvider implements ContextProviderInterface
{
private $limit;
private $charset;
private $projectDir;
private $fileLinkFormatter;
public function __construct(string $charset = null, string $projectDir = null, FileLinkFormatter $fileLinkFormatter = null, int $limit = 9)
{
$this->charset = $charset;
$this->projectDir = $projectDir;
$this->fileLinkFormatter = $fileLinkFormatter;
$this->limit = $limit;
}
public function getContext(): ?array
{
$trace = debug_backtrace(DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS, $this->limit);
$file = $trace[1]['file'];
$line = $trace[1]['line'];
$name = false;
$fileExcerpt = false;
for ($i = 2; $i < $this->limit; ++$i) {
if (isset($trace[$i]['class'], $trace[$i]['function'])
&& 'dump' === $trace[$i]['function']
&& VarDumper::class === $trace[$i]['class']
) {
$file = $trace[$i]['file'] ?? $file;
$line = $trace[$i]['line'] ?? $line;
while (++$i < $this->limit) {
if (isset($trace[$i]['function'], $trace[$i]['file']) && empty($trace[$i]['class']) && 0 !== strpos($trace[$i]['function'], 'call_user_func')) {
$file = $trace[$i]['file'];
$line = $trace[$i]['line'];
break;
} elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof Template) {
$template = $trace[$i]['object'];
$name = $template->getTemplateName();
$src = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getCode() : (method_exists($template, 'getSource') ? $template->getSource() : false);
$info = $template->getDebugInfo();
if (isset($info[$trace[$i - 1]['line']])) {
$line = $info[$trace[$i - 1]['line']];
$file = method_exists($template, 'getSourceContext') ? $template->getSourceContext()->getPath() : null;
if ($src) {
$src = explode("\n", $src);
$fileExcerpt = [];
for ($i = max($line - 3, 1), $max = min($line + 3, \count($src)); $i <= $max; ++$i) {
$fileExcerpt[] = '<li'.($i === $line ? ' class="selected"' : '').'><code>'.$this->htmlEncode($src[$i - 1]).'</code></li>';
}
$fileExcerpt = '<ol start="'.max($line - 3, 1).'">'.implode("\n", $fileExcerpt).'</ol>';
}
}
break;
}
}
break;
}
}
if (false === $name) {
$name = str_replace('\\', '/', $file);
$name = substr($name, strrpos($name, '/') + 1);
}
$context = ['name' => $name, 'file' => $file, 'line' => $line];
$context['file_excerpt'] = $fileExcerpt;
if (null !== $this->projectDir) {
$context['project_dir'] = $this->projectDir;
if (0 === strpos($file, $this->projectDir)) {
$context['file_relative'] = ltrim(substr($file, \strlen($this->projectDir)), \DIRECTORY_SEPARATOR);
}
}
if ($this->fileLinkFormatter && $fileLink = $this->fileLinkFormatter->format($context['file'], $context['line'])) {
$context['file_link'] = $fileLink;
}
return $context;
}
private function htmlEncode(string $s): string
{
$html = '';
$dumper = new HtmlDumper(function ($line) use (&$html) { $html .= $line; }, $this->charset);
$dumper->setDumpHeader('');
$dumper->setDumpBoundaries('', '');
$cloner = new VarCloner();
$dumper->dump($cloner->cloneVar($s));
return substr(strip_tags($html), 1, -1);
}
}

View File

@ -1,43 +0,0 @@
<?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\Dumper;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
/**
* @author Kévin Thérage <therage.kevin@gmail.com>
*/
class ContextualizedDumper implements DataDumperInterface
{
private $wrappedDumper;
private $contextProviders;
/**
* @param ContextProviderInterface[] $contextProviders
*/
public function __construct(DataDumperInterface $wrappedDumper, array $contextProviders)
{
$this->wrappedDumper = $wrappedDumper;
$this->contextProviders = $contextProviders;
}
public function dump(Data $data)
{
$context = [];
foreach ($this->contextProviders as $contextProvider) {
$context[\get_class($contextProvider)] = $contextProvider->getContext();
}
$this->wrappedDumper->dump($data->withContext($context));
}
}

View File

@ -20,5 +20,10 @@ use Symfony\Component\VarDumper\Cloner\Data;
*/ */
interface DataDumperInterface interface DataDumperInterface
{ {
/**
* Dumps a Data object.
*
* @param Data $data A Data object.
*/
public function dump(Data $data); public function dump(Data $data);
} }

View File

@ -23,66 +23,47 @@ class HtmlDumper extends CliDumper
{ {
public static $defaultOutput = 'php://output'; public static $defaultOutput = 'php://output';
protected static $themes = [
'dark' => [
'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: break-all',
'num' => 'font-weight:bold; color:#1299DA',
'const' => 'font-weight:bold',
'str' => 'font-weight:bold; color:#56DB3A',
'note' => 'color:#1299DA',
'ref' => 'color:#A0A0A0',
'public' => 'color:#FFFFFF',
'protected' => 'color:#FFFFFF',
'private' => 'color:#FFFFFF',
'meta' => 'color:#B729D9',
'key' => 'color:#56DB3A',
'index' => 'color:#1299DA',
'ellipsis' => 'color:#FF8400',
'ns' => 'user-select:none;',
],
'light' => [
'default' => 'background:none; color:#CC7832; 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: break-all',
'num' => 'font-weight:bold; color:#1299DA',
'const' => 'font-weight:bold',
'str' => 'font-weight:bold; color:#629755;',
'note' => 'color:#6897BB',
'ref' => 'color:#6E6E6E',
'public' => 'color:#262626',
'protected' => 'color:#262626',
'private' => 'color:#262626',
'meta' => 'color:#B729D9',
'key' => 'color:#789339',
'index' => 'color:#1299DA',
'ellipsis' => 'color:#CC7832',
'ns' => 'user-select:none;',
],
];
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; 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',
private $displayOptions = [ 'num' => 'font-weight:bold; color:#1299DA',
'maxDepth' => 1, 'const' => 'font-weight:bold',
'maxStringLength' => 160, 'str' => 'font-weight:bold; color:#56DB3A',
'fileLinkFormat' => null, 'note' => 'color:#1299DA',
]; 'ref' => 'color:#A0A0A0',
private $extraDisplayOptions = []; 'public' => 'color:#FFFFFF',
'protected' => 'color:#FFFFFF',
'private' => 'color:#FFFFFF',
'meta' => 'color:#B729D9',
'key' => 'color:#56DB3A',
'index' => 'color:#1299DA',
);
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function __construct($output = null, string $charset = null, int $flags = 0) public function __construct($output = null, $charset = null)
{ {
AbstractDumper::__construct($output, $charset, $flags); AbstractDumper::__construct($output, $charset);
$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'); }
$this->styles = static::$themes['dark'] ?? self::$themes['dark'];
/**
* {@inheritdoc}
*/
public function setOutput($output)
{
if ($output !== $prev = parent::setOutput($output)) {
$this->headerIsDumped = false;
}
return $prev;
} }
/** /**
@ -94,30 +75,10 @@ class HtmlDumper extends CliDumper
$this->styles = $styles + $this->styles; $this->styles = $styles + $this->styles;
} }
public function setTheme(string $themeName)
{
if (!isset(static::$themes[$themeName])) {
throw new \InvalidArgumentException(sprintf('Theme "%s" does not exist in class "%s".', $themeName, static::class));
}
$this->setStyles(static::$themes[$themeName]);
}
/**
* 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)
{ {
@ -127,8 +88,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)
{ {
@ -139,13 +100,10 @@ class HtmlDumper extends CliDumper
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function dump(Data $data, $output = null, array $extraDisplayOptions = []) public function dump(Data $data, $output = null)
{ {
$this->extraDisplayOptions = $extraDisplayOptions; parent::dump($data, $output);
$result = parent::dump($data, $output);
$this->dumpId = 'sf-dump-'.mt_rand(); $this->dumpId = 'sf-dump-'.mt_rand();
return $result;
} }
/** /**
@ -153,13 +111,13 @@ class HtmlDumper extends CliDumper
*/ */
protected function getDumpHeader() protected function getDumpHeader()
{ {
$this->headerIsDumped = null !== $this->outputStream ? $this->outputStream : $this->lineDumper; $this->headerIsDumped = true;
if (null !== $this->dumpHeader) { if (null !== $this->dumpHeader) {
return $this->dumpHeader; return $this->dumpHeader;
} }
$line = str_replace('{$options}', json_encode($this->displayOptions, JSON_FORCE_OBJECT), <<<'EOHTML' $line = <<<'EOHTML'
<script> <script>
Sfdump = window.Sfdump || (function (doc) { Sfdump = window.Sfdump || (function (doc) {
@ -186,31 +144,24 @@ if (!doc.addEventListener) {
function toggle(a, recursive) { function toggle(a, recursive) {
var s = a.nextSibling || {}, oldClass = s.className, arrow, newClass; var s = a.nextSibling || {}, oldClass = s.className, arrow, newClass;
if (/\bsf-dump-compact\b/.test(oldClass)) { if ('sf-dump-compact' == oldClass) {
arrow = '▼'; arrow = '▼';
newClass = 'sf-dump-expanded'; newClass = 'sf-dump-expanded';
} else if (/\bsf-dump-expanded\b/.test(oldClass)) { } else if ('sf-dump-expanded' == oldClass) {
arrow = '▶'; arrow = '▶';
newClass = 'sf-dump-compact'; newClass = 'sf-dump-compact';
} else { } else {
return false; return false;
} }
if (doc.createEvent && s.dispatchEvent) {
var event = doc.createEvent('Event');
event.initEvent('sf-dump-expanded' === newClass ? 'sfbeforedumpexpand' : 'sfbeforedumpcollapse', true, false);
s.dispatchEvent(event);
}
a.lastChild.innerHTML = arrow; a.lastChild.innerHTML = arrow;
s.className = s.className.replace(/\bsf-dump-(compact|expanded)\b/, newClass); s.className = newClass;
if (recursive) { if (recursive) {
try { try {
a = s.querySelectorAll('.'+oldClass); a = s.querySelectorAll('.'+oldClass);
for (s = 0; s < a.length; ++s) { for (s = 0; s < a.length; ++s) {
if (-1 == a[s].className.indexOf(newClass)) { if (a[s].className !== newClass) {
a[s].className = newClass; a[s].className = newClass;
a[s].previousSibling.lastChild.innerHTML = arrow; a[s].previousSibling.lastChild.innerHTML = arrow;
} }
@ -222,152 +173,35 @@ function toggle(a, recursive) {
return true; return true;
}; };
function collapse(a, recursive) { return function (root) {
var s = a.nextSibling || {}, oldClass = s.className;
if (/\bsf-dump-expanded\b/.test(oldClass)) {
toggle(a, recursive);
return true;
}
return false;
};
function expand(a, recursive) {
var s = a.nextSibling || {}, oldClass = s.className;
if (/\bsf-dump-compact\b/.test(oldClass)) {
toggle(a, recursive);
return true;
}
return false;
};
function collapseAll(root) {
var a = root.querySelector('a.sf-dump-toggle');
if (a) {
collapse(a, true);
expand(a);
return true;
}
return false;
}
function reveal(node) {
var previous, parents = [];
while ((node = node.parentNode || {}) && (previous = node.previousSibling) && 'A' === previous.tagName) {
parents.push(previous);
}
if (0 !== parents.length) {
parents.forEach(function (parent) {
expand(parent);
});
return true;
}
return false;
}
function highlight(root, activeNode, nodes) {
resetHighlightedNodes(root);
Array.from(nodes||[]).forEach(function (node) {
if (!/\bsf-dump-highlight\b/.test(node.className)) {
node.className = node.className + ' sf-dump-highlight';
}
});
if (!/\bsf-dump-highlight-active\b/.test(activeNode.className)) {
activeNode.className = activeNode.className + ' sf-dump-highlight-active';
}
}
function resetHighlightedNodes(root) {
Array.from(root.querySelectorAll('.sf-dump-str, .sf-dump-key, .sf-dump-public, .sf-dump-protected, .sf-dump-private')).forEach(function (strNode) {
strNode.className = strNode.className.replace(/\bsf-dump-highlight\b/, '');
strNode.className = strNode.className.replace(/\bsf-dump-highlight-active\b/, '');
});
}
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, n) { 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 {
n = /\bsf-dump-ellipsis\b/.test(e.target.className) ? e.target.parentNode : e.target;
if ((n = n.nextElementSibling) && 'A' == n.tagName) {
if (!/\bsf-dump-toggle\b/.test(n.className)) {
n = n.nextElementSibling || n;
}
f(n, e, true);
}
} }
}); });
}; };
function isCtrlKey(e) { function isCtrlKey(e) {
return e.ctrlKey || e.metaKey; return e.ctrlKey || e.metaKey;
} }
function xpathString(str) {
var parts = str.match(/[^'"]+|['"]/g).map(function (part) {
if ("'" == part) {
return '"\'"';
}
if ('"' == part) {
return "'\"'";
}
return "'" + part + "'";
});
return "concat(" + parts.join(",") + ", '')";
}
function xpathHasClass(className) {
return "contains(concat(' ', normalize-space(@class), ' '), ' " + className +" ')";
}
addEventListener(root, 'mouseover', function (e) { addEventListener(root, 'mouseover', function (e) {
if ('' != refStyle.innerHTML) { if ('' != refStyle.innerHTML) {
refStyle.innerHTML = ''; refStyle.innerHTML = '';
} }
}); });
a('mouseover', function (a, e, c) { a('mouseover', function (a) {
if (c) { if (a = idRx.exec(a.className)) {
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, c) { a('click', function (a, e) {
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))) {
@ -383,13 +217,12 @@ return function (root, x) {
if (f && t && f[0] !== t[0]) { if (f && t && f[0] !== t[0]) {
r.innerHTML = r.innerHTML.replace(new RegExp('^'+f[0].replace(rxEsc, '\\$1'), 'mg'), t[0]); r.innerHTML = r.innerHTML.replace(new RegExp('^'+f[0].replace(rxEsc, '\\$1'), 'mg'), t[0]);
} }
if (/\bsf-dump-compact\b/.test(r.className)) { if ('sf-dump-compact' == r.className) {
toggle(s, isCtrlKey(e)); toggle(s, isCtrlKey(e));
} }
} }
if (c) { if (doc.getSelection) {
} else if (doc.getSelection) {
try { try {
doc.getSelection().removeAllRanges(); doc.getSelection().removeAllRanges();
} catch (e) { } catch (e) {
@ -398,23 +231,31 @@ return function (root, x) {
} 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(/\bsf-dump-str-(expand|collapse)\b/, 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++]);
len = t.length;
for (i = 0; i < len; ++i) { root = t;
elt = t[i]; len = t.length;
if ('SAMP' == elt.tagName) { i = t = 0;
while (i < len) {
elt = root[i];
if ("SAMP" == elt.tagName) {
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');
@ -426,27 +267,19 @@ return function (root, x) {
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);
var className = elt.className;
elt.className = 'sf-dump-expanded';
if (className ? 'sf-dump-expanded' !== className : (x > options.maxDepth)) {
toggle(a); toggle(a);
} }
} else if (/\bsf-dump-ref\b/.test(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 {
s = a.nextSibling; t = a.nextSibling;
elt.appendChild(a); elt.appendChild(a);
s.parentNode.insertBefore(a, s); t.parentNode.insertBefore(a, t);
if (/^[@#]/.test(elt.innerHTML)) { if (/^[@#]/.test(elt.innerHTML)) {
elt.innerHTML += ' <span>▶</span>'; elt.innerHTML += ' <span>▶</span>';
} else { } else {
@ -462,211 +295,17 @@ return function (root, x) {
} }
} }
} }
} ++i;
if (doc.evaluate && Array.from && root.children.length > 1) {
root.setAttribute('tabindex', 0);
SearchState = function () {
this.nodes = [];
this.idx = 0;
};
SearchState.prototype = {
next: function () {
if (this.isEmpty()) {
return this.current();
}
this.idx = this.idx < (this.nodes.length - 1) ? this.idx + 1 : 0;
return this.current();
},
previous: function () {
if (this.isEmpty()) {
return this.current();
}
this.idx = this.idx > 0 ? this.idx - 1 : (this.nodes.length - 1);
return this.current();
},
isEmpty: function () {
return 0 === this.count();
},
current: function () {
if (this.isEmpty()) {
return null;
}
return this.nodes[this.idx];
},
reset: function () {
this.nodes = [];
this.idx = 0;
},
count: function () {
return this.nodes.length;
},
};
function showCurrent(state)
{
var currentNode = state.current(), currentRect, searchRect;
if (currentNode) {
reveal(currentNode);
highlight(root, currentNode, state.nodes);
if ('scrollIntoView' in currentNode) {
currentNode.scrollIntoView(true);
currentRect = currentNode.getBoundingClientRect();
searchRect = search.getBoundingClientRect();
if (currentRect.top < (searchRect.top + searchRect.height)) {
window.scrollBy(0, -(searchRect.top + searchRect.height + 5));
}
}
}
counter.textContent = (state.isEmpty() ? 0 : state.idx + 1) + ' of ' + state.count();
}
var search = doc.createElement('div');
search.className = 'sf-dump-search-wrapper sf-dump-search-hidden';
search.innerHTML = '
<input type="text" class="sf-dump-search-input">
<span class="sf-dump-search-count">0 of 0<\/span>
<button type="button" class="sf-dump-search-input-previous" tabindex="-1">
<svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1683 1331l-166 165q-19 19-45 19t-45-19L896 965l-531 531q-19 19-45 19t-45-19l-166-165q-19-19-19-45.5t19-45.5l742-741q19-19 45-19t45 19l742 741q19 19 19 45.5t-19 45.5z"\/><\/svg>
<\/button>
<button type="button" class="sf-dump-search-input-next" tabindex="-1">
<svg viewBox="0 0 1792 1792" xmlns="http://www.w3.org/2000/svg"><path d="M1683 808l-742 741q-19 19-45 19t-45-19L109 808q-19-19-19-45.5t19-45.5l166-165q19-19 45-19t45 19l531 531 531-531q19-19 45-19t45 19l166 165q19 19 19 45.5t-19 45.5z"\/><\/svg>
<\/button>
';
root.insertBefore(search, root.firstChild);
var state = new SearchState();
var searchInput = search.querySelector('.sf-dump-search-input');
var counter = search.querySelector('.sf-dump-search-count');
var searchInputTimer = 0;
var previousSearchQuery = '';
addEventListener(searchInput, 'keyup', function (e) {
var searchQuery = e.target.value;
/* Don't perform anything if the pressed key didn't change the query */
if (searchQuery === previousSearchQuery) {
return;
}
previousSearchQuery = searchQuery;
clearTimeout(searchInputTimer);
searchInputTimer = setTimeout(function () {
state.reset();
collapseAll(root);
resetHighlightedNodes(root);
if ('' === searchQuery) {
counter.textContent = '0 of 0';
return;
}
var classMatches = [
"sf-dump-str",
"sf-dump-key",
"sf-dump-public",
"sf-dump-protected",
"sf-dump-private",
].map(xpathHasClass).join(' or ');
var xpathResult = doc.evaluate('.//span[' + classMatches + '][contains(translate(child::text(), ' + xpathString(searchQuery.toUpperCase()) + ', ' + xpathString(searchQuery.toLowerCase()) + '), ' + xpathString(searchQuery.toLowerCase()) + ')]', root, null, XPathResult.ORDERED_NODE_ITERATOR_TYPE, null);
while (node = xpathResult.iterateNext()) state.nodes.push(node);
showCurrent(state);
}, 400);
});
Array.from(search.querySelectorAll('.sf-dump-search-input-next, .sf-dump-search-input-previous')).forEach(function (btn) {
addEventListener(btn, 'click', function (e) {
e.preventDefault();
-1 !== e.target.className.indexOf('next') ? state.next() : state.previous();
searchInput.focus();
collapseAll(root);
showCurrent(state);
})
});
addEventListener(root, 'keydown', function (e) {
var isSearchActive = !/\bsf-dump-search-hidden\b/.test(search.className);
if ((114 === e.keyCode && !isSearchActive) || (isCtrlKey(e) && 70 === e.keyCode)) {
/* F3 or CMD/CTRL + F */
if (70 === e.keyCode && document.activeElement === searchInput) {
/*
* If CMD/CTRL + F is hit while having focus on search input,
* the user probably meant to trigger browser search instead.
* Let the browser execute its behavior:
*/
return;
}
e.preventDefault();
search.className = search.className.replace(/\bsf-dump-search-hidden\b/, '');
searchInput.focus();
} else if (isSearchActive) {
if (27 === e.keyCode) {
/* ESC key */
search.className += ' sf-dump-search-hidden';
e.preventDefault();
resetHighlightedNodes(root);
searchInput.value = '';
} else if (
(isCtrlKey(e) && 71 === e.keyCode) /* CMD/CTRL + G */
|| 13 === e.keyCode /* Enter */
|| 114 === e.keyCode /* F3 */
) {
e.preventDefault();
e.shiftKey ? state.previous() : state.next();
collapseAll(root);
showCurrent(state);
}
}
});
}
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><style> </script>
<style>
pre.sf-dump { pre.sf-dump {
display: block; display: block;
white-space: pre; white-space: pre;
padding: 5px; padding: 5px;
overflow: initial !important;
}
pre.sf-dump:after {
content: "";
visibility: hidden;
display: block;
height: 0;
clear: both;
} }
pre.sf-dump span { pre.sf-dump span {
display: inline; display: inline;
@ -674,177 +313,42 @@ pre.sf-dump span {
pre.sf-dump .sf-dump-compact { pre.sf-dump .sf-dump-compact {
display: none; display: none;
} }
pre.sf-dump abbr {
text-decoration: none;
border: none;
cursor: help;
}
pre.sf-dump a { pre.sf-dump a {
text-decoration: none; text-decoration: none;
cursor: pointer; cursor: pointer;
border: 0; border: 0;
outline: none; outline: none;
color: inherit;
} }
pre.sf-dump img { EOHTML;
max-width: 50em;
max-height: 50em;
margin: .5em 0 0 0;
padding: 0;
background: url(data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAAAAAA6mKC9AAAAHUlEQVQY02O8zAABilCaiQEN0EeA8QuUcX9g3QEAAjcC5piyhyEAAAAASUVORK5CYII=) #D3D3D3;
}
pre.sf-dump .sf-dump-ellipsis {
display: inline-block;
overflow: visible;
text-overflow: ellipsis;
max-width: 5em;
white-space: nowrap;
overflow: hidden;
vertical-align: top;
}
pre.sf-dump .sf-dump-ellipsis+.sf-dump-ellipsis {
max-width: none;
}
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;
}
.sf-dump-public.sf-dump-highlight,
.sf-dump-protected.sf-dump-highlight,
.sf-dump-private.sf-dump-highlight,
.sf-dump-str.sf-dump-highlight,
.sf-dump-key.sf-dump-highlight {
background: rgba(111, 172, 204, 0.3);
border: 1px solid #7DA0B1;
border-radius: 3px;
}
.sf-dump-public.sf-dump-highlight-active,
.sf-dump-protected.sf-dump-highlight-active,
.sf-dump-private.sf-dump-highlight-active,
.sf-dump-str.sf-dump-highlight-active,
.sf-dump-key.sf-dump-highlight-active {
background: rgba(253, 175, 0, 0.4);
border: 1px solid #ffa500;
border-radius: 3px;
}
pre.sf-dump .sf-dump-search-hidden {
display: none !important;
}
pre.sf-dump .sf-dump-search-wrapper {
font-size: 0;
white-space: nowrap;
margin-bottom: 5px;
display: flex;
position: -webkit-sticky;
position: sticky;
top: 5px;
}
pre.sf-dump .sf-dump-search-wrapper > * {
vertical-align: top;
box-sizing: border-box;
height: 21px;
font-weight: normal;
border-radius: 0;
background: #FFF;
color: #757575;
border: 1px solid #BBB;
}
pre.sf-dump .sf-dump-search-wrapper > input.sf-dump-search-input {
padding: 3px;
height: 21px;
font-size: 12px;
border-right: none;
border-top-left-radius: 3px;
border-bottom-left-radius: 3px;
color: #000;
min-width: 15px;
width: 100%;
}
pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-next,
pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-previous {
background: #F2F2F2;
outline: none;
border-left: none;
font-size: 0;
line-height: 0;
}
pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-next {
border-top-right-radius: 3px;
border-bottom-right-radius: 3px;
}
pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-next > svg,
pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-input-previous > svg {
pointer-events: none;
width: 12px;
height: 12px;
}
pre.sf-dump .sf-dump-search-wrapper > .sf-dump-search-count {
display: inline-block;
padding: 0 5px;
margin: 0;
border-left: none;
line-height: 21px;
font-size: 12px;
}
EOHTML
);
foreach ($this->styles as $class => $style) { foreach ($this->styles as $class => $style) {
$line .= 'pre.sf-dump'.('default' === $class ? ', pre.sf-dump' : '').' .sf-dump-'.$class.'{'.$style.'}'; $line .= 'pre.sf-dump'.('default' !== $class ? ' .sf-dump-'.$class : '').'{'.$style.'}';
} }
$line .= 'pre.sf-dump .sf-dump-ellipsis-note{'.$this->styles['note'].'}';
return $this->dumpHeader = preg_replace('/\s+/', ' ', $line).'</style>'.$this->dumpHeader; return $this->dumpHeader = preg_replace('/\s+/', ' ', $line).'</style>'.$this->dumpHeader;
} }
/**
* {@inheritdoc}
*/
public function dumpString(Cursor $cursor, $str, $bin, $cut)
{
if ('' === $str && isset($cursor->attr['img-data'], $cursor->attr['content-type'])) {
$this->dumpKey($cursor);
$this->line .= $this->style('default', $cursor->attr['img-size'] ?? '', []).' <samp>';
$this->endValue($cursor);
$this->line .= $this->indentPad;
$this->line .= sprintf('<img src="data:%s;base64,%s" /></samp>', $cursor->attr['content-type'], base64_encode($cursor->attr['img-data']));
$this->endValue($cursor);
} else {
parent::dumpString($cursor, $str, $bin, $cut);
}
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function enterHash(Cursor $cursor, $type, $class, $hasChild) public function enterHash(Cursor $cursor, $type, $class, $hasChild)
{ {
if (Cursor::HASH_OBJECT === $type) {
$cursor->attr['depth'] = $cursor->depth;
}
parent::enterHash($cursor, $type, $class, false); parent::enterHash($cursor, $type, $class, false);
if ($cursor->skipChildren) {
$cursor->skipChildren = false;
$eol = ' class=sf-dump-compact>';
} elseif ($this->expandNextHash) {
$this->expandNextHash = false;
$eol = ' class=sf-dump-expanded>';
} else {
$eol = '>';
}
if ($hasChild) { if ($hasChild) {
$this->line .= '<samp';
if ($cursor->refIndex) { if ($cursor->refIndex) {
$r = Cursor::HASH_OBJECT !== $type ? 1 - (Cursor::HASH_RESOURCE !== $type) : 2; $r = Cursor::HASH_OBJECT !== $type ? 1 - (Cursor::HASH_RESOURCE !== $type) : 2;
$r .= $r && 0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->refIndex; $r .= $r && 0 < $cursor->softRefHandle ? $cursor->softRefHandle : $cursor->refIndex;
$this->line .= sprintf(' id=%s-ref%s', $this->dumpId, $r); $this->line .= sprintf('<samp id=%s-ref%s>', $this->dumpId, $r);
} else {
$this->line .= '<samp>';
} }
$this->line .= $eol;
$this->dumpLine($cursor->depth); $this->dumpLine($cursor->depth);
} }
} }
@ -864,13 +368,13 @@ EOHTML
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
protected function style($style, $value, $attr = []) protected function style($style, $value, $attr = array())
{ {
if ('' === $value) { if ('' === $value) {
return ''; return '';
} }
$v = esc($value); $v = htmlspecialchars($value, ENT_QUOTES, 'UTF-8');
if ('ref' === $style) { if ('ref' === $style) {
if (empty($attr['count'])) { if (empty($attr['count'])) {
@ -881,76 +385,41 @@ 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 && isset($attr['value'])) { if ('const' === $style && array_key_exists('value', $attr)) {
$style .= sprintf(' title="%s"', esc(is_scalar($attr['value']) ? $attr['value'] : json_encode($attr['value']))); $style .= sprintf(' title="%s"', htmlspecialchars(json_encode($attr['value']), ENT_QUOTES, 'UTF-8'));
} 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="%d%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : ''); $style .= sprintf(' title="%s%s characters"', $attr['length'], $attr['binary'] ? ' binary or non-UTF-8' : '');
} elseif ('note' === $style && 0 < ($attr['depth'] ?? 0) && false !== $c = strrpos($value, '\\')) { } elseif ('note' === $style && false !== $c = strrpos($v, '\\')) {
$style .= ' title=""'; return sprintf('<abbr title="%s" class=sf-dump-%s>%s</abbr>', $v, $style, substr($v, $c + 1));
$attr += [
'ellipsis' => \strlen($value) - $c,
'ellipsis-type' => 'note',
'ellipsis-tail' => 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`"', esc($this->utf8Encode($attr['class']))); $style .= sprintf(' title="Private property defined in class:&#10;`%s`"', $attr['class']);
} }
$map = static::$controlCharsMap; $map = static::$controlCharsMap;
$style = "<span class=sf-dump-{$style}>";
if (isset($attr['ellipsis'])) { $v = preg_replace_callback(static::$controlCharsRx, function ($c) use ($map, $style) {
$class = 'sf-dump-ellipsis'; $s = '</span>';
if (isset($attr['ellipsis-type'])) {
$class = sprintf('"%s sf-dump-ellipsis-%s"', $class, $attr['ellipsis-type']);
}
$label = esc(substr($value, -$attr['ellipsis']));
$style = str_replace(' title="', " title=\"$v\n", $style);
$v = sprintf('<span class=%s>%s</span>', $class, substr($v, 0, -\strlen($label)));
if (!empty($attr['ellipsis-tail'])) {
$tail = \strlen(esc(substr($value, -$attr['ellipsis'], $attr['ellipsis-tail'])));
$v .= sprintf('<span class=%s>%s</span>%s', $class, substr($label, 0, $tail), substr($label, $tail));
} else {
$v .= $label;
}
}
$v = "<span class=sf-dump-{$style}>".preg_replace_callback(static::$controlCharsRx, function ($c) use ($map) {
$s = $b = '<span class="sf-dump-default';
$c = $c[$i = 0]; $c = $c[$i = 0];
if ($ns = "\r" === $c[$i] || "\n" === $c[$i]) {
$s .= ' sf-dump-ns';
}
$s .= '">';
do { do {
if (("\r" === $c[$i] || "\n" === $c[$i]) !== $ns) { $s .= isset($map[$c[$i]]) ? $map[$c[$i]] : sprintf('\x%02X', ord($c[$i]));
$s .= '</span>'.$b;
if ($ns = !$ns) {
$s .= ' sf-dump-ns';
}
$s .= '">';
}
$s .= isset($map[$c[$i]]) ? $map[$c[$i]] : sprintf('\x%02X', \ord($c[$i]));
} while (isset($c[++$i])); } while (isset($c[++$i]));
return $s.'</span>'; return $s.$style;
}, $v).'</span>'; }, $v, -1, $cchrCount);
if (isset($attr['file']) && $href = $this->getSourceLink($attr['file'], isset($attr['line']) ? $attr['line'] : 0)) { if ($cchrCount && '<' === $v[0]) {
$attr['href'] = $href; $v = substr($v, 7);
} else {
$v = $style.$v;
} }
if (isset($attr['href'])) { if ($cchrCount && '>' === substr($v, -1)) {
$target = isset($attr['file']) ? '' : ' target="_blank"'; $v = substr($v, 0, -strlen($style));
$v = sprintf('<a href="%s"%s rel="noopener noreferrer">%s</a>', esc($this->utf8Encode($attr['href'])), $target, $v); } else {
} $v .= '</span>';
if (isset($attr['lang'])) {
$v = sprintf('<code class="%s">%s</code>', esc($attr['lang']), $v);
} }
return $v; return $v;
@ -964,17 +433,12 @@ 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 !== (null !== $this->outputStream ? $this->outputStream : $this->lineDumper)) { if (!$this->headerIsDumped) {
$this->line = $this->getDumpHeader().$this->line; $this->line = $this->getDumpHeader().$this->line;
} }
if (-1 === $depth) { if (-1 === $depth) {
$args = ['"'.$this->dumpId.'"']; $this->line .= sprintf($this->dumpSuffix, $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;
@ -985,20 +449,4 @@ EOHTML
} }
AbstractDumper::dumpLine($depth); AbstractDumper::dumpLine($depth);
} }
private function getSourceLink(string $file, int $line)
{
$options = $this->extraDisplayOptions + $this->displayOptions;
if ($fmt = $options['fileLinkFormat']) {
return \is_string($fmt) ? strtr($fmt, ['%f' => $file, '%l' => $line]) : $fmt->format($file, $line);
}
return false;
}
}
function esc($str)
{
return htmlspecialchars($str, ENT_QUOTES, 'UTF-8');
} }

View File

@ -1,53 +0,0 @@
<?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\Dumper;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
use Symfony\Component\VarDumper\Server\Connection;
/**
* ServerDumper forwards serialized Data clones to a server.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
class ServerDumper implements DataDumperInterface
{
private $connection;
private $wrappedDumper;
/**
* @param string $host The server host
* @param DataDumperInterface|null $wrappedDumper A wrapped instance used whenever we failed contacting the server
* @param ContextProviderInterface[] $contextProviders Context providers indexed by context name
*/
public function __construct(string $host, DataDumperInterface $wrappedDumper = null, array $contextProviders = [])
{
$this->connection = new Connection($host, $contextProviders);
$this->wrappedDumper = $wrappedDumper;
}
public function getContextProviders(): array
{
return $this->connection->getContextProviders();
}
/**
* {@inheritdoc}
*/
public function dump(Data $data)
{
if (!$this->connection->write($data) && $this->wrappedDumper) {
$this->wrappedDumper->dump($data);
}
}
}

View File

@ -17,10 +17,10 @@ namespace Symfony\Component\VarDumper\Exception;
class ThrowingCasterException extends \Exception class ThrowingCasterException extends \Exception
{ {
/** /**
* @param \Throwable $prev The exception thrown from the caster * @param \Exception $prev The exception thrown from the caster
*/ */
public function __construct(\Throwable $prev) public function __construct(\Exception $prev)
{ {
parent::__construct('Unexpected '.\get_class($prev).' thrown from a caster: '.$prev->getMessage(), 0, $prev); parent::__construct('Unexpected '.get_class($prev).' thrown from a caster: '.$prev->getMessage(), 0, $prev);
} }
} }

View File

@ -1,4 +1,4 @@
Copyright (c) 2014-2020 Fabien Potencier Copyright (c) 2014-2015 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,15 +1,14 @@
VarDumper Component Symfony mechanism for exploring and dumping PHP variables
=================== =========================================================
The VarDumper component provides mechanisms for walking through any arbitrary This component provides a mechanism that allows exploring then dumping
PHP variable. It provides a better `dump()` function that you can use instead any PHP variable.
of `var_dump`.
Resources It handles scalars, objects and resources properly, taking hard and soft
--------- 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.
* [Documentation](https://symfony.com/doc/current/components/var_dumper/introduction.html) The component exposes all the parts involved in the different steps of
* [Contributing](https://symfony.com/doc/current/contributing/index.html) cloning then dumping a PHP variable, while applying size limits and having
* [Report issues](https://github.com/symfony/symfony/issues) and specialized output formats and methods.
[send Pull Requests](https://github.com/symfony/symfony/pulls)
in the [main Symfony repository](https://github.com/symfony/symfony)

View File

@ -1,63 +0,0 @@
#!/usr/bin/env php
<?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.
*/
/**
* Starts a dump server to collect and output dumps on a single place with multiple formats support.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
use Psr\Log\LoggerInterface;
use Symfony\Component\Console\Application;
use Symfony\Component\Console\Input\ArgvInput;
use Symfony\Component\Console\Input\InputOption;
use Symfony\Component\Console\Logger\ConsoleLogger;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\VarDumper\Command\ServerDumpCommand;
use Symfony\Component\VarDumper\Server\DumpServer;
function includeIfExists(string $file): bool
{
return file_exists($file) && include $file;
}
if (
!includeIfExists(__DIR__ . '/../../../../autoload.php') &&
!includeIfExists(__DIR__ . '/../../vendor/autoload.php') &&
!includeIfExists(__DIR__ . '/../../../../../../vendor/autoload.php')
) {
fwrite(STDERR, 'Install dependencies using Composer.'.PHP_EOL);
exit(1);
}
if (!class_exists(Application::class)) {
fwrite(STDERR, 'You need the "symfony/console" component in order to run the VarDumper server.'.PHP_EOL);
exit(1);
}
$input = new ArgvInput();
$output = new ConsoleOutput();
$defaultHost = '127.0.0.1:9912';
$host = $input->getParameterOption(['--host'], $_SERVER['VAR_DUMPER_SERVER'] ?? $defaultHost, true);
$logger = interface_exists(LoggerInterface::class) ? new ConsoleLogger($output->getErrorOutput()) : null;
$app = new Application();
$app->getDefinition()->addOption(
new InputOption('--host', null, InputOption::VALUE_REQUIRED, 'The address the server should listen to', $defaultHost)
);
$app->add($command = new ServerDumpCommand(new DumpServer($host, $logger)))
->getApplication()
->setDefaultCommand($command->getName(), true)
->run($input, $output)
;

View File

@ -1,130 +0,0 @@
body {
display: flex;
flex-direction: column-reverse;
justify-content: flex-end;
max-width: 1140px;
margin: auto;
padding: 15px;
word-wrap: break-word;
background-color: #F9F9F9;
color: #222;
font-family: Helvetica, Arial, sans-serif;
font-size: 14px;
line-height: 1.4;
}
p {
margin: 0;
}
a {
color: #218BC3;
text-decoration: none;
}
a:hover {
text-decoration: underline;
}
.text-small {
font-size: 12px !important;
}
article {
margin: 5px;
margin-bottom: 10px;
}
article > header > .row {
display: flex;
flex-direction: row;
align-items: baseline;
margin-bottom: 10px;
}
article > header > .row > .col {
flex: 1;
display: flex;
align-items: baseline;
}
article > header > .row > h2 {
font-size: 14px;
color: #222;
font-weight: normal;
font-family: "Lucida Console", monospace, sans-serif;
word-break: break-all;
margin: 20px 5px 0 0;
user-select: all;
}
article > header > .row > h2 > code {
white-space: nowrap;
user-select: none;
color: #cc2255;
background-color: #f7f7f9;
border: 1px solid #e1e1e8;
border-radius: 3px;
margin-right: 5px;
padding: 0 3px;
}
article > header > .row > time.col {
flex: 0;
text-align: right;
white-space: nowrap;
color: #999;
font-style: italic;
}
article > header ul.tags {
list-style: none;
padding: 0;
margin: 0;
font-size: 12px;
}
article > header ul.tags > li {
user-select: all;
margin-bottom: 2px;
}
article > header ul.tags > li > span.badge {
display: inline-block;
padding: .25em .4em;
margin-right: 5px;
border-radius: 4px;
background-color: #6c757d3b;
color: #524d4d;
font-size: 12px;
text-align: center;
font-weight: 700;
line-height: 1;
white-space: nowrap;
vertical-align: baseline;
user-select: none;
}
article > section.body {
border: 1px solid #d8d8d8;
background: #FFF;
padding: 10px;
border-radius: 3px;
}
pre.sf-dump {
border-radius: 3px;
margin-bottom: 0;
}
.hidden {
display: none !important;
}
.dumped-tag > .sf-dump {
display: inline-block;
margin: 0;
padding: 1px 5px;
line-height: 1.4;
vertical-align: top;
background-color: transparent;
user-select: auto;
}
.dumped-tag > pre.sf-dump,
.dumped-tag > .sf-dump-default {
color: #CC7832;
background: none;
}
.dumped-tag > .sf-dump .sf-dump-str { color: #629755; }
.dumped-tag > .sf-dump .sf-dump-private,
.dumped-tag > .sf-dump .sf-dump-protected,
.dumped-tag > .sf-dump .sf-dump-public { color: #262626; }
.dumped-tag > .sf-dump .sf-dump-note { color: #6897BB; }
.dumped-tag > .sf-dump .sf-dump-key { color: #789339; }
.dumped-tag > .sf-dump .sf-dump-ref { color: #6E6E6E; }
.dumped-tag > .sf-dump .sf-dump-ellipsis { color: #CC7832; max-width: 100em; }
.dumped-tag > .sf-dump .sf-dump-ellipsis-path { max-width: 5em; }
.dumped-tag > .sf-dump .sf-dump-ns { user-select: none; }

View File

@ -15,29 +15,10 @@ if (!function_exists('dump')) {
/** /**
* @author Nicolas Grekas <p@tchwork.com> * @author Nicolas Grekas <p@tchwork.com>
*/ */
function dump($var, ...$moreVars) function dump($var)
{ {
VarDumper::dump($var); foreach (func_get_args() as $var) {
VarDumper::dump($var);
foreach ($moreVars as $v) {
VarDumper::dump($v);
} }
if (1 < func_num_args()) {
return func_get_args();
}
return $var;
}
}
if (!function_exists('dd')) {
function dd(...$vars)
{
foreach ($vars as $v) {
VarDumper::dump($v);
}
exit(1);
} }
} }

View File

@ -1,10 +0,0 @@
document.addEventListener('DOMContentLoaded', function() {
let prev = null;
Array.from(document.getElementsByTagName('article')).reverse().forEach(function (article) {
const dedupId = article.dataset.dedupId;
if (dedupId === prev) {
article.getElementsByTagName('header')[0].classList.add('hidden');
}
prev = dedupId;
});
});

View File

@ -1,95 +0,0 @@
<?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\Server;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Dumper\ContextProvider\ContextProviderInterface;
/**
* Forwards serialized Data clones to a server.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*/
class Connection
{
private $host;
private $contextProviders;
private $socket;
/**
* @param string $host The server host
* @param ContextProviderInterface[] $contextProviders Context providers indexed by context name
*/
public function __construct(string $host, array $contextProviders = [])
{
if (false === strpos($host, '://')) {
$host = 'tcp://'.$host;
}
$this->host = $host;
$this->contextProviders = $contextProviders;
}
public function getContextProviders(): array
{
return $this->contextProviders;
}
public function write(Data $data): bool
{
$socketIsFresh = !$this->socket;
if (!$this->socket = $this->socket ?: $this->createSocket()) {
return false;
}
$context = ['timestamp' => microtime(true)];
foreach ($this->contextProviders as $name => $provider) {
$context[$name] = $provider->getContext();
}
$context = array_filter($context);
$encodedPayload = base64_encode(serialize([$data, $context]))."\n";
set_error_handler([self::class, 'nullErrorHandler']);
try {
if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) {
return true;
}
if (!$socketIsFresh) {
stream_socket_shutdown($this->socket, STREAM_SHUT_RDWR);
fclose($this->socket);
$this->socket = $this->createSocket();
}
if (-1 !== stream_socket_sendto($this->socket, $encodedPayload)) {
return true;
}
} finally {
restore_error_handler();
}
return false;
}
private static function nullErrorHandler($t, $m)
{
// no-op
}
private function createSocket()
{
set_error_handler([self::class, 'nullErrorHandler']);
try {
return stream_socket_client($this->host, $errno, $errstr, 3, STREAM_CLIENT_CONNECT | STREAM_CLIENT_ASYNC_CONNECT);
} finally {
restore_error_handler();
}
}
}

View File

@ -1,107 +0,0 @@
<?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\Server;
use Psr\Log\LoggerInterface;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\Stub;
/**
* A server collecting Data clones sent by a ServerDumper.
*
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
*
* @final
*/
class DumpServer
{
private $host;
private $socket;
private $logger;
public function __construct(string $host, LoggerInterface $logger = null)
{
if (false === strpos($host, '://')) {
$host = 'tcp://'.$host;
}
$this->host = $host;
$this->logger = $logger;
}
public function start(): void
{
if (!$this->socket = stream_socket_server($this->host, $errno, $errstr)) {
throw new \RuntimeException(sprintf('Server start failed on "%s": ', $this->host).$errstr.' '.$errno);
}
}
public function listen(callable $callback): void
{
if (null === $this->socket) {
$this->start();
}
foreach ($this->getMessages() as $clientId => $message) {
$payload = @unserialize(base64_decode($message), ['allowed_classes' => [Data::class, Stub::class]]);
// Impossible to decode the message, give up.
if (false === $payload) {
if ($this->logger) {
$this->logger->warning('Unable to decode a message from {clientId} client.', ['clientId' => $clientId]);
}
continue;
}
if (!\is_array($payload) || \count($payload) < 2 || !$payload[0] instanceof Data || !\is_array($payload[1])) {
if ($this->logger) {
$this->logger->warning('Invalid payload from {clientId} client. Expected an array of two elements (Data $data, array $context)', ['clientId' => $clientId]);
}
continue;
}
list($data, $context) = $payload;
$callback($data, $context, $clientId);
}
}
public function getHost(): string
{
return $this->host;
}
private function getMessages(): iterable
{
$sockets = [(int) $this->socket => $this->socket];
$write = [];
while (true) {
$read = $sockets;
stream_select($read, $write, $write, null);
foreach ($read as $stream) {
if ($this->socket === $stream) {
$stream = stream_socket_accept($this->socket);
$sockets[(int) $stream] = $stream;
} elseif (feof($stream)) {
unset($sockets[(int) $stream]);
fclose($stream);
} else {
yield (int) $stream => fgets($stream);
}
}
}
}
}

View File

@ -19,69 +19,27 @@ use Symfony\Component\VarDumper\Dumper\CliDumper;
*/ */
trait VarDumperTestTrait trait VarDumperTestTrait
{ {
/** public function assertDumpEquals($dump, $data, $message = '')
* @internal
*/
private $varDumperConfig = [
'casters' => [],
'flags' => null,
];
protected function setUpVarDumper(array $casters, int $flags = null): void
{ {
$this->varDumperConfig['casters'] = $casters; $this->assertSame(rtrim($dump), $this->getDump($data), $message);
$this->varDumperConfig['flags'] = $flags;
} }
/** public function assertDumpMatchesFormat($dump, $data, $message = '')
* @after
*/
protected function tearDownVarDumper(): void
{ {
$this->varDumperConfig['casters'] = []; $this->assertStringMatchesFormat(rtrim($dump), $this->getDump($data), $message);
$this->varDumperConfig['flags'] = null;
} }
public function assertDumpEquals($expected, $data, $filter = 0, $message = '') protected function getDump($data)
{ {
$this->assertSame($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message); $h = fopen('php://memory', 'r+b');
}
public function assertDumpMatchesFormat($expected, $data, $filter = 0, $message = '')
{
$this->assertStringMatchesFormat($this->prepareExpectation($expected, $filter), $this->getDump($data, null, $filter), $message);
}
/**
* @return string|null
*/
protected function getDump($data, $key = null, $filter = 0)
{
if (null === $flags = $this->varDumperConfig['flags']) {
$flags = getenv('DUMP_LIGHT_ARRAY') ? CliDumper::DUMP_LIGHT_ARRAY : 0;
$flags |= getenv('DUMP_STRING_LENGTH') ? CliDumper::DUMP_STRING_LENGTH : 0;
$flags |= getenv('DUMP_COMMA_SEPARATOR') ? CliDumper::DUMP_COMMA_SEPARATOR : 0;
}
$cloner = new VarCloner(); $cloner = new VarCloner();
$cloner->addCasters($this->varDumperConfig['casters']);
$cloner->setMaxItems(-1); $cloner->setMaxItems(-1);
$dumper = new CliDumper(null, null, $flags); $dumper = new CliDumper($h);
$dumper->setColors(false); $dumper->setColors(false);
$data = $cloner->cloneVar($data, $filter)->withRefHandles(false); $dumper->dump($cloner->cloneVar($data)->withRefHandles(false));
if (null !== $key && null === $data = $data->seek($key)) { $data = stream_get_contents($h, -1, 0);
return null; fclose($h);
}
return rtrim($dumper->dump($data, true)); return rtrim($data);
}
private function prepareExpectation($expected, int $filter): string
{
if (!\is_string($expected)) {
$expected = $this->getDump($expected, null, $filter);
}
return rtrim($expected);
} }
} }

View File

@ -0,0 +1,180 @@
<?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 Symfony\Component\VarDumper\Caster\Caster;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class CasterTest extends \PHPUnit_Framework_TestCase
{
use VarDumperTestTrait;
private $referenceArray = array(
'null' => null,
'empty' => false,
'public' => 'pub',
"\0~\0virtual" => 'virt',
"\0+\0dynamic" => 'dyn',
"\0*\0protected" => 'prot',
"\0Foo\0private" => 'priv',
);
/**
* @dataProvider provideFilter
*/
public function testFilter($filter, $expectedDiff, $listedProperties = null)
{
if (null === $listedProperties) {
$filteredArray = Caster::filter($this->referenceArray, $filter);
} else {
$filteredArray = Caster::filter($this->referenceArray, $filter, $listedProperties);
}
$this->assertSame($expectedDiff, array_diff_assoc($this->referenceArray, $filteredArray));
}
public function provideFilter()
{
return array(
array(
0,
array(),
),
array(
Caster::EXCLUDE_PUBLIC,
array(
'null' => null,
'empty' => false,
'public' => 'pub',
),
),
array(
Caster::EXCLUDE_NULL,
array(
'null' => null,
),
),
array(
Caster::EXCLUDE_EMPTY,
array(
'null' => null,
'empty' => false,
),
),
array(
Caster::EXCLUDE_VIRTUAL,
array(
"\0~\0virtual" => 'virt',
),
),
array(
Caster::EXCLUDE_DYNAMIC,
array(
"\0+\0dynamic" => 'dyn',
),
),
array(
Caster::EXCLUDE_PROTECTED,
array(
"\0*\0protected" => 'prot',
),
),
array(
Caster::EXCLUDE_PRIVATE,
array(
"\0Foo\0private" => 'priv',
),
),
array(
Caster::EXCLUDE_VERBOSE,
array(
'public' => 'pub',
"\0*\0protected" => 'prot',
),
array('public', "\0*\0protected"),
),
array(
Caster::EXCLUDE_NOT_IMPORTANT,
array(
'null' => null,
'empty' => false,
"\0~\0virtual" => 'virt',
"\0+\0dynamic" => 'dyn',
"\0Foo\0private" => 'priv',
),
array('public', "\0*\0protected"),
),
array(
Caster::EXCLUDE_VIRTUAL | Caster::EXCLUDE_DYNAMIC,
array(
"\0~\0virtual" => 'virt',
"\0+\0dynamic" => 'dyn',
),
),
array(
Caster::EXCLUDE_NOT_IMPORTANT | Caster::EXCLUDE_VERBOSE,
$this->referenceArray,
array('public', "\0*\0protected"),
),
array(
Caster::EXCLUDE_NOT_IMPORTANT | Caster::EXCLUDE_EMPTY,
array(
'null' => null,
'empty' => false,
"\0~\0virtual" => 'virt',
"\0+\0dynamic" => 'dyn',
"\0*\0protected" => 'prot',
"\0Foo\0private" => 'priv',
),
array('public', 'empty'),
),
array(
Caster::EXCLUDE_VERBOSE | Caster::EXCLUDE_EMPTY | Caster::EXCLUDE_STRICT,
array(
'empty' => false,
),
array('public', 'empty'),
),
);
}
/**
* @requires PHP 7.0
*/
public function testAnonymousClass()
{
$c = eval('return new class extends stdClass { private $foo = "foo"; };');
$this->assertDumpMatchesFormat(
<<<'EOTXT'
stdClass@anonymous {
-foo: "foo"
}
EOTXT
, $c
);
$c = eval('return new class { private $foo = "foo"; };');
$this->assertDumpMatchesFormat(
<<<'EOTXT'
@anonymous {
-foo: "foo"
}
EOTXT
, $c
);
}
}

View File

@ -0,0 +1,57 @@
<?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 Symfony\Component\VarDumper\Caster\PdoCaster;
use Symfony\Component\VarDumper\Cloner\Stub;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class PdoCasterTest extends \PHPUnit_Framework_TestCase
{
/**
* @requires extension pdo_sqlite
*/
public function testCastPdo()
{
$pdo = new \PDO('sqlite::memory:');
$pdo->setAttribute(\PDO::ATTR_STATEMENT_CLASS, array('PDOStatement', array($pdo)));
$cast = PdoCaster::castPdo($pdo, array(), new Stub(), false);
$this->assertInstanceOf('Symfony\Component\VarDumper\Caster\EnumStub', $cast["\0~\0attributes"]);
$attr = $cast["\0~\0attributes"] = $cast["\0~\0attributes"]->value;
$this->assertInstanceOf('Symfony\Component\VarDumper\Caster\ConstStub', $attr['CASE']);
$this->assertSame('NATURAL', $attr['CASE']->class);
$this->assertSame('BOTH', $attr['DEFAULT_FETCH_MODE']->class);
$xCast = array(
"\0~\0inTransaction" => $pdo->inTransaction(),
"\0~\0attributes" => array(
'CASE' => $attr['CASE'],
'ERRMODE' => $attr['ERRMODE'],
'PERSISTENT' => false,
'DRIVER_NAME' => 'sqlite',
'ORACLE_NULLS' => $attr['ORACLE_NULLS'],
'CLIENT_VERSION' => $pdo->getAttribute(\PDO::ATTR_CLIENT_VERSION),
'SERVER_VERSION' => $pdo->getAttribute(\PDO::ATTR_SERVER_VERSION),
'STATEMENT_CLASS' => array('PDOStatement'),
'DEFAULT_FETCH_MODE' => $attr['DEFAULT_FETCH_MODE'],
),
);
unset($cast["\0~\0attributes"]['STATEMENT_CLASS'][1]);
$this->assertSame($xCast, $cast);
}
}

View File

@ -0,0 +1,228 @@
<?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 Symfony\Component\VarDumper\Test\VarDumperTestTrait;
use Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class ReflectionCasterTest extends \PHPUnit_Framework_TestCase
{
use VarDumperTestTrait;
public function testReflectionCaster()
{
$var = new \ReflectionClass('ReflectionClass');
$this->assertDumpMatchesFormat(
<<<'EOTXT'
ReflectionClass {
+name: "ReflectionClass"
%Aimplements: array:%d [
0 => "Reflector"
%A]
constants: array:3 [
"IS_IMPLICIT_ABSTRACT" => 16
"IS_EXPLICIT_ABSTRACT" => 32
"IS_FINAL" => %d
]
properties: array:%d [
"name" => ReflectionProperty {
%A +name: "name"
+class: "ReflectionClass"
%A modifiers: "public"
}
%A]
methods: array:%d [
%A
"export" => ReflectionMethod {
+name: "export"
+class: "ReflectionClass"
parameters: {
$%s: ReflectionParameter {
%A position: 0
%A
}
EOTXT
, $var
);
}
public function testClosureCaster()
{
$a = $b = 123;
$var = function ($x) use ($a, &$b) {};
$this->assertDumpMatchesFormat(
<<<EOTXT
Closure {
%Aparameters: {
\$x: {}
}
use: {
\$a: 123
\$b: & 123
}
file: "%sReflectionCasterTest.php"
line: "65 to 65"
}
EOTXT
, $var
);
}
public function testReflectionParameter()
{
$var = new \ReflectionParameter(__NAMESPACE__.'\reflectionParameterFixture', 0);
$this->assertDumpMatchesFormat(
<<<'EOTXT'
ReflectionParameter {
+name: "arg1"
position: 0
typeHint: "Symfony\Component\VarDumper\Tests\Caster\NotExistingClass"
default: null
}
EOTXT
, $var
);
}
/**
* @requires PHP 7.0
*/
public function testReflectionParameterScalar()
{
$f = eval('return function (int $a) {};');
$var = new \ReflectionParameter($f, 0);
$this->assertDumpMatchesFormat(
<<<'EOTXT'
ReflectionParameter {
+name: "a"
position: 0
typeHint: "int"
}
EOTXT
, $var
);
}
/**
* @requires PHP 7.0
*/
public function testReturnType()
{
$f = eval('return function ():int {};');
$line = __LINE__ - 1;
$this->assertDumpMatchesFormat(
<<<EOTXT
Closure {
returnType: "int"
class: "Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest"
this: Symfony\Component\VarDumper\Tests\Caster\ReflectionCasterTest { …}
file: "%sReflectionCasterTest.php($line) : eval()'d code"
line: "1 to 1"
}
EOTXT
, $f
);
}
/**
* @requires PHP 7.0
*/
public function testGenerator()
{
$g = new GeneratorDemo();
$g = $g->baz();
$r = new \ReflectionGenerator($g);
$xDump = <<<'EODUMP'
Generator {
this: Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo { …}
executing: {
Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo->baz(): {
%sGeneratorDemo.php:14: """
{\n
yield from bar();\n
}\n
"""
}
}
}
EODUMP;
$this->assertDumpMatchesFormat($xDump, $g);
foreach ($g as $v) {
break;
}
$xDump = <<<'EODUMP'
array:2 [
0 => ReflectionGenerator {
this: Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo { …}
trace: {
3. Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo::foo() ==> yield(): {
src: {
%sGeneratorDemo.php:9: """
{\n
yield 1;\n
}\n
"""
}
}
2. Symfony\Component\VarDumper\Tests\Fixtures\bar() ==> Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo::foo(): {
src: {
%sGeneratorDemo.php:20: """
{\n
yield from GeneratorDemo::foo();\n
}\n
"""
}
}
1. Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo->baz() ==> Symfony\Component\VarDumper\Tests\Fixtures\bar(): {
src: {
%sGeneratorDemo.php:14: """
{\n
yield from bar();\n
}\n
"""
}
}
}
}
1 => Generator {
executing: {
Symfony\Component\VarDumper\Tests\Fixtures\GeneratorDemo::foo(): {
%sGeneratorDemo.php:10: """
yield 1;\n
}\n
\n
"""
}
}
}
]
EODUMP;
$this->assertDumpMatchesFormat($xDump, array($r, $r->getExecutingGenerator()));
}
}
function reflectionParameterFixture(NotExistingClass $arg1 = null, $arg2)
{
}

View File

@ -0,0 +1,126 @@
<?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 Symfony\Component\VarDumper\Test\VarDumperTestTrait;
/**
* @author Grégoire Pineau <lyrixx@lyrixx.info>
*/
class SplCasterTest extends \PHPUnit_Framework_TestCase
{
use VarDumperTestTrait;
public function getCastFileInfoTests()
{
return array(
array(__FILE__, <<<'EOTXT'
SplFileInfo {
%Apath: "%sCaster"
filename: "SplCasterTest.php"
basename: "SplCasterTest.php"
pathname: "%sSplCasterTest.php"
extension: "php"
realPath: "%sSplCasterTest.php"
aTime: %s-%s-%d %d:%d:%d
mTime: %s-%s-%d %d:%d:%d
cTime: %s-%s-%d %d:%d:%d
inode: %d
size: %d
perms: 0%d
owner: %d
group: %d
type: "file"
writable: true
readable: true
executable: false
file: true
dir: false
link: false
%A}
EOTXT
),
array('https://google.com/about', <<<'EOTXT'
SplFileInfo {
%Apath: "https://google.com"
filename: "about"
basename: "about"
pathname: "https://google.com/about"
extension: ""
realPath: false
writable: false
readable: false
executable: false
file: false
dir: false
link: false
%A}
EOTXT
),
);
}
/** @dataProvider getCastFileInfoTests */
public function testCastFileInfo($file, $dump)
{
$this->assertDumpMatchesFormat($dump, new \SplFileInfo($file));
}
public function testCastFileObject()
{
$var = new \SplFileObject(__FILE__);
$var->setFlags(\SplFileObject::DROP_NEW_LINE | \SplFileObject::SKIP_EMPTY);
$dump = <<<'EOTXT'
SplFileObject {
%Apath: "%sCaster"
filename: "SplCasterTest.php"
basename: "SplCasterTest.php"
pathname: "%sSplCasterTest.php"
extension: "php"
realPath: "%sSplCasterTest.php"
aTime: %s-%s-%d %d:%d:%d
mTime: %s-%s-%d %d:%d:%d
cTime: %s-%s-%d %d:%d:%d
inode: %d
size: %d
perms: 0%d
owner: %d
group: %d
type: "file"
writable: true
readable: true
executable: false
file: true
dir: false
link: false
%AcsvControl: array:2 [
0 => ","
1 => """
]
flags: DROP_NEW_LINE|SKIP_EMPTY
maxLineLen: 0
fstat: array:26 [
"dev" => %d
"ino" => %d
"nlink" => %d
"rdev" => 0
"blksize" => %i
"blocks" => %i
…20
]
eof: false
key: 0
}
EOTXT;
$this->assertDumpMatchesFormat($dump, $var);
}
}

View File

@ -0,0 +1,441 @@
<?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;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\CliDumper;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class CliDumperTest extends \PHPUnit_Framework_TestCase
{
use VarDumperTestTrait;
public function testGet()
{
require __DIR__.'/Fixtures/dumb-var.php';
$dumper = new CliDumper('php://output');
$dumper->setColors(false);
$cloner = new VarCloner();
$cloner->addCasters(array(
':stream' => function ($res, $a) {
unset($a['uri'], $a['wrapper_data']);
return $a;
},
));
$data = $cloner->cloneVar($var);
ob_start();
$dumper->dump($data);
$out = ob_get_clean();
$out = preg_replace('/[ \t]+$/m', '', $out);
$intMax = PHP_INT_MAX;
$res = (int) $var['res'];
$r = defined('HHVM_VERSION') ? '' : '#%d';
$this->assertStringMatchesFormat(
<<<EOTXT
array:24 [
"number" => 1
0 => &1 null
"const" => 1.1
1 => true
2 => false
3 => NAN
4 => INF
5 => -INF
6 => {$intMax}
"str" => "déjà\\n"
7 => b"é\\x00"
"[]" => []
"res" => stream resource {@{$res}
wrapper_type: "plainfile"
stream_type: "STDIO"
mode: "r"
unread_bytes: 0
seekable: true
timed_out: false
blocked: true
eof: false
options: []
}
"obj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {#%d
+foo: "foo"
+"bar": "bar"
}
"closure" => Closure {{$r}
class: "Symfony\Component\VarDumper\Tests\CliDumperTest"
this: Symfony\Component\VarDumper\Tests\CliDumperTest {{$r} …}
parameters: {
\$a: {}
&\$b: {
typeHint: "PDO"
default: null
}
}
file: "{$var['file']}"
line: "{$var['line']} to {$var['line']}"
}
"line" => {$var['line']}
"nobj" => array:1 [
0 => &3 {#%d}
]
"recurs" => &4 array:1 [
0 => &4 array:1 [&4]
]
8 => &1 null
"sobj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {#%d}
"snobj" => &3 {#%d}
"snobj2" => {#%d}
"file" => "{$var['file']}"
b"bin-key-é" => ""
]
EOTXT
,
$out
);
}
/**
* @requires extension xml
*/
public function testXmlResource()
{
$var = xml_parser_create();
$this->assertDumpMatchesFormat(
<<<EOTXT
xml resource {
current_byte_index: %i
current_column_number: %i
current_line_number: 1
error_code: XML_ERROR_NONE
}
EOTXT
,
$var
);
}
public function testClosedResource()
{
if (defined('HHVM_VERSION') && HHVM_VERSION_ID < 30600) {
$this->markTestSkipped();
}
$var = fopen(__FILE__, 'r');
fclose($var);
$dumper = new CliDumper('php://output');
$dumper->setColors(false);
$cloner = new VarCloner();
$data = $cloner->cloneVar($var);
ob_start();
$dumper->dump($data);
$out = ob_get_clean();
$res = (int) $var;
$this->assertStringMatchesFormat(
<<<EOTXT
Unknown resource @{$res}
EOTXT
,
$out
);
}
public function testThrowingCaster()
{
$out = fopen('php://memory', 'r+b');
require_once __DIR__.'/Fixtures/Twig.php';
$twig = new \__TwigTemplate_VarDumperFixture_u75a09(new \Twig_Environment(new \Twig_Loader_Filesystem()));
$dumper = new CliDumper();
$dumper->setColors(false);
$cloner = new VarCloner();
$cloner->addCasters(array(
':stream' => function ($res, $a) {
unset($a['wrapper_data']);
return $a;
},
));
$cloner->addCasters(array(
':stream' => eval('return function () use ($twig) {
try {
$twig->render(array());
} catch (\Twig_Error_Runtime $e) {
throw $e->getPrevious();
}
};'),
));
$line = __LINE__ - 2;
$ref = (int) $out;
$data = $cloner->cloneVar($out);
$dumper->dump($data, $out);
rewind($out);
$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';
$this->assertStringMatchesFormat(
<<<EOTXT
stream resource {@{$ref}
wrapper_type: "PHP"
stream_type: "MEMORY"
mode: "%s+b"
unread_bytes: 0
seekable: true
uri: "php://memory"
timed_out: false
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
,
$out
);
}
public function testRefsInProperties()
{
$var = (object) array('foo' => 'foo');
$var->bar = &$var->foo;
$dumper = new CliDumper();
$dumper->setColors(false);
$cloner = new VarCloner();
$out = fopen('php://memory', 'r+b');
$data = $cloner->cloneVar($var);
$dumper->dump($data, $out);
rewind($out);
$out = stream_get_contents($out);
$r = defined('HHVM_VERSION') ? '' : '#%d';
$this->assertStringMatchesFormat(
<<<EOTXT
{{$r}
+"foo": &1 "foo"
+"bar": &1 "foo"
}
EOTXT
,
$out
);
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
* @requires PHP 5.6
*/
public function testSpecialVars56()
{
$var = $this->getSpecialVars();
$this->assertDumpEquals(
<<<EOTXT
array:3 [
0 => array:1 [
0 => &1 array:1 [
0 => &1 array:1 [&1]
]
]
1 => array:1 [
"GLOBALS" => &2 array:1 [
"GLOBALS" => &2 array:1 [&2]
]
]
2 => &2 array:1 [&2]
]
EOTXT
,
$var
);
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testGlobalsNoExt()
{
$var = $this->getSpecialVars();
unset($var[0]);
$out = '';
$dumper = new CliDumper(function ($line, $depth) use (&$out) {
if ($depth >= 0) {
$out .= str_repeat(' ', $depth).$line."\n";
}
});
$dumper->setColors(false);
$cloner = new VarCloner();
$refl = new \ReflectionProperty($cloner, 'useExt');
$refl->setAccessible(true);
$refl->setValue($cloner, false);
$data = $cloner->cloneVar($var);
$dumper->dump($data);
$this->assertSame(
<<<EOTXT
array:2 [
1 => array:1 [
"GLOBALS" => &1 array:1 [
"GLOBALS" => &1 array:1 [&1]
]
]
2 => &1 array:1 [&1]
]
EOTXT
,
$out
);
}
/**
* @runInSeparateProcess
* @preserveGlobalState disabled
*/
public function testBuggyRefs()
{
if (PHP_VERSION_ID >= 50600) {
$this->markTestSkipped('PHP 5.6 fixed refs counting');
}
$var = $this->getSpecialVars();
$var = $var[0];
$dumper = new CliDumper();
$dumper->setColors(false);
$cloner = new VarCloner();
$data = $cloner->cloneVar($var)->withMaxDepth(3);
$out = '';
$dumper->dump($data, function ($line, $depth) use (&$out) {
if ($depth >= 0) {
$out .= str_repeat(' ', $depth).$line."\n";
}
});
$this->assertSame(
<<<EOTXT
array:1 [
0 => array:1 [
0 => array:1 [
0 => array:1 [ …1]
]
]
]
EOTXT
,
$out
);
}
private function getSpecialVars()
{
foreach (array_keys($GLOBALS) as $var) {
if ('GLOBALS' !== $var) {
unset($GLOBALS[$var]);
}
}
$var = function &() {
$var = array();
$var[] = &$var;
return $var;
};
return array($var(), $GLOBALS, &$GLOBALS);
}
}

View File

@ -0,0 +1,21 @@
<?php
namespace Symfony\Component\VarDumper\Tests\Fixtures;
class GeneratorDemo
{
public static function foo()
{
yield 1;
}
public function baz()
{
yield from bar();
}
}
function bar()
{
yield from GeneratorDemo::foo();
}

View File

@ -0,0 +1,34 @@
<?php
/* foo.twig */
class __TwigTemplate_VarDumperFixture_u75a09 extends Twig_Template
{
public function __construct(Twig_Environment $env)
{
parent::__construct($env);
$this->parent = false;
$this->blocks = array(
);
}
protected function doDisplay(array $context, array $blocks = array())
{
// line 2
throw new \Exception('Foobar');
}
public function getTemplateName()
{
return 'foo.twig';
}
public function getDebugInfo()
{
return array (19 => 2);
}
}
/* foo bar*/
/* twig source*/
/* */

View File

@ -0,0 +1,40 @@
<?php
namespace Symfony\Component\VarDumper\Tests\Fixture;
if (!class_exists('Symfony\Component\VarDumper\Tests\Fixture\DumbFoo')) {
class DumbFoo
{
public $foo = 'foo';
}
}
$foo = new DumbFoo();
$foo->bar = 'bar';
$g = fopen(__FILE__, 'r');
$var = array(
'number' => 1, null,
'const' => 1.1, true, false, NAN, INF, -INF, PHP_INT_MAX,
'str' => "déjà\n", "\xE9\x00",
'[]' => array(),
'res' => $g,
'obj' => $foo,
'closure' => function ($a, \PDO &$b = null) {},
'line' => __LINE__ - 1,
'nobj' => array((object) array()),
);
$r = array();
$r[] = &$r;
$var['recurs'] = &$r;
$var[] = &$var[0];
$var['sobj'] = $var['obj'];
$var['snobj'] = &$var['nobj'][0];
$var['snobj2'] = $var['nobj'][0];
$var['file'] = __FILE__;
$var["bin-key-\xE9"] = '';
unset($g, $r);

View File

@ -0,0 +1,142 @@
<?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;
use Symfony\Component\VarDumper\Cloner\VarCloner;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class HtmlDumperTest extends \PHPUnit_Framework_TestCase
{
public function testGet()
{
require __DIR__.'/Fixtures/dumb-var.php';
$dumper = new HtmlDumper('php://output');
$dumper->setDumpHeader('<foo></foo>');
$dumper->setDumpBoundaries('<bar>', '</bar>');
$cloner = new VarCloner();
$cloner->addCasters(array(
':stream' => function ($res, $a) {
unset($a['uri'], $a['wrapper_data']);
return $a;
},
));
$data = $cloner->cloneVar($var);
ob_start();
$dumper->dump($data);
$out = ob_get_clean();
$out = preg_replace('/[ \t]+$/m', '', $out);
$var['file'] = htmlspecialchars($var['file'], ENT_QUOTES, 'UTF-8');
$intMax = PHP_INT_MAX;
preg_match('/sf-dump-\d+/', $out, $dumpId);
$dumpId = $dumpId[0];
$res = (int) $var['res'];
$r = defined('HHVM_VERSION') ? '' : '<a class=sf-dump-ref>#%d</a>';
$this->assertStringMatchesFormat(
<<<EOTXT
<foo></foo><bar><span class=sf-dump-note>array:24</span> [<samp>
"<span class=sf-dump-key>number</span>" => <span class=sf-dump-num>1</span>
<span class=sf-dump-key>0</span> => <a class=sf-dump-ref href=#{$dumpId}-ref01 title="2 occurrences">&amp;1</a> <span class=sf-dump-const>null</span>
"<span class=sf-dump-key>const</span>" => <span class=sf-dump-num>1.1</span>
<span class=sf-dump-key>1</span> => <span class=sf-dump-const>true</span>
<span class=sf-dump-key>2</span> => <span class=sf-dump-const>false</span>
<span class=sf-dump-key>3</span> => <span class=sf-dump-num>NAN</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>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>7</span> => b"<span class=sf-dump-str title="2 binary or non-UTF-8 characters">&%s;</span>\\x00"
"<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-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>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>seekable</span>: <span class=sf-dump-const>true</span>
<span class=sf-dump-meta>timed_out</span>: <span class=sf-dump-const>false</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>}
"<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="Runtime added dynamic property">bar</span>": "<span class=sf-dump-str title="3 characters">bar</span>"
</samp>}
"<span class=sf-dump-key>closure</span>" => <span class=sf-dump-note>Closure</span> {{$r}<samp>
<span class=sf-dump-meta>class</span>: "<span class=sf-dump-str title="48 characters">Symfony\Component\VarDumper\Tests\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>parameters</span>: {<samp>
<span class=sf-dump-meta>\$a</span>: {}
<span class=sf-dump-meta>&amp;\$b</span>: {<samp>
<span class=sf-dump-meta>typeHint</span>: "<span class=sf-dump-str title="3 characters">PDO</span>"
<span class=sf-dump-meta>default</span>: <span class=sf-dump-const>null</span>
</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>line</span>: "<span class=sf-dump-str title="%d characters">{$var['line']} to {$var['line']}</span>"
</samp>}
"<span class=sf-dump-key>line</span>" => <span class=sf-dump-num>{$var['line']}</span>
"<span class=sf-dump-key>nobj</span>" => <span class=sf-dump-note>array:1</span> [<samp>
<span class=sf-dump-index>0</span> => <a class=sf-dump-ref href=#{$dumpId}-ref03 title="2 occurrences">&amp;3</a> {<a class=sf-dump-ref href=#{$dumpId}-ref2%d title="3 occurrences">#%d</a>}
</samp>]
"<span class=sf-dump-key>recurs</span>" => <a class=sf-dump-ref href=#{$dumpId}-ref04 title="2 occurrences">&amp;4</a> <span class=sf-dump-note>array:1</span> [<samp id={$dumpId}-ref04>
<span class=sf-dump-index>0</span> => <a class=sf-dump-ref href=#{$dumpId}-ref04 title="2 occurrences">&amp;4</a> <span class=sf-dump-note>array:1</span> [<a class=sf-dump-ref href=#{$dumpId}-ref04 title="2 occurrences">&amp;4</a>]
</samp>]
<span class=sf-dump-key>8</span> => <a class=sf-dump-ref href=#{$dumpId}-ref01 title="2 occurrences">&amp;1</a> <span class=sf-dump-const>null</span>
"<span class=sf-dump-key>sobj</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>}
"<span class=sf-dump-key>snobj</span>" => <a class=sf-dump-ref href=#{$dumpId}-ref03 title="2 occurrences">&amp;3</a> {<a class=sf-dump-ref href=#{$dumpId}-ref2%d title="3 occurrences">#%d</a>}
"<span class=sf-dump-key>snobj2</span>" => {<a class=sf-dump-ref href=#{$dumpId}-ref2%d title="3 occurrences">#%d</a>}
"<span class=sf-dump-key>file</span>" => "<span class=sf-dump-str title="%d characters">{$var['file']}</span>"
b"<span class=sf-dump-key>bin-key-&%s;</span>" => ""
</samp>]
</bar>
EOTXT
,
$out
);
}
public function testCharset()
{
$var = mb_convert_encoding('Словарь', 'CP1251', 'UTF-8');
$dumper = new HtmlDumper('php://output', 'CP1251');
$dumper->setDumpHeader('<foo></foo>');
$dumper->setDumpBoundaries('<bar>', '</bar>');
$cloner = new VarCloner();
$data = $cloner->cloneVar($var);
$out = fopen('php://memory', 'r+b');
$dumper->dump($data, $out);
rewind($out);
$out = stream_get_contents($out);
$this->assertStringMatchesFormat(
<<<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>"
</bar>
EOTXT
,
$out
);
}
}

View File

@ -0,0 +1,40 @@
<?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\Test;
use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
class VarDumperTestTraitTest extends \PHPUnit_Framework_TestCase
{
use VarDumperTestTrait;
public function testItComparesLargeData()
{
$howMany = 700;
$data = array_fill_keys(range(0, $howMany), array('a', 'b', 'c', 'd'));
$expected = sprintf("array:%d [\n", $howMany + 1);
for ($i = 0; $i <= $howMany; ++$i) {
$expected .= <<<EODUMP
$i => array:4 [
0 => "a"
1 => "b"
2 => "c"
3 => "d"
]\n
EODUMP;
}
$expected .= "]\n";
$this->assertDumpEquals($expected, $data);
}
}

View File

@ -0,0 +1,187 @@
<?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;
use Symfony\Component\VarDumper\Cloner\VarCloner;
/**
* @author Nicolas Grekas <p@tchwork.com>
*/
class VarClonerTest extends \PHPUnit_Framework_TestCase
{
public function testMaxIntBoundary()
{
$data = array(PHP_INT_MAX => 123);
$cloner = new VarCloner();
$clone = $cloner->cloneVar($data);
$expected = <<<EOTXT
Symfony\Component\VarDumper\Cloner\Data Object
(
[data:Symfony\Component\VarDumper\Cloner\Data:private] => Array
(
[0] => Array
(
[0] => Symfony\Component\VarDumper\Cloner\Stub Object
(
[type] => array
[class] => assoc
[value] => 1
[cut] => 0
[handle] => 0
[refCount] => 0
[position] => 1
)
)
[1] => Array
(
[%s] => 123
)
)
[maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
[maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
[useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
)
EOTXT;
$this->assertSame(sprintf($expected, PHP_INT_MAX), print_r($clone, true));
}
public function testClone()
{
$json = json_decode('{"1":{"var":"val"},"2":{"var":"val"}}');
$cloner = new VarCloner();
$clone = $cloner->cloneVar($json);
$expected = <<<EOTXT
Symfony\Component\VarDumper\Cloner\Data Object
(
[data:Symfony\Component\VarDumper\Cloner\Data:private] => Array
(
[0] => Array
(
[0] => Symfony\Component\VarDumper\Cloner\Stub Object
(
[type] => object
[class] => stdClass
[value] =>
[cut] => 0
[handle] => %i
[refCount] => 0
[position] => 1
)
)
[1] => Array
(
[\000+\0001] => Symfony\Component\VarDumper\Cloner\Stub Object
(
[type] => object
[class] => stdClass
[value] =>
[cut] => 0
[handle] => %i
[refCount] => 0
[position] => 2
)
[\000+\0002] => Symfony\Component\VarDumper\Cloner\Stub Object
(
[type] => object
[class] => stdClass
[value] =>
[cut] => 0
[handle] => %i
[refCount] => 0
[position] => 3
)
)
[2] => Array
(
[\000+\000var] => val
)
[3] => Array
(
[\000+\000var] => val
)
)
[maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
[maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
[useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
)
EOTXT;
$this->assertStringMatchesFormat($expected, print_r($clone, true));
}
public function testCaster()
{
$cloner = new VarCloner(array(
'*' => function ($obj, $array) {
return array('foo' => 123);
},
__CLASS__ => function ($obj, $array) {
++$array['foo'];
return $array;
},
));
$clone = $cloner->cloneVar($this);
$expected = <<<EOTXT
Symfony\Component\VarDumper\Cloner\Data Object
(
[data:Symfony\Component\VarDumper\Cloner\Data:private] => Array
(
[0] => Array
(
[0] => Symfony\Component\VarDumper\Cloner\Stub Object
(
[type] => object
[class] => %s
[value] =>
[cut] => 0
[handle] => %i
[refCount] => 0
[position] => 1
)
)
[1] => Array
(
[foo] => 124
)
)
[maxDepth:Symfony\Component\VarDumper\Cloner\Data:private] => 20
[maxItemsPerDepth:Symfony\Component\VarDumper\Cloner\Data:private] => -1
[useRefHandles:Symfony\Component\VarDumper\Cloner\Data:private] => -1
)
EOTXT;
$this->assertStringMatchesFormat($expected, print_r($clone, true));
}
}

View File

@ -11,11 +11,8 @@
namespace Symfony\Component\VarDumper; namespace Symfony\Component\VarDumper;
use Symfony\Component\VarDumper\Caster\ReflectionCaster;
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\Dumper\ContextProvider\SourceContextProvider;
use Symfony\Component\VarDumper\Dumper\ContextualizedDumper;
use Symfony\Component\VarDumper\Dumper\HtmlDumper; use Symfony\Component\VarDumper\Dumper\HtmlDumper;
// Load the global dump() function // Load the global dump() function
@ -32,33 +29,18 @@ class VarDumper
{ {
if (null === self::$handler) { if (null === self::$handler) {
$cloner = new VarCloner(); $cloner = new VarCloner();
$cloner->addCasters(ReflectionCaster::UNSET_CLOSURE_FILE_INFO); $dumper = 'cli' === PHP_SAPI ? new CliDumper() : new HtmlDumper();
if (isset($_SERVER['VAR_DUMPER_FORMAT'])) {
$dumper = 'html' === $_SERVER['VAR_DUMPER_FORMAT'] ? new HtmlDumper() : new CliDumper();
} else {
$dumper = \in_array(\PHP_SAPI, ['cli', 'phpdbg']) ? new CliDumper() : new HtmlDumper();
}
$dumper = new ContextualizedDumper($dumper, [new SourceContextProvider()]);
self::$handler = function ($var) use ($cloner, $dumper) { self::$handler = function ($var) use ($cloner, $dumper) {
$dumper->dump($cloner->cloneVar($var)); $dumper->dump($cloner->cloneVar($var));
}; };
} }
return (self::$handler)($var); return call_user_func(self::$handler, $var);
} }
public static function setHandler(callable $callable = null) public static function setHandler(callable $callable = null)
{ {
$prevHandler = self::$handler; $prevHandler = self::$handler;
// Prevent replacing the handler with expected format as soon as the env var was set:
if (isset($_SERVER['VAR_DUMPER_FORMAT'])) {
return $prevHandler;
}
self::$handler = $callable; self::$handler = $callable;
return $prevHandler; return $prevHandler;

View File

@ -1,55 +1,44 @@
{ {
"name": "symfony/var-dumper", "name" : "symfony/var-dumper",
"type": "library", "type" : "library",
"description": "Symfony mechanism for exploring and dumping PHP variables", "description" : "Symfony mechanism for exploring and dumping PHP variables",
"keywords": ["dump", "debug"], "keywords" : [
"homepage": "https://symfony.com", "dump",
"license": "MIT", "debug"
"authors": [ ],
{ "homepage" : "https://symfony.com",
"name": "Nicolas Grekas", "license" : "MIT",
"email": "p@tchwork.com" "authors" : [{
}, "name" : "Nicolas Grekas",
{ "email" : "p@tchwork.com"
"name": "Symfony Community", }, {
"homepage": "https://symfony.com/contributors" "name" : "Symfony Community",
} "homepage" : "https://symfony.com/contributors"
], }
"require": { ],
"php": ">=7.1.3", "require" : {
"symfony/polyfill-mbstring": "~1.0", "php" : ">=5.5.9",
"symfony/polyfill-php72": "~1.5", "symfony/polyfill-mbstring" : "~1.0"
"symfony/polyfill-php80": "^1.15" },
}, "require-dev" : {
"require-dev": { "twig/twig" : "~1.20|~2.0"
"ext-iconv": "*", },
"symfony/console": "^3.4|^4.0|^5.0", "suggest" : {},
"symfony/process": "^4.4|^5.0", "autoload" : {
"twig/twig": "^1.34|^2.4|^3.0" "files" : [
}, "Resources/functions/dump.php"
"conflict": { ],
"phpunit/phpunit": "<4.8.35|<5.4.3,>=5.0", "psr-4" : {
"symfony/console": "<3.4" "Symfony\\Component\\VarDumper\\" : ""
}, },
"suggest": { "exclude-from-classmap" : [
"ext-iconv": "To convert non-UTF-8 strings to UTF-8 (or symfony/polyfill-iconv in case ext-iconv cannot be used).", "/Tests/"
"ext-intl": "To show region name in time zone dump", ]
"symfony/console": "To use the ServerDumpCommand and/or the bin/var-dump-server script" },
}, "minimum-stability" : "dev",
"autoload": { "extra" : {
"files": [ "Resources/functions/dump.php" ], "branch-alias" : {
"psr-4": { "Symfony\\Component\\VarDumper\\": "" }, "dev-master" : "3.0-dev"
"exclude-from-classmap": [ }
"/Tests/" }
]
},
"bin": [
"Resources/bin/var-dump-server"
],
"minimum-stability": "dev",
"extra": {
"branch-alias": {
"dev-master": "4.4-dev"
}
}
} }