From eec5c92c35012e2bb79c5dc6a498ff2942c79156 Mon Sep 17 00:00:00 2001 From: Julien Pauli Date: Fri, 7 Mar 2014 11:14:00 +0100 Subject: [PATCH 01/31] [Debug] Symfony debug extension --- .../Debug/Resources/ext/ext/config.m4 | 63 +++++ .../Debug/Resources/ext/ext/config.w32 | 13 + .../Resources/ext/ext/php_symfony_debug.h | 55 +++++ .../Debug/Resources/ext/ext/symfony_debug.c | 223 ++++++++++++++++++ .../Debug/Resources/ext/ext/tests/001.phpt | 149 ++++++++++++ 5 files changed, 503 insertions(+) create mode 100644 src/Symfony/Component/Debug/Resources/ext/ext/config.m4 create mode 100644 src/Symfony/Component/Debug/Resources/ext/ext/config.w32 create mode 100644 src/Symfony/Component/Debug/Resources/ext/ext/php_symfony_debug.h create mode 100644 src/Symfony/Component/Debug/Resources/ext/ext/symfony_debug.c create mode 100644 src/Symfony/Component/Debug/Resources/ext/ext/tests/001.phpt diff --git a/src/Symfony/Component/Debug/Resources/ext/ext/config.m4 b/src/Symfony/Component/Debug/Resources/ext/ext/config.m4 new file mode 100644 index 0000000000000..3c56047150569 --- /dev/null +++ b/src/Symfony/Component/Debug/Resources/ext/ext/config.m4 @@ -0,0 +1,63 @@ +dnl $Id$ +dnl config.m4 for extension symfony_debug + +dnl Comments in this file start with the string 'dnl'. +dnl Remove where necessary. This file will not work +dnl without editing. + +dnl If your extension references something external, use with: + +dnl PHP_ARG_WITH(symfony_debug, for symfony_debug support, +dnl Make sure that the comment is aligned: +dnl [ --with-symfony_debug Include symfony_debug support]) + +dnl Otherwise use enable: + +PHP_ARG_ENABLE(symfony_debug, whether to enable symfony_debug support, +dnl Make sure that the comment is aligned: +[ --enable-symfony_debug Enable symfony_debug support]) + +if test "$PHP_SYMFONY_DEBUG" != "no"; then + dnl Write more examples of tests here... + + dnl # --with-symfony_debug -> check with-path + dnl SEARCH_PATH="/usr/local /usr" # you might want to change this + dnl SEARCH_FOR="/include/symfony_debug.h" # you most likely want to change this + dnl if test -r $PHP_SYMFONY_DEBUG/$SEARCH_FOR; then # path given as parameter + dnl SYMFONY_DEBUG_DIR=$PHP_SYMFONY_DEBUG + dnl else # search default path list + dnl AC_MSG_CHECKING([for symfony_debug files in default path]) + dnl for i in $SEARCH_PATH ; do + dnl if test -r $i/$SEARCH_FOR; then + dnl SYMFONY_DEBUG_DIR=$i + dnl AC_MSG_RESULT(found in $i) + dnl fi + dnl done + dnl fi + dnl + dnl if test -z "$SYMFONY_DEBUG_DIR"; then + dnl AC_MSG_RESULT([not found]) + dnl AC_MSG_ERROR([Please reinstall the symfony_debug distribution]) + dnl fi + + dnl # --with-symfony_debug -> add include path + dnl PHP_ADD_INCLUDE($SYMFONY_DEBUG_DIR/include) + + dnl # --with-symfony_debug -> check for lib and symbol presence + dnl LIBNAME=symfony_debug # you may want to change this + dnl LIBSYMBOL=symfony_debug # you most likely want to change this + + dnl PHP_CHECK_LIBRARY($LIBNAME,$LIBSYMBOL, + dnl [ + dnl PHP_ADD_LIBRARY_WITH_PATH($LIBNAME, $SYMFONY_DEBUG_DIR/lib, SYMFONY_DEBUG_SHARED_LIBADD) + dnl AC_DEFINE(HAVE_SYMFONY_DEBUGLIB,1,[ ]) + dnl ],[ + dnl AC_MSG_ERROR([wrong symfony_debug lib version or lib not found]) + dnl ],[ + dnl -L$SYMFONY_DEBUG_DIR/lib -lm + dnl ]) + dnl + dnl PHP_SUBST(SYMFONY_DEBUG_SHARED_LIBADD) + + PHP_NEW_EXTENSION(symfony_debug, symfony_debug.c, $ext_shared) +fi diff --git a/src/Symfony/Component/Debug/Resources/ext/ext/config.w32 b/src/Symfony/Component/Debug/Resources/ext/ext/config.w32 new file mode 100644 index 0000000000000..487e6913891cf --- /dev/null +++ b/src/Symfony/Component/Debug/Resources/ext/ext/config.w32 @@ -0,0 +1,13 @@ +// $Id$ +// vim:ft=javascript + +// If your extension references something external, use ARG_WITH +// ARG_WITH("symfony_debug", "for symfony_debug support", "no"); + +// Otherwise, use ARG_ENABLE +// ARG_ENABLE("symfony_debug", "enable symfony_debug support", "no"); + +if (PHP_SYMFONY_DEBUG != "no") { + EXTENSION("symfony_debug", "symfony_debug.c"); +} + diff --git a/src/Symfony/Component/Debug/Resources/ext/ext/php_symfony_debug.h b/src/Symfony/Component/Debug/Resources/ext/ext/php_symfony_debug.h new file mode 100644 index 0000000000000..c935f67019299 --- /dev/null +++ b/src/Symfony/Component/Debug/Resources/ext/ext/php_symfony_debug.h @@ -0,0 +1,55 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#ifndef PHP_SYMFONY_DEBUG_H +#define PHP_SYMFONY_DEBUG_H + +extern zend_module_entry symfony_debug_module_entry; +#define phpext_symfony_debug_ptr &symfony_debug_module_entry + +#define PHP_SYMFONY_DEBUG_VERSION "1.0" + +#ifdef PHP_WIN32 +# define PHP_SYMFONY_DEBUG_API __declspec(dllexport) +#elif defined(__GNUC__) && __GNUC__ >= 4 +# define PHP_SYMFONY_DEBUG_API __attribute__ ((visibility("default"))) +#else +# define PHP_SYMFONY_DEBUG_API +#endif + +#ifdef ZTS +#include "TSRM.h" +#endif + +ZEND_BEGIN_MODULE_GLOBALS(symfony_debug) + intptr_t req_rand_init; +ZEND_END_MODULE_GLOBALS(symfony_debug) + +PHP_MINIT_FUNCTION(symfony_debug); +PHP_MSHUTDOWN_FUNCTION(symfony_debug); +PHP_RINIT_FUNCTION(symfony_debug); +PHP_RSHUTDOWN_FUNCTION(symfony_debug); +PHP_MINFO_FUNCTION(symfony_debug); +PHP_GINIT_FUNCTION(symfony_debug); +PHP_GSHUTDOWN_FUNCTION(symfony_debug); + +PHP_FUNCTION(symfony_zval_info); + +static char *_symfony_debug_memory_address_hash(void *); +static const char *_symfony_debug_zval_type(zval *); +static const char* _symfony_debug_get_resource_type(long); +static int _symfony_debug_get_resource_refcount(long); + +#ifdef ZTS +#define SYMFONY_DEBUG_G(v) TSRMG(symfony_debug_globals_id, zend_symfony_debug_globals *, v) +#else +#define SYMFONY_DEBUG_G(v) (symfony_debug_globals.v) +#endif + +#endif /* PHP_SYMFONY_DEBUG_H */ diff --git a/src/Symfony/Component/Debug/Resources/ext/ext/symfony_debug.c b/src/Symfony/Component/Debug/Resources/ext/ext/symfony_debug.c new file mode 100644 index 0000000000000..2bcb922d5c98b --- /dev/null +++ b/src/Symfony/Component/Debug/Resources/ext/ext/symfony_debug.c @@ -0,0 +1,223 @@ +/* + * This file is part of the Symfony package. + * + * (c) Fabien Potencier + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +#ifdef HAVE_CONFIG_H +#include "config.h" +#endif + +#include "php.h" +#include "php_ini.h" +#include "ext/standard/info.h" +#include "php_symfony_debug.h" +#include "ext/standard/php_rand.h" +#include "ext/standard/php_lcg.h" +#include "ext/spl/php_spl.h" +#include "Zend/zend_gc.h" + +ZEND_DECLARE_MODULE_GLOBALS(symfony_debug) + +ZEND_BEGIN_ARG_INFO_EX(symfony_zval_arginfo, 0, 0, 2) + ZEND_ARG_INFO(0, key) + ZEND_ARG_ARRAY_INFO(0, array, 0) + ZEND_ARG_INFO(0, options) +ZEND_END_ARG_INFO() + +const zend_function_entry symfony_debug_functions[] = { + PHP_FE(symfony_zval_info, symfony_zval_arginfo) + PHP_FE_END +}; + +PHP_FUNCTION(symfony_zval_info) +{ + zval *key = NULL, *arg = NULL; + zval **data = NULL; + HashTable *array = NULL; + long options = 0; + + if (zend_parse_parameters(ZEND_NUM_ARGS(), "zh|l", &key, &array, &options) == FAILURE) { + return; + } + + switch (Z_TYPE_P(key)) { + case IS_STRING: + if (zend_symtable_find(array, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, (void **)&data) == FAILURE) { + return; + } + break; + case IS_LONG: + if (zend_hash_index_find(array, Z_LVAL_P(key), (void **)&data)) { + return; + } + break; + } + + arg = *data; + + array_init(return_value); + + add_assoc_string(return_value, "type", (char *)_symfony_debug_zval_type(arg), 1); + add_assoc_stringl(return_value, "zval_hash", _symfony_debug_memory_address_hash((void *)arg), 16, 1); + add_assoc_long(return_value, "zval_refcount", Z_REFCOUNT_P(arg)); + add_assoc_bool(return_value, "zval_isref", (zend_bool)Z_ISREF_P(arg)); + + if (Z_TYPE_P(arg) == IS_OBJECT) { + static char hash[33] = {0}; + php_spl_object_hash(arg, (char *)hash); + add_assoc_stringl(return_value, "object_class", (char *)Z_OBJCE_P(arg)->name, Z_OBJCE_P(arg)->name_length, 1); + add_assoc_long(return_value, "object_refcount", EG(objects_store).object_buckets[Z_OBJ_HANDLE_P(arg)].bucket.obj.refcount); + add_assoc_string(return_value, "object_hash", hash, 1); + } else if (Z_TYPE_P(arg) == IS_ARRAY) { + add_assoc_long(return_value, "array_count", zend_hash_num_elements(Z_ARRVAL_P(arg))); + } else if(Z_TYPE_P(arg) == IS_RESOURCE) { + add_assoc_long(return_value, "resource_id", Z_LVAL_P(arg)); + add_assoc_string(return_value, "resource_type", (char *)_symfony_debug_get_resource_type(Z_LVAL_P(arg)), 1); + add_assoc_long(return_value, "resource_refcount", _symfony_debug_get_resource_refcount(Z_LVAL_P(arg))); + } else if (Z_TYPE_P(arg) == IS_STRING) { + add_assoc_long(return_value, "strlen", Z_STRLEN_P(arg)); + } +} + +static const char* _symfony_debug_get_resource_type(long rsid) +{ + const char *res_type; + res_type = zend_rsrc_list_get_rsrc_type(rsid); + + if (!res_type) { + return "Unknown"; + } + + return res_type; +} + +static int _symfony_debug_get_resource_refcount(long rsid) +{ + zend_rsrc_list_entry *le; + + if (zend_hash_index_find(&EG(regular_list), rsid, (void **) &le)==SUCCESS) { + return le->refcount; + } + + return 0; +} + +static char *_symfony_debug_memory_address_hash(void *address) +{ + static char result[17] = {0}; + intptr_t address_rand; + + if (!SYMFONY_DEBUG_G(req_rand_init)) { + if (!BG(mt_rand_is_seeded)) { + php_mt_srand(GENERATE_SEED() TSRMLS_CC); + } + SYMFONY_DEBUG_G(req_rand_init) = (intptr_t)php_mt_rand(); + } + + address_rand = (intptr_t)address ^ SYMFONY_DEBUG_G(req_rand_init); + + snprintf(result, 17, "%016zx", address_rand); + + return result; +} + +static const char *_symfony_debug_zval_type(zval *zv) +{ + switch (Z_TYPE_P(zv)) { + case IS_NULL: + return "NULL"; + break; + + case IS_BOOL: + return "boolean"; + break; + + case IS_LONG: + return "integer"; + break; + + case IS_DOUBLE: + return "double"; + break; + + case IS_STRING: + return "string"; + break; + + case IS_ARRAY: + return "array"; + break; + + case IS_OBJECT: + return "object"; + + case IS_RESOURCE: + return "resource"; + + default: + return "unknown type"; + } +} + +zend_module_entry symfony_debug_module_entry = { + STANDARD_MODULE_HEADER, + "symfony_debug", + symfony_debug_functions, + PHP_MINIT(symfony_debug), + PHP_MSHUTDOWN(symfony_debug), + PHP_RINIT(symfony_debug), + PHP_RSHUTDOWN(symfony_debug), + PHP_MINFO(symfony_debug), + PHP_SYMFONY_DEBUG_VERSION, + PHP_MODULE_GLOBALS(symfony_debug), + PHP_GINIT(symfony_debug), + PHP_GSHUTDOWN(symfony_debug), + NULL, + STANDARD_MODULE_PROPERTIES_EX +}; + +#ifdef COMPILE_DL_SYMFONY_DEBUG +ZEND_GET_MODULE(symfony_debug) +#endif + +PHP_GINIT_FUNCTION(symfony_debug) +{ + symfony_debug_globals->req_rand_init = 0; +} + +PHP_GSHUTDOWN_FUNCTION(symfony_debug) +{ + +} + +PHP_MINIT_FUNCTION(symfony_debug) +{ + return SUCCESS; +} + +PHP_MSHUTDOWN_FUNCTION(symfony_debug) +{ + return SUCCESS; +} + +PHP_RINIT_FUNCTION(symfony_debug) +{ + return SUCCESS; +} + +PHP_RSHUTDOWN_FUNCTION(symfony_debug) +{ + return SUCCESS; +} + +PHP_MINFO_FUNCTION(symfony_debug) +{ + php_info_print_table_start(); + php_info_print_table_header(2, "Symfony Debug support", "enabled"); + php_info_print_table_header(2, "Symfony Debug version", PHP_SYMFONY_DEBUG_VERSION); + php_info_print_table_end(); +} diff --git a/src/Symfony/Component/Debug/Resources/ext/ext/tests/001.phpt b/src/Symfony/Component/Debug/Resources/ext/ext/tests/001.phpt new file mode 100644 index 0000000000000..a0015a939445c --- /dev/null +++ b/src/Symfony/Component/Debug/Resources/ext/ext/tests/001.phpt @@ -0,0 +1,149 @@ +--TEST-- +Test symfony_zval_info API +--SKIPIF-- + +--FILE-- + $int, + 'float' => $float, + 'str' => $str, + 'object' => $object, + 'array' => $array, + 'resource' => $resource, + 'null' => $null, + 'bool' => $bool, + 'refcount' => &$refcount2); + +var_dump(symfony_zval_info('int', $var)); +var_dump(symfony_zval_info('float', $var)); +var_dump(symfony_zval_info('str', $var)); +var_dump(symfony_zval_info('object', $var)); +var_dump(symfony_zval_info('array', $var)); +var_dump(symfony_zval_info('resource', $var)); +var_dump(symfony_zval_info('null', $var)); +var_dump(symfony_zval_info('bool', $var)); + +var_dump(symfony_zval_info('refcount', $var)); +var_dump(symfony_zval_info('not-exist', $var)); +?> +--EXPECTF-- +array(4) { + ["type"]=> + string(7) "integer" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(4) { + ["type"]=> + string(6) "double" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(5) { + ["type"]=> + string(6) "string" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["strlen"]=> + int(6) +} +array(7) { + ["type"]=> + string(6) "object" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["object_class"]=> + string(8) "stdClass" + ["object_refcount"]=> + int(1) + ["object_hash"]=> + string(32) "%s" +} +array(5) { + ["type"]=> + string(5) "array" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["array_count"]=> + int(2) +} +array(7) { + ["type"]=> + string(8) "resource" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) + ["resource_id"]=> + int(4) + ["resource_type"]=> + string(6) "stream" + ["resource_refcount"]=> + int(1) +} +array(4) { + ["type"]=> + string(4) "NULL" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(4) { + ["type"]=> + string(7) "boolean" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(2) + ["zval_isref"]=> + bool(false) +} +array(4) { + ["type"]=> + string(7) "integer" + ["zval_hash"]=> + string(16) "%s" + ["zval_refcount"]=> + int(3) + ["zval_isref"]=> + bool(true) +} +NULL \ No newline at end of file From 4bf9300c09ec4033f66e95d2aec7457012edb49c Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 20 Mar 2014 13:53:59 +0100 Subject: [PATCH 02/31] [Debug] a README for the debug extension --- .../Component/Debug/Resources/ext/README.rst | 71 +++++++++++++++++++ 1 file changed, 71 insertions(+) create mode 100644 src/Symfony/Component/Debug/Resources/ext/README.rst diff --git a/src/Symfony/Component/Debug/Resources/ext/README.rst b/src/Symfony/Component/Debug/Resources/ext/README.rst new file mode 100644 index 0000000000000..77be5648aaa2b --- /dev/null +++ b/src/Symfony/Component/Debug/Resources/ext/README.rst @@ -0,0 +1,71 @@ +Symfony Debug Extension +======================= + +This extension adds a ``symfony_zval_info($key, $array, $options = 0)`` function that: + +- exposes zval_hash/refcounts, allowing e.g. efficient exploration of arbitrary structures in PHP, +- does work with references, preventing memory copying. + +Its behavior is about the same as: + +.. code-block:: php + + gettype($array[$key]), + 'zval_hash' => /* hashed memory address of $array[$key] */, + 'zval_refcount' => /* internal zval refcount of $array[$key] */, + 'zval_isref' => /* is_ref status of $array[$key] */, + ); + + switch ($info['type']) { + case 'object': + $info += array( + 'object_class' => get_class($array[$key]), + 'object_refcount' => /* internal object refcount of $array[$key] */, + 'object_hash' => spl_object_hash($array[$key]), + ); + break; + + case 'resource': + $info += array( + 'resource_id' => (int) substr((string) $array[$key], 13), + 'resource_type' => get_resource_type($array[$key]), + 'resource_refcount' => /* internal resource refcount of $array[$key] */, + ); + break; + + case 'array': + $info += array( + 'array_count' => count($array[$key]), + ); + break; + + case 'string': + $info += array( + 'strlen' => strlen($array[$key]), + ); + break; + } + + return $info; + } + +To enable the extension from source, run: + +.. code-block:: sh + + phpize + ./configure + make + sudo make install + From 07135a09597d09c30055a3a8c95d26e1b01458f5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 5 Apr 2014 20:35:00 +0200 Subject: [PATCH 03/31] [VarDumper] algo to clone any PHP variable to a breadth-first queue --- .../VarDumper/Cloner/AbstractCloner.php | 160 +++++++++++++++ .../VarDumper/Cloner/ClonerInterface.php | 27 +++ .../Component/VarDumper/Cloner/Data.php | 33 +++ .../Component/VarDumper/Cloner/PhpCloner.php | 188 ++++++++++++++++++ .../Exception/ThrowingCasterException.php | 36 ++++ 5 files changed, 444 insertions(+) create mode 100644 src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php create mode 100644 src/Symfony/Component/VarDumper/Cloner/ClonerInterface.php create mode 100644 src/Symfony/Component/VarDumper/Cloner/Data.php create mode 100644 src/Symfony/Component/VarDumper/Cloner/PhpCloner.php create mode 100644 src/Symfony/Component/VarDumper/Exception/ThrowingCasterException.php diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php new file mode 100644 index 0000000000000..2c12aaefc5d1c --- /dev/null +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -0,0 +1,160 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +use Symfony\Component\VarDumper\Exception\ThrowingCasterException; + +/** + * AbstractCloner implements a generic caster mechanism for objects and resources. + * + * @author Nicolas Grekas + */ +abstract class AbstractCloner implements ClonerInterface +{ + protected $maxItems = 2500; + protected $maxString = -1; + + private $data = array(array(null)); + private $prevErrorHandler; + + /** + * Sets the maximum number of items to clone past the first level in nested structures. + * + * @param int $maxItems + */ + public function setMaxItems($maxItems) + { + $this->maxItems = (int) $maxItems; + } + + /** + * Sets the maximum cloned length for strings. + * + * @param int $maxString + */ + public function setMaxString($maxString) + { + $this->maxString = (int) $maxString; + } + + /** + * {@inheritdoc} + */ + public function cloneVar($var) + { + $this->prevErrorHandler = set_error_handler(array($this, 'handleError')); + try { + if (!function_exists('iconv')) { + $this->maxString = -1; + } + $data = $this->doClone($var); + } catch (\Exception $e) { + } + restore_error_handler(); + $this->prevErrorHandler = null; + + if (isset($e)) { + throw $e; + } + + return new Data($data); + } + + /** + * Effectively clones the PHP variable. + * + * @param mixed $var Any PHP variable. + * + * @return array The cloned variable represented in an array. + */ + abstract protected function doClone($var); + + /** + * Casts an object to an array representation. + * + * @param string $class The class of the object. + * @param object $obj The object itself. + * + * @return array The object casted as array. + */ + protected function castObject($class, $obj) + { + if (method_exists($obj, '__debugInfo')) { + if (!$a = $this->callCaster(array($this, '__debugInfo'), $obj, array())) { + $a = (array) $obj; + } + } else { + $a = (array) $obj; + } + + return $a; + } + + /** + * Casts a resource to an array representation. + * + * @param string $type The type of the resource. + * @param resource $res The resource. + * + * @return array The resource casted as array. + */ + protected function castResource($type, $res) + { + $a = array(); + + 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. + * + * @return array The casted object/resource. + */ + private function callCaster($callback, $obj, $a) + { + try { + // Ignore invalid $callback + $cast = @call_user_func($callback, $obj, $a); + + if (is_array($cast)) { + $a = $cast; + } + } catch (\Exception $e) { + $a["\0~\0⚠"] = new ThrowingCasterException($callback, $e); + } + + return $a; + } + + /** + * Special handling for errors: cloning must be fail-safe. + * + * @internal + */ + public function handleError($type, $msg, $file, $line, $context) + { + if (E_RECOVERABLE_ERROR === $type || E_USER_ERROR === $type) { + // Cloner never dies + throw new \ErrorException($msg, 0, $type, $file, $line); + } + + if ($this->prevErrorHandler) { + return call_user_func($this->prevErrorHandler, $type, $msg, $file, $line, $context); + } + + return false; + } +} diff --git a/src/Symfony/Component/VarDumper/Cloner/ClonerInterface.php b/src/Symfony/Component/VarDumper/Cloner/ClonerInterface.php new file mode 100644 index 0000000000000..c1df5933dbf2e --- /dev/null +++ b/src/Symfony/Component/VarDumper/Cloner/ClonerInterface.php @@ -0,0 +1,27 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * @author Nicolas Grekas + */ +interface ClonerInterface +{ + /** + * Clones a PHP variable. + * + * @param mixed $var Any PHP variable. + * + * @return Data The cloned variable represented by a Data object. + */ + public function cloneVar($var); +} diff --git a/src/Symfony/Component/VarDumper/Cloner/Data.php b/src/Symfony/Component/VarDumper/Cloner/Data.php new file mode 100644 index 0000000000000..038cad53bb72a --- /dev/null +++ b/src/Symfony/Component/VarDumper/Cloner/Data.php @@ -0,0 +1,33 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * @author Nicolas Grekas + */ +class Data +{ + private $data; + + /** + * @param array $data A array as returned by ClonerInterface::cloneVar(). + */ + public function __construct(array $data) + { + $this->data = $data; + } + + public function getRawData() + { + return $this->data; + } +} diff --git a/src/Symfony/Component/VarDumper/Cloner/PhpCloner.php b/src/Symfony/Component/VarDumper/Cloner/PhpCloner.php new file mode 100644 index 0000000000000..19a5b6bf069ae --- /dev/null +++ b/src/Symfony/Component/VarDumper/Cloner/PhpCloner.php @@ -0,0 +1,188 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * @author Nicolas Grekas + */ +class PhpCloner extends AbstractCloner +{ + /** + * {@inheritdoc} + */ + protected function doClone($var) + { + $i = 0; // Current iteration position in $queue + $len = 1; // Length of $queue + $pos = 0; // Number of cloned items past the first level + $refs = 0; // Number of hard+soft references in $var + $queue = array(array($var)); // This breadth-first queue is the return value + $arrayRefs = array(); // Map of queue indexes to stub array objects + $hardRefs = array(); // By-ref map of stub objects' hashes to original hard `&` references + $softRefs = array(); // Map of original object hashes to their stub object couterpart + $values = array(); // Map of stub objects' hashes to original values + $maxItems = $this->maxItems; + $maxString = $this->maxString; + $cookie = (object) array(); // Unique object used to detect hard references + $isRef = false; + $a = null; // Array cast for nested structures + $stub = null; // stdClass capturing the main properties of an original item value, + // or null if the original value is used directly + + for ($i = 0; $i < $len; ++$i) { + $indexed = true; // Whether the currently iterated array is numerically indexed or not + $j = -1; // Position in the currently iterated array + $step = $queue[$i]; // Copy of the currently iterated array used for hard references detection + foreach ($step as $k => $v) { + // $k is the original key + // $v is the original value or a stub object in case of hard references + if ($indexed && $k !== ++$j) { + $indexed = false; + } + $step[$k] = $cookie; + if ($queue[$i][$k] === $cookie) { + $queue[$i][$k] =& $stub; // Break hard references to make $queue completely + unset($stub); // independent from the original structure + if ($v instanceof \stdClass && isset($hardRefs[spl_object_hash($v)])) { + $v->ref = ++$refs; + $step[$k] = $queue[$i][$k] = $v; + continue; + } + $isRef = true; + } + // Create $stub when the original value $v can not be used directly + // If $v is a nested structure, put that structure in array $a + switch (gettype($v)) { + case 'string': + if (isset($v[0]) && !preg_match('//u', $v)) { + if (0 <= $maxString && 0 < $cut = strlen($v) - $maxString) { + $stub = substr_replace($v, '', -$cut); + $stub = (object) array('cut' => $cut, 'bin' => Data::utf8Encode($stub)); + } else { + $stub = (object) array('bin' => Data::utf8Encode($v)); + } + } elseif (0 <= $maxString && isset($v[1+($maxString>>2)]) && 0 < $cut = iconv_strlen($v, 'UTF-8') - $maxString) { + $stub = iconv_substr($v, 0, $maxString, 'UTF-8'); + $stub = (object) array('cut' => $cut, 'str' => $stub); + } + break; + + case 'integer': + break; + + case 'array': + if ($v) { + $stub = (object) array('count' => count($v)); + $arrayRefs[$len] = $stub; + $a = $v; + } + break; + + case 'object': + if (empty($softRefs[$h = spl_object_hash($v)])) { + $stub = $softRefs[$h] = (object) array('class' => get_class($v)); + if (0 > $maxItems || $pos < $maxItems) { + $a = $this->castObject($stub->class, $v, 0 < $i, $cut); + if ($cut) { + $stub->cut = $cut; + } + } else { + $stub->cut = -1; + } + } else { + $stub = $softRefs[$h]; + $stub->ref = ++$refs; + } + break; + + case 'resource': + case 'unknown type': + if (empty($softRefs[$h = (int) substr_replace($v, '', 0, 13)])) { + $stub = $softRefs[$h] = (object) array('res' => @get_resource_type($v)); + if (0 > $maxItems || $pos < $maxItems) { + $a = $this->castResource($stub->res, $v, 0 < $i); + } else { + $stub->cut = -1; + } + } else { + $stub = $softRefs[$h]; + $stub->ref = ++$refs; + } + break; + } + + if (isset($stub)) { + if ($isRef) { + if (isset($stub->count)) { + $step[$k] = $stub; + } else { + $step[$k] = (object) array('val' => $stub); + } + $h = spl_object_hash($step[$k]); + $queue[$i][$k] = $hardRefs[$h] =& $step[$k]; + $values[$h] = $v; + $isRef = false; + } 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 (empty($stub->cut)) { + $stub->cut = $pos - $maxItems; + } elseif ($stub->cut > 0) { + $stub->cut += $pos - $maxItems; + } + } + } else { + if (empty($stub->cut)) { + $stub->cut = $k; + } elseif ($stub->cut > 0) { + $stub->cut += $k; + } + $stub = $a = null; + unset($arrayRefs[$len]); + continue; + } + } + $queue[$len] = $a; + $stub->pos = $len++; + } + $stub = $a = null; + } elseif ($isRef) { + $step[$k] = $queue[$i][$k] = $v = (object) array('val' => $v); + $h = spl_object_hash($v); + $hardRefs[$h] =& $step[$k]; + $values[$h] = $v->val; + $isRef = false; + } + } + + if (isset($arrayRefs[$i])) { + if ($indexed) { + $arrayRefs[$i]->indexed = 1; + } + unset($arrayRefs[$i]); + } + } + + foreach ($values as $h => $v) { + $hardRefs[$h] = $v; + } + + return $queue; + } +} diff --git a/src/Symfony/Component/VarDumper/Exception/ThrowingCasterException.php b/src/Symfony/Component/VarDumper/Exception/ThrowingCasterException.php new file mode 100644 index 0000000000000..1959ac8985e48 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Exception/ThrowingCasterException.php @@ -0,0 +1,36 @@ + +* +* For the full copyright and license information, please view the LICENSE +* file that was distributed with this source code. +*/ + +namespace Symfony\Component\VarDumper\Exception; + +/** + * @author Nicolas Grekas + */ +class ThrowingCasterException extends \Exception +{ + private $caster; + + /** + * @param callable $caster The failing caster + * @param \Exception $prev The exception thrown from the caster + */ + public function __construct($caster, \Exception $prev) + { + if (is_array($caster)) { + if (isset($caster[0]) && is_object($caster[0])) { + $caster[0] = get_class($caster[0]); + } + $caster = implode('::', $caster); + } + $this->caster = $caster; + parent::__construct(null, 0, $prev); + } +} From 5b7ae2862f8e6428a26a7ea3c3fa971647de829d Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 5 Apr 2014 20:36:53 +0200 Subject: [PATCH 04/31] [VarDumper] symfony_debug ext. fast and memory efficient cloning algo --- .../Component/VarDumper/Cloner/ExtCloner.php | 171 ++++++++++++++++++ 1 file changed, 171 insertions(+) create mode 100644 src/Symfony/Component/VarDumper/Cloner/ExtCloner.php diff --git a/src/Symfony/Component/VarDumper/Cloner/ExtCloner.php b/src/Symfony/Component/VarDumper/Cloner/ExtCloner.php new file mode 100644 index 0000000000000..00fa51efbaf1a --- /dev/null +++ b/src/Symfony/Component/VarDumper/Cloner/ExtCloner.php @@ -0,0 +1,171 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * @author Nicolas Grekas + */ +class ExtCloner extends AbstractCloner +{ + /** + * {@inheritdoc} + */ + protected function doClone($var) + { + $i = 0; // Current iteration position in $queue + $len = 1; // Length of $queue + $pos = 0; // Number of cloned items past the first level + $refs = 0; // Number of hard+soft references in $var + $queue = array(array($var)); // This breadth-first queue is the return value + $arrayRefs = array(); // Map of queue indexes to stub array objects + $hardRefs = array(); // Map of original zval hashes to stub objects + $softRefs = array(); // Map of original object hashes to their stub object couterpart + $maxItems = $this->maxItems; + $maxString = $this->maxString; + $a = null; // Array cast for nested structures + $stub = null; // stdClass capturing the main properties of an original item value, + // or null if the original value is used directly + + for ($i = 0; $i < $len; ++$i) { + $indexed = true; // Whether the currently iterated array is numerically indexed or not + $j = -1; // Position in the currently iterated array + $step = $queue[$i]; // Copy of the currently iterated array used for hard references detection + foreach ($step as $k => $v) { + // $k is the original key + // $v is the original value or a stub object in case of hard references + if ($indexed && $k !== ++$j) { + $indexed = false; + } + $zval = symfony_zval_info($k, $step); + if ($zval['zval_isref']) { + $queue[$i][$k] =& $stub; // Break hard references to make $queue completely + unset($stub); // independent from the original structure + if (isset($hardRefs[$h = $zval['zval_hash']])) { + $hardRefs[$h]->ref = ++$refs; + $queue[$i][$k] = $hardRefs[$h]; + continue; + } + } + // Create $stub when the original value $v can not be used directly + // If $v is a nested structure, put that structure in array $a + switch ($zval['type']) { + case 'string': + if (isset($v[0]) && !preg_match('//u', $v)) { + if (0 <= $maxString && 0 < $cut = strlen($v) - $maxString) { + $stub = substr_replace($v, '', -$cut); + $stub = (object) array('cut' => $cut, 'bin' => Data::utf8Encode($stub)); + } else { + $stub = (object) array('bin' => Data::utf8Encode($v)); + } + } elseif (0 <= $maxString && isset($v[1+($maxString>>2)]) && 0 < $cut = iconv_strlen($v, 'UTF-8') - $maxString) { + $stub = iconv_substr($v, 0, $maxString, 'UTF-8'); + $stub = (object) array('cut' => $cut, 'str' => $stub); + } + break; + + case 'integer': + break; + + case 'array': + if ($v) { + $stub = (object) array('count' => $zval['array_count']); + $arrayRefs[$len] = $stub; + $a = $v; + } + break; + + case 'object': + if (empty($softRefs[$h = $zval['object_hash']])) { + $stub = $softRefs[$h] = (object) array('class' => $zval['object_class']); + if (0 > $maxItems || $pos < $maxItems) { + $a = $this->castObject($stub->class, $v, 0 < $i, $cut); + if ($cut) { + $stub->cut = $cut; + } + } else { + $stub->cut = -1; + } + } else { + $stub = $softRefs[$h]; + $stub->ref = ++$refs; + } + break; + + case 'resource': + if (empty($softRefs[$h = $zval['resource_id']])) { + $stub = $softRefs[$h] = (object) array('res' => $zval['resource_type']); + if (0 > $maxItems || $pos < $maxItems) { + $a = $this->castResource($stub->res, $v, 0 < $i); + } else { + $stub->cut = -1; + } + } else { + $stub = $softRefs[$h]; + $stub->ref = ++$refs; + } + break; + } + + if (isset($stub)) { + if ($zval['zval_isref']) { + if (isset($stub->count)) { + $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = $stub; + } else { + $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = (object) array('val' => $stub); + } + } 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 (empty($stub->cut)) { + $stub->cut = $pos - $maxItems; + } elseif ($stub->cut > 0) { + $stub->cut += $pos - $maxItems; + } + } + } else { + if (empty($stub->cut)) { + $stub->cut = $k; + } elseif ($stub->cut > 0) { + $stub->cut += $k; + } + $stub = $a = null; + unset($arrayRefs[$len]); + continue; + } + } + $queue[$len] = $a; + $stub->pos = $len++; + } + $stub = $a = null; + } elseif ($zval['zval_isref']) { + $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = (object) array('val' => $v); + } + } + + if (isset($arrayRefs[$i])) { + if ($indexed) { + $arrayRefs[$i]->indexed = 1; + } + unset($arrayRefs[$i]); + } + } + + return $queue; + } +} From 3ddbf4b6b9e4986e9af4f65399e16c9cd7421214 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 5 Apr 2014 20:45:54 +0200 Subject: [PATCH 05/31] [VarDumper] add casters for per class/resource custom state extraction --- .../VarDumper/Caster/ReflectionCaster.php | 35 ++++++ .../VarDumper/Caster/ResourceCaster.php | 65 ++++++++++ .../VarDumper/Cloner/AbstractCloner.php | 111 +++++++++++++++--- 3 files changed, 197 insertions(+), 14 deletions(-) create mode 100644 src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php create mode 100644 src/Symfony/Component/VarDumper/Caster/ResourceCaster.php diff --git a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php new file mode 100644 index 0000000000000..0c39c460ebd43 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php @@ -0,0 +1,35 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Casts Reflector related classes to array representation. + * + * @author Nicolas Grekas + */ +class ReflectionCaster +{ + public static function castReflector(\Reflector $c, array $a) + { + $a["\0~\0reflection"] = $c->__toString(); + + return $a; + } + + public static function castClosure(\Closure $c, array $a) + { + $a = static::castReflector(new \ReflectionFunction($c), $a); + unset($a["\0+\0000"], $a['name']); + + return $a; + } +} diff --git a/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php b/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php new file mode 100644 index 0000000000000..c381bf73ab2d6 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php @@ -0,0 +1,65 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Casts common resource types to array representation. + * + * @author Nicolas Grekas + */ +class ResourceCaster +{ + public static function castCurl($h, array $a) + { + return curl_getinfo($h); + } + + public static function castDba($dba, array $a) + { + $list = dba_list(); + $a['file'] = $list[substr((string) $dba, 13)]; + + return $a; + } + + public static function castProcess($process, array $a) + { + return proc_get_status($process); + } + + public static function castStream($stream, array $a) + { + return stream_get_meta_data($stream) + static::castStreamContext($stream, $a); + } + + public static function castStreamContext($stream, array $a) + { + return stream_context_get_params($stream); + } + + public static function castGd($gd, array $a) + { + $a['size'] = imagesx($gd).'x'.imagesy($gd); + $a['trueColor'] = imageistruecolor($gd); + + return $a; + } + + public static function castMysqlLink($h, array $a) + { + $a['host'] = mysql_get_host_info($h); + $a['protocol'] = mysql_get_proto_info($h); + $a['server'] = mysql_get_server_info($h); + + return $a; + } +} diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index 2c12aaefc5d1c..a0b956c136be9 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -20,11 +20,58 @@ */ abstract class AbstractCloner implements ClonerInterface { - protected $maxItems = 2500; + public static $defaultCasters = array( + 'o:Closure' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castClosure', + 'o:Reflector' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castReflector', + + 'r:curl' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castCurl', + 'r:dba' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba', + 'r:dba persistent' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba', + 'r:gd' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castGd', + 'r:mysql link' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castMysqlLink', + 'r:process' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castProcess', + 'r:stream' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStream', + 'r:stream-context' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStreamContext', + ); + + protected $maxItems = 250; protected $maxString = -1; + private $casters = array(); private $data = array(array(null)); private $prevErrorHandler; + private $classInfo = array(); + + /** + * @param callable[]|null $casters A map of casters. + * + * @see addCasters + */ + public function __construct(array $casters = null) + { + if (null === $casters) { + $casters = static::$defaultCasters; + } + $this->addCasters($casters); + } + + /** + * Adds casters for resources and objects. + * + * Maps resources or objects types to a callback. + * Types are in the key, with a callable caster for value. + * Objects class are to be prefixed with a `o:`, + * resources type are to be prefixed with a `r:`, + * see e.g. static::$defaultCasters. + * + * @param callable[] $casters A map of casters. + */ + public function addCasters(array $casters) + { + foreach ($casters as $type => $callback) { + $this->casters[strtolower($type)][] = $callback; + } + } /** * Sets the maximum number of items to clone past the first level in nested structures. @@ -81,20 +128,48 @@ abstract protected function doClone($var); /** * Casts an object to an array representation. * - * @param string $class The class of the object. - * @param object $obj The object itself. + * @param string $class The class of the object. + * @param object $obj The object itself. + * @param bool $isNested True if the object is nested in the dumped structure. + * @param int &$cut After the cast, number of items removed from $obj. * * @return array The object casted as array. */ - protected function castObject($class, $obj) + protected function castObject($class, $obj, $isNested, &$cut) { - if (method_exists($obj, '__debugInfo')) { - if (!$a = $this->callCaster(array($this, '__debugInfo'), $obj, array())) { - $a = (array) $obj; - } + if (isset($this->classInfo[$class])) { + $classInfo = $this->classInfo[$class]; + } else { + $classInfo = array( + method_exists($class, '__debugInfo'), + new \ReflectionClass($class), + array_reverse(array($class => $class) + class_parents($class) + class_implements($class) + array('*' => '*')), + ); + + $this->classInfo[$class] = $classInfo; + } + + if ($classInfo[0]) { + $a = $this->callCaster(array($obj, '__debugInfo'), $obj, array(), $isNested); } else { $a = (array) $obj; } + $cut = 0; + + foreach ($a as $k => $p) { + if (!isset($k[0]) || ("\0" !== $k[0] && !$classInfo[1]->hasProperty($k))) { + unset($a[$k]); + $a["\0+\0".$k] = $p; + } + } + + foreach ($classInfo[2] as $p) { + if (!empty($this->casters[$p = 'o:'.strtolower($p)])) { + foreach ($this->casters[$p] as $p) { + $a = $this->callCaster($p, $obj, $a, $isNested, $cut); + } + } + } return $a; } @@ -102,15 +177,22 @@ protected function castObject($class, $obj) /** * Casts a resource to an array representation. * - * @param string $type The type of the resource. - * @param resource $res The resource. + * @param string $type The type of the resource. + * @param resource $res The resource. + * @param bool $isNested True if the object is nested in the dumped structure. * * @return array The resource casted as array. */ - protected function castResource($type, $res) + protected function castResource($type, $res, $isNested) { $a = array(); + if (!empty($this->casters['r:'.$type])) { + foreach ($this->casters['r:'.$type] as $c) { + $a = $this->callCaster($c, $res, $a, $isNested); + } + } + return $a; } @@ -120,14 +202,15 @@ protected function castResource($type, $res) * @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 bool $isNested True if $obj is nested in the dumped structure. + * @param int &$cut After the cast, number of items removed from $obj. * * @return array The casted object/resource. */ - private function callCaster($callback, $obj, $a) + private function callCaster($callback, $obj, $a, $isNested, &$cut = 0) { try { - // Ignore invalid $callback - $cast = @call_user_func($callback, $obj, $a); + $cast = call_user_func_array($callback, array($obj, $a, $isNested, &$cut)); if (is_array($cast)) { $a = $cast; From c91bc83c86399a7ad69456ede77e6da5f8c1d6f2 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 5 Apr 2014 20:47:45 +0200 Subject: [PATCH 06/31] [VarDumper] casters for exceptions representation --- .../VarDumper/Caster/ExceptionCaster.php | 132 ++++++++++++++++++ .../VarDumper/Cloner/AbstractCloner.php | 5 + 2 files changed, 137 insertions(+) create mode 100644 src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php diff --git a/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php new file mode 100644 index 0000000000000..4abc2c53c8737 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php @@ -0,0 +1,132 @@ + + * + * 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\Exception\ThrowingCasterException; + +/** + * Casts common Exception classes to array representation. + * + * @author Nicolas Grekas + */ +class ExceptionCaster +{ + public static $traceArgs = true; + public static $errorTypes = array( + E_DEPRECATED => 'E_DEPRECATED', + E_USER_DEPRECATED => 'E_USER_DEPRECATED', + E_RECOVERABLE_ERROR => 'E_RECOVERABLE_ERROR', + E_ERROR => 'E_ERROR', + E_WARNING => 'E_WARNING', + E_PARSE => 'E_PARSE', + E_NOTICE => 'E_NOTICE', + E_CORE_ERROR => 'E_CORE_ERROR', + E_CORE_WARNING => 'E_CORE_WARNING', + E_COMPILE_ERROR => 'E_COMPILE_ERROR', + E_COMPILE_WARNING => 'E_COMPILE_WARNING', + E_USER_ERROR => 'E_USER_ERROR', + E_USER_WARNING => 'E_USER_WARNING', + E_USER_NOTICE => 'E_USER_NOTICE', + E_STRICT => 'E_STRICT', + ); + + public static function castException(\Exception $e, array $a) + { + $trace = $a["\0Exception\0trace"]; + unset($a["\0Exception\0trace"]); // Ensures the trace is always last + + static::filterTrace($trace, static::$traceArgs); + + if (null !== $trace) { + $a["\0Exception\0trace"] = $trace; + } + if (empty($a["\0Exception\0previous"])) { + unset($a["\0Exception\0previous"]); + } + unset($a["\0Exception\0string"], $a["\0+\0xdebug_message"], $a["\0+\0__destructorException"]); + + return $a; + } + + public static function castErrorException(\ErrorException $e, array $a) + { + if (isset($a[$s = "\0*\0severity"], self::$errorTypes[$a[$s]])) { + $a[$s] = self::$errorTypes[$a[$s]]; + } + + return $a; + } + + public static function castThrowingCasterException(ThrowingCasterException $e, array $a) + { + $b = (array) $a["\0Exception\0previous"]; + + array_splice($b["\0Exception\0trace"], count($a["\0Exception\0trace"])); + + $t = static::$traceArgs; + static::$traceArgs = false; + $b = static::castException($a["\0Exception\0previous"], $b); + static::$traceArgs = $t; + + if (empty($a["\0*\0message"])) { + $a["\0*\0message"] = "Unexpected exception thrown from a caster: ".get_class($a["\0Exception\0previous"]); + } + + if (isset($b["\0*\0message"])) { + $a["\0~\0message"] = $b["\0*\0message"]; + } + if (isset($b["\0*\0file"])) { + $a["\0~\0file"] = $b["\0*\0file"]; + } + if (isset($b["\0*\0line"])) { + $a["\0~\0line"] = $b["\0*\0line"]; + } + if (isset($b["\0Exception\0trace"])) { + $a["\0~\0trace"] = $b["\0Exception\0trace"]; + } + + unset($a["\0Exception\0trace"], $a["\0Exception\0previous"], $a["\0*\0code"], $a["\0*\0file"], $a["\0*\0line"]); + + return $a; + } + + public static function filterTrace(&$trace, $dumpArgs, $offset = 0) + { + if (0 > $offset || empty($trace[$offset])) { + return $trace = null; + } + + $t = $trace[$offset]; + + if (empty($t['class']) && isset($t['function'])) { + if ('user_error' === $t['function'] || 'trigger_error' === $t['function']) { + ++$offset; + } + } + + if ($offset) { + array_splice($trace, 0, $offset); + } + + foreach ($trace as &$t) { + $t = array( + 'call' => (isset($t['class']) ? $t['class'].$t['type'] : '').$t['function'].'()', + 'file' => isset($t['line']) ? "{$t['file']}:{$t['line']}" : '', + 'args' => &$t['args'], + ); + + if (!isset($t['args']) || !$dumpArgs) { + unset($t['args']); + } + } + } +} diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index a0b956c136be9..73dbfcc601eba 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -24,6 +24,11 @@ abstract class AbstractCloner implements ClonerInterface 'o:Closure' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castClosure', 'o:Reflector' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castReflector', + 'o:ErrorException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castErrorException', + 'o:Exception' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castException', + 'o:Symfony\Component\VarDumper\Exception\ThrowingCasterException' + => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castThrowingCasterException', + 'r:curl' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castCurl', 'r:dba' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba', 'r:dba persistent' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba', From da3e50a2bfdb54949f2f7197e96b0cdc349891fa Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 5 Apr 2014 20:54:33 +0200 Subject: [PATCH 07/31] [VarDumper] casters for SPL data structures --- .../Component/VarDumper/Caster/SplCaster.php | 108 ++++++++++++++++++ .../VarDumper/Cloner/AbstractCloner.php | 7 ++ 2 files changed, 115 insertions(+) create mode 100644 src/Symfony/Component/VarDumper/Caster/SplCaster.php diff --git a/src/Symfony/Component/VarDumper/Caster/SplCaster.php b/src/Symfony/Component/VarDumper/Caster/SplCaster.php new file mode 100644 index 0000000000000..9f4680294d6c1 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Caster/SplCaster.php @@ -0,0 +1,108 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Casts SPL related classes to array representation. + * + * @author Nicolas Grekas + */ +class SplCaster +{ + public static function castArrayObject(\ArrayObject $c, array $a) + { + $class = get_class($c); + $flags = $c->getFlags(); + + $b = array( + "\0~\0flag::STD_PROP_LIST" => (bool) ($flags & \ArrayObject::STD_PROP_LIST), + "\0~\0flag::ARRAY_AS_PROPS" => (bool) ($flags & \ArrayObject::ARRAY_AS_PROPS), + "\0~\0iteratorClass" => $c->getIteratorClass(), + "\0~\0storage" => $c->getArrayCopy(), + ); + + if ($class === 'ArrayObject') { + $a = $b; + } else { + if (!($flags & \ArrayObject::STD_PROP_LIST)) { + $c->setFlags(\ArrayObject::STD_PROP_LIST); + + if ($a = (array) $c) { + $class = new \ReflectionClass($class); + foreach ($a as $k => $p) { + if (!isset($k[0]) || ("\0" !== $k[0] && !$class->hasProperty($k))) { + unset($a[$k]); + $a["\0+\0".$k] = $p; + } + } + } + + $c->setFlags($flags); + } + + $a += $b; + } + + return $a; + } + + public static function castHeap(\Iterator $c, array $a) + { + $a += array( + "\0~\0heap" => iterator_to_array(clone $c), + ); + + return $a; + } + + public static function castDoublyLinkedList(\SplDoublyLinkedList $c, array $a) + { + $mode = $c->getIteratorMode(); + $c->setIteratorMode(\SplDoublyLinkedList::IT_MODE_KEEP | $mode & ~\SplDoublyLinkedList::IT_MODE_DELETE); + + $a += array( + "\0~\0mode" => (($mode & \SplDoublyLinkedList::IT_MODE_LIFO) ? 'IT_MODE_LIFO' : 'IT_MODE_FIFO').' | '.(($mode & \SplDoublyLinkedList::IT_MODE_KEEP) ? 'IT_MODE_KEEP' : 'IT_MODE_DELETE'), + "\0~\0dllist" => iterator_to_array($c), + ); + $c->setIteratorMode($mode); + + return $a; + } + + public static function castFixedArray(\SplFixedArray $c, array $a) + { + $a += array( + "\0~\0storage" => $c->toArray(), + ); + + return $a; + } + + public static function castObjectStorage(\SplObjectStorage $c, array $a) + { + $storage = array(); + unset($a["\0+\0\0gcdata"]); // Don't hit https://bugs.php.net/65967 + + foreach ($c as $obj) { + $storage[spl_object_hash($obj)] = array( + 'object' => $obj, + 'info' => $c->getInfo(), + ); + } + + $a += array( + "\0~\0storage" => $storage, + ); + + return $a; + } +} diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index 73dbfcc601eba..9aac06982fc1f 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -29,6 +29,13 @@ abstract class AbstractCloner implements ClonerInterface 'o:Symfony\Component\VarDumper\Exception\ThrowingCasterException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castThrowingCasterException', + 'o:ArrayObject' => 'Symfony\Component\VarDumper\Caster\SplCaster::castArrayObject', + 'o:SplDoublyLinkedList' => 'Symfony\Component\VarDumper\Caster\SplCaster::castDoublyLinkedList', + 'o:SplFixedArray' => 'Symfony\Component\VarDumper\Caster\SplCaster::castFixedArray', + 'o:SplHeap' => 'Symfony\Component\VarDumper\Caster\SplCaster::castHeap', + 'o:SplObjectStorage' => 'Symfony\Component\VarDumper\Caster\SplCaster::castObjectStorage', + 'o:SplPriorityQueue' => 'Symfony\Component\VarDumper\Caster\SplCaster::castHeap', + 'r:curl' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castCurl', 'r:dba' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba', 'r:dba persistent' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba', From 0a92c0869977ddda5831463f8fd8e1a16a18d69b Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 5 Apr 2014 20:58:59 +0200 Subject: [PATCH 08/31] [VarDumper] casters for PDO related objects --- .../Component/VarDumper/Caster/PdoCaster.php | 112 ++++++++++++++++++ .../VarDumper/Cloner/AbstractCloner.php | 3 + 2 files changed, 115 insertions(+) create mode 100644 src/Symfony/Component/VarDumper/Caster/PdoCaster.php diff --git a/src/Symfony/Component/VarDumper/Caster/PdoCaster.php b/src/Symfony/Component/VarDumper/Caster/PdoCaster.php new file mode 100644 index 0000000000000..0b1c2d17efcd4 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Caster/PdoCaster.php @@ -0,0 +1,112 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Casts PDO related classes to array representation. + * + * @author Nicolas Grekas + */ +class PdoCaster +{ + private static $pdoAttributes = array( + 'CASE' => array( + \PDO::CASE_LOWER => 'LOWER', + \PDO::CASE_NATURAL => 'NATURAL', + \PDO::CASE_UPPER => 'UPPER', + ), + 'ERRMODE' => array( + \PDO::ERRMODE_SILENT => 'SILENT', + \PDO::ERRMODE_WARNING => 'WARNING', + \PDO::ERRMODE_EXCEPTION => 'EXCEPTION', + ), + 'TIMEOUT', + 'PREFETCH', + 'AUTOCOMMIT', + 'PERSISTENT', + 'DRIVER_NAME', + 'SERVER_INFO', + 'ORACLE_NULLS' => array( + \PDO::NULL_NATURAL => 'NATURAL', + \PDO::NULL_EMPTY_STRING => 'EMPTY_STRING', + \PDO::NULL_TO_STRING => 'TO_STRING', + ), + 'CLIENT_VERSION', + 'SERVER_VERSION', + 'STATEMENT_CLASS', + 'EMULATE_PREPARES', + 'CONNECTION_STATUS', + 'STRINGIFY_FETCHES', + 'DEFAULT_FETCH_MODE' => array( + \PDO::FETCH_ASSOC => 'ASSOC', + \PDO::FETCH_BOTH => 'BOTH', + \PDO::FETCH_LAZY => 'LAZY', + \PDO::FETCH_NUM => 'NUM', + \PDO::FETCH_OBJ => 'OBJ', + ), + ); + + public static function castPdo(\PDO $c, array $a) + { + $a = array(); + $errmode = $c->getAttribute(\PDO::ATTR_ERRMODE); + $c->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); + + foreach (self::$pdoAttributes as $attr => $values) { + if (!isset($attr[0])) { + $attr = $values; + $values = array(); + } + + try { + $a[$attr] = 'ERRMODE' === $attr ? $errmode : $c->getAttribute(constant("PDO::ATTR_{$attr}")); + if (isset($values[$a[$attr]])) { + $a[$attr] = $values[$a[$attr]]; + } + } catch (\Exception $m) { + } + } + + $m = "\0~\0"; + $a = (array) $c + array( + $m.'inTransaction' => method_exists($c, 'inTransaction'), + $m.'errorInfo' => $c->errorInfo(), + $m.'attributes' => $a, + ); + + if ($a[$m.'inTransaction']) { + $a[$m.'inTransaction'] = $c->inTransaction(); + } else { + unset($a[$m.'inTransaction']); + } + + if (!isset($a[$m.'errorInfo'][1], $a[$m.'errorInfo'][2])) { + unset($a[$m.'errorInfo']); + } + + $c->setAttribute(\PDO::ATTR_ERRMODE, $errmode); + + return $a; + } + + public static function castPdoStatement(\PDOStatement $c, array $a) + { + $m = "\0~\0"; + $a[$m.'errorInfo'] = $c->errorInfo(); + + if (!isset($a[$m.'errorInfo'][1], $a[$m.'errorInfo'][2])) { + unset($a[$m.'errorInfo']); + } + + return $a; + } +} diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index 9aac06982fc1f..01cfd7f763c41 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -29,6 +29,9 @@ abstract class AbstractCloner implements ClonerInterface 'o:Symfony\Component\VarDumper\Exception\ThrowingCasterException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castThrowingCasterException', + 'o:PDO' => 'Symfony\Component\VarDumper\Caster\PdoCaster::castPdo', + 'o:PDOStatement' => 'Symfony\Component\VarDumper\Caster\PdoCaster::castPdoStatement', + 'o:ArrayObject' => 'Symfony\Component\VarDumper\Caster\SplCaster::castArrayObject', 'o:SplDoublyLinkedList' => 'Symfony\Component\VarDumper\Caster\SplCaster::castDoublyLinkedList', 'o:SplFixedArray' => 'Symfony\Component\VarDumper\Caster\SplCaster::castFixedArray', From c426d8bc091869e7575ff1c5822010af8d368c08 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 5 Apr 2014 21:01:17 +0200 Subject: [PATCH 09/31] [VarDumper] casters for Doctrine objects --- .../VarDumper/Caster/DoctrineCaster.php | 73 +++++++++++++++++++ .../VarDumper/Cloner/AbstractCloner.php | 5 ++ 2 files changed, 78 insertions(+) create mode 100644 src/Symfony/Component/VarDumper/Caster/DoctrineCaster.php diff --git a/src/Symfony/Component/VarDumper/Caster/DoctrineCaster.php b/src/Symfony/Component/VarDumper/Caster/DoctrineCaster.php new file mode 100644 index 0000000000000..49e1b7bbed3f7 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Caster/DoctrineCaster.php @@ -0,0 +1,73 @@ + + * + * 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 Doctrine\Common\Persistence\ObjectManager; +use Doctrine\Common\Proxy\Proxy as CommonProxy; +use Doctrine\ORM\Proxy\Proxy as OrmProxy; +use Doctrine\ORM\PersistentCollection; + +/** + * Casts Doctrine related classes to array representation. + * + * @author Nicolas Grekas + */ +class DoctrineCaster +{ + public static function castCommonProxy(CommonProxy $proxy, array $a, $isNested, &$cut) + { + unset( + $a['__cloner__'], + $a['__initializer__'] + ); + $cut += 2; + + return $a; + } + + public static function castOrmProxy(OrmProxy $proxy, array $a, $isNested, &$cut) + { + $prefix = "\0Doctrine\\ORM\\Proxy\\Proxy\0"; + unset( + $a[$prefix.'_entityPersister'], + $a[$prefix.'_identifier'] + ); + $cut += 2; + + return $a; + } + + public static function castObjectManager(ObjectManager $manager, array $a, $isNested, &$cut) + { + if ($isNested) { + $cut += count($a); + + return array(); + } + + return $a; + } + + public static function castPersistentCollection(PersistentCollection $coll, array $a, $isNested, &$cut) + { + $prefix = "\0Doctrine\\ORM\\PersistentCollection\0"; + unset( + $a[$prefix.'snapshot'], + $a[$prefix.'association'], + $a[$prefix.'em'], + $a[$prefix.'typeClass'] + ); + $cut += 4; + + return $a; + } +} diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index 01cfd7f763c41..f61cde388b3b0 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -24,6 +24,11 @@ abstract class AbstractCloner implements ClonerInterface 'o:Closure' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castClosure', 'o:Reflector' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castReflector', + 'o:Doctrine\Common\Persistence\ObjectManager' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castObjectManager', + 'o:Doctrine\Common\Proxy\Proxy' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castCommonProxy', + 'o:Doctrine\ORM\Proxy\Proxy' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castOrmProxy', + 'o:Doctrine\ORM\PersistentCollection' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castPersistentCollection', + 'o:ErrorException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castErrorException', 'o:Exception' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castException', 'o:Symfony\Component\VarDumper\Exception\ThrowingCasterException' From 0266072bb3c49e42bce46e8dc845226e3fe0b3d4 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sun, 24 Aug 2014 11:28:24 +0200 Subject: [PATCH 10/31] [VarDumper] casters for DOM objects --- .../Component/VarDumper/Caster/DOMCaster.php | 279 ++++++++++++++++++ .../VarDumper/Cloner/AbstractCloner.php | 23 ++ 2 files changed, 302 insertions(+) create mode 100644 src/Symfony/Component/VarDumper/Caster/DOMCaster.php diff --git a/src/Symfony/Component/VarDumper/Caster/DOMCaster.php b/src/Symfony/Component/VarDumper/Caster/DOMCaster.php new file mode 100644 index 0000000000000..7d227d8a71d74 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Caster/DOMCaster.php @@ -0,0 +1,279 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Caster; + +/** + * Casts DOM related classes to array representation. + * + * @author Nicolas Grekas + */ +class DOMCaster +{ + private static $errorCodes = array( + DOM_PHP_ERR => 'DOM_PHP_ERR', + DOM_INDEX_SIZE_ERR => 'DOM_INDEX_SIZE_ERR', + DOMSTRING_SIZE_ERR => 'DOMSTRING_SIZE_ERR', + DOM_HIERARCHY_REQUEST_ERR => 'DOM_HIERARCHY_REQUEST_ERR', + DOM_WRONG_DOCUMENT_ERR => 'DOM_WRONG_DOCUMENT_ERR', + DOM_INVALID_CHARACTER_ERR => 'DOM_INVALID_CHARACTER_ERR', + DOM_NO_DATA_ALLOWED_ERR => 'DOM_NO_DATA_ALLOWED_ERR', + DOM_NO_MODIFICATION_ALLOWED_ERR => 'DOM_NO_MODIFICATION_ALLOWED_ERR', + DOM_NOT_FOUND_ERR => 'DOM_NOT_FOUND_ERR', + DOM_NOT_SUPPORTED_ERR => 'DOM_NOT_SUPPORTED_ERR', + DOM_INUSE_ATTRIBUTE_ERR => 'DOM_INUSE_ATTRIBUTE_ERR', + DOM_INVALID_STATE_ERR => 'DOM_INVALID_STATE_ERR', + DOM_SYNTAX_ERR => 'DOM_SYNTAX_ERR', + DOM_INVALID_MODIFICATION_ERR => 'DOM_INVALID_MODIFICATION_ERR', + DOM_NAMESPACE_ERR => 'DOM_NAMESPACE_ERR', + DOM_INVALID_ACCESS_ERR => 'DOM_INVALID_ACCESS_ERR', + DOM_VALIDATION_ERR => 'DOM_VALIDATION_ERR', + ); + + public static function castException(\DOMException $e, array $a, $isNested, &$cut) + { + if (isset($a["\0*\0code"], self::$errorCodes[$a["\0*\0code"]])) { + $a["\0*\0code"] .= ' ('.self::$errorCodes[$a["\0*\0code"]].')'; + } + + return $a; + } + + public static function castLength($dom, array $a, $isNested, &$cut) + { + $a += array( + 'length' => $dom->length, + ); + + return $a; + } + + public static function castImplementation($dom, array $a, $isNested, &$cut) + { + $a += array( + "\0~\0Core" => '1.0', + "\0~\0XML" => '2.0', + ); + + return $a; + } + + public static function castNode(\DOMNode $dom, array $a, $isNested, &$cut) + { + $a += array( + 'nodeName' => $dom->nodeName, + //'nodeValue' => $dom->nodeValue, + 'nodeType' => $dom->nodeType, + //'parentNode' => $dom->parentNode, + 'childNodes' => $dom->childNodes, + //'firstChild' => $dom->firstChild, + //'lastChild' => $dom->lastChild, + //'previousSibling' => $dom->previousSibling, + //'nextSibling' => $dom->nextSibling, + 'attributes' => $dom->attributes, + //'ownerDocument' => $dom->ownerDocument, + 'namespaceURI' => $dom->namespaceURI, + 'prefix' => $dom->prefix, + 'localName' => $dom->localName, + 'baseURI' => $dom->baseURI, + //'textContent' => $dom->textContent, + ); + $cut += 8; + + return $a; + } + + public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, $isNested, &$cut) + { + $a += array( + 'nodeName' => $dom->nodeName, + //'nodeValue' => $dom->nodeValue, + 'nodeType' => $dom->nodeType, + 'prefix' => $dom->prefix, + 'localName' => $dom->localName, + 'namespaceURI' => $dom->namespaceURI, + //'ownerDocument' => $dom->ownerDocument, + //'parentNode' => $dom->parentNode, + ); + $cut += 3; + + return $a; + } + + public static function castDocument(\DOMDocument $dom, array $a, $isNested, &$cut) + { + $formatOutput = $dom->formatOutput; + $dom->formatOutput = true; + + $a += array( + 'doctype' => $dom->doctype, + 'implementation' => $dom->implementation, + 'documentElement' => $dom->documentElement, + 'actualEncoding' => $dom->actualEncoding, + 'encoding' => $dom->encoding, + 'xmlEncoding' => $dom->xmlEncoding, + 'standalone' => $dom->standalone, + 'xmlStandalone' => $dom->xmlStandalone, + 'version' => $dom->version, + 'xmlVersion' => $dom->xmlVersion, + 'strictErrorChecking' => $dom->strictErrorChecking, + 'documentURI' => $dom->documentURI, + 'config' => $dom->config, + 'formatOutput' => $formatOutput, + 'validateOnParse' => $dom->validateOnParse, + 'resolveExternals' => $dom->resolveExternals, + 'preserveWhiteSpace' => $dom->preserveWhiteSpace, + 'recover' => $dom->recover, + 'substituteEntities' => $dom->substituteEntities, + "\0~\0xml" => $dom->saveXML(), + ); + + $dom->formatOutput = $formatOutput; + + return $a; + } + + public static function castCharacterData(\DOMCharacterData $dom, array $a, $isNested, &$cut) + { + $a += array( + 'data' => $dom->data, + 'length' => $dom->length, + ); + + return $a; + } + + public static function castAttr(\DOMAttr $dom, array $a, $isNested, &$cut) + { + $a += array( + 'name' => $dom->name, + 'specified' => $dom->specified, + 'value' => $dom->value, + 'ownerElement' => $dom->ownerElement, + 'schemaTypeInfo' => $dom->schemaTypeInfo, + ); + + return $a; + } + + public static function castElement(\DOMElement $dom, array $a, $isNested, &$cut) + { + $a += array( + 'tagName' => $dom->tagName, + 'schemaTypeInfo' => $dom->schemaTypeInfo, + ); + + return $a; + } + + public static function castText(\DOMText $dom, array $a, $isNested, &$cut) + { + $a += array( + 'wholeText' => $dom->wholeText, + ); + + return $a; + } + + public static function castTypeinfo(\DOMTypeinfo $dom, array $a, $isNested, &$cut) + { + $a += array( + 'typeName' => $dom->typeName, + 'typeNamespace' => $dom->typeNamespace, + ); + + return $a; + } + + public static function castDomError(\DOMDomError $dom, array $a, $isNested, &$cut) + { + $a += array( + 'severity' => $dom->severity, + 'message' => $dom->message, + 'type' => $dom->type, + 'relatedException' => $dom->relatedException, + 'related_data' => $dom->related_data, + 'location' => $dom->location, + ); + + return $a; + } + + public static function castLocator(\DOMLocator $dom, array $a, $isNested, &$cut) + { + $a += array( + 'lineNumber' => $dom->lineNumber, + 'columnNumber' => $dom->columnNumber, + 'offset' => $dom->offset, + 'relatedNode' => $dom->relatedNode, + 'uri' => $dom->uri, + ); + + return $a; + } + + public static function castDocumentType(\DOMDocumentType $dom, array $a, $isNested, &$cut) + { + $a += array( + 'name' => $dom->name, + 'entities' => $dom->entities, + 'notations' => $dom->notations, + 'publicId' => $dom->publicId, + 'systemId' => $dom->systemId, + 'internalSubset' => $dom->internalSubset, + ); + + return $a; + } + + public static function castNotation(\DOMNotation $dom, array $a, $isNested, &$cut) + { + $a += array( + 'publicId' => $dom->publicId, + 'systemId' => $dom->systemId, + ); + + return $a; + } + + public static function castEntity(\DOMEntity $dom, array $a, $isNested, &$cut) + { + $a += array( + 'publicId' => $dom->publicId, + 'systemId' => $dom->systemId, + 'notationName' => $dom->notationName, + 'actualEncoding' => $dom->actualEncoding, + 'encoding' => $dom->encoding, + 'version' => $dom->version, + ); + + return $a; + } + + public static function castProcessingInstruction(\DOMProcessingInstruction $dom, array $a, $isNested, &$cut) + { + $a += array( + 'target' => $dom->target, + 'data' => $dom->data, + ); + + return $a; + } + + public static function castXPath(\DOMXPath $dom, array $a, $isNested, &$cut) + { + $a += array( + 'document' => $dom->document, + ); + + return $a; + } +} diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index f61cde388b3b0..69fd84e37ef70 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -29,6 +29,29 @@ abstract class AbstractCloner implements ClonerInterface 'o:Doctrine\ORM\Proxy\Proxy' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castOrmProxy', 'o:Doctrine\ORM\PersistentCollection' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castPersistentCollection', + 'o:DOMException' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castException', + 'o:DOMStringList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength', + 'o:DOMNameList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength', + 'o:DOMImplementation' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castImplementation', + 'o:DOMImplementationList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength', + 'o:DOMNode' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNode', + 'o:DOMNameSpaceNode' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNameSpaceNode', + 'o:DOMDocument' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDocument', + 'o:DOMNodeList' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength', + 'o:DOMNamedNodeMap' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength', + 'o:DOMCharacterData' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castCharacterData', + 'o:DOMAttr' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castAttr', + 'o:DOMElement' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castElement', + 'o:DOMText' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castText', + 'o:DOMTypeinfo' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castTypeinfo', + 'o:DOMDomError' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDomError', + 'o:DOMLocator' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLocator', + 'o:DOMDocumentType' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDocumentType', + 'o:DOMNotation' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNotation', + 'o:DOMEntity' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castEntity', + 'o:DOMProcessingInstruction' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castProcessingInstruction', + 'o:DOMXPath' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castXPath', + 'o:ErrorException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castErrorException', 'o:Exception' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castException', 'o:Symfony\Component\VarDumper\Exception\ThrowingCasterException' From 1d5e3f4dc59d0ec7d9ecf0e3c36c2d6905566e21 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 5 Apr 2014 21:13:51 +0200 Subject: [PATCH 11/31] [VarDumper] interface for dumping collected variables --- .../Component/VarDumper/Cloner/Data.php | 168 ++++++++++++++++++ .../Component/VarDumper/Dumper/Cursor.php | 36 ++++ .../VarDumper/Dumper/DataDumperInterface.php | 29 +++ .../Dumper/DumperInternalsInterface.php | 98 ++++++++++ 4 files changed, 331 insertions(+) create mode 100644 src/Symfony/Component/VarDumper/Dumper/Cursor.php create mode 100644 src/Symfony/Component/VarDumper/Dumper/DataDumperInterface.php create mode 100644 src/Symfony/Component/VarDumper/Dumper/DumperInternalsInterface.php diff --git a/src/Symfony/Component/VarDumper/Cloner/Data.php b/src/Symfony/Component/VarDumper/Cloner/Data.php index 038cad53bb72a..7361b839877d0 100644 --- a/src/Symfony/Component/VarDumper/Cloner/Data.php +++ b/src/Symfony/Component/VarDumper/Cloner/Data.php @@ -11,6 +11,9 @@ namespace Symfony\Component\VarDumper\Cloner; +use Symfony\Component\VarDumper\Dumper\DumperInternalsInterface; +use Symfony\Component\VarDumper\Dumper\Cursor; + /** * @author Nicolas Grekas */ @@ -30,4 +33,169 @@ public function getRawData() { return $this->data; } + + /** + * Dumps data with a DumperInternalsInterface dumper. + */ + public function dump(DumperInternalsInterface $dumper) + { + $refs = array(0); + $this->dumpItem($dumper, new Cursor, $refs, $this->data[0][0]); + } + + /** + * Breadth-first dumping of items. + * + * @param DumperInternalsInterface $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 stdClass or the original value being dumped. + */ + private function dumpItem($dumper, $cursor, &$refs, $item) + { + $cursor->refIndex = $cursor->refTo = $cursor->refIsHard = false; + + if ($item instanceof \stdClass) { + if (property_exists($item, 'val')) { + if (isset($item->ref)) { + if (isset($refs[$r = $item->ref])) { + $cursor->refTo = $refs[$r]; + $cursor->refIsHard = true; + } else { + $cursor->refIndex = $refs[$r] = ++$refs[0]; + } + } + $item = $item->val; + } + if (isset($item->ref)) { + if (isset($refs[$r = $item->ref])) { + if (false === $cursor->refTo) { + $cursor->refTo = $refs[$r]; + $cursor->refIsHard = isset($item->count); + } + } elseif (false !== $cursor->refIndex) { + $refs[$r] = $cursor->refIndex; + } else { + $cursor->refIndex = $refs[$r] = ++$refs[0]; + } + } + $cut = isset($item->cut) ? $item->cut : 0; + + if (isset($item->pos) && false === $cursor->refTo) { + $children = $this->data[$item->pos]; + + if ($cursor->stop) { + if ($cut >= 0) { + $cut += count($children); + } + $children = array(); + } + } else { + $children = array(); + } + switch (true) { + case isset($item->bin): + $dumper->dumpString($cursor, $item->bin, true, $cut); + + return; + + case isset($item->str): + $dumper->dumpString($cursor, $item->str, false, $cut); + + return; + + case isset($item->count): + $dumper->enterArray($cursor, $item->count, !empty($item->indexed), (bool) $children); + $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, empty($item->indexed) ? $cursor::HASH_ASSOC : $cursor::HASH_INDEXED); + $dumper->leaveArray($cursor, $item->count, !empty($item->indexed), (bool) $children, $cut); + + return; + + case isset($item->class): + $dumper->enterObject($cursor, $item->class, (bool) $children); + $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $cursor::HASH_OBJECT); + $dumper->leaveObject($cursor, $item->class, (bool) $children, $cut); + + return; + + case isset($item->res): + $dumper->enterResource($cursor, $item->res, (bool) $children); + $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $cursor::HASH_RESOURCE); + $dumper->leaveResource($cursor, $item->res, (bool) $children, $cut); + + return; + } + } + + if ('array' === $type = gettype($item)) { + $dumper->enterArray($cursor, 0, true, 0, 0); + $dumper->leaveArray($cursor, 0, true, 0, 0); + } else { + $dumper->dumpScalar($cursor, $type, $item); + } + } + + /** + * Dumps children of hash structures. + * + * @param DumperInternalsInterface $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 int $hashType A Cursor::HASH_* const. + * + * @return int The final number of removed items. + */ + private function dumpChildren($dumper, $parentCursor, &$refs, $children, $hashCut, $hashType) + { + if ($children) { + $cursor = clone $parentCursor; + ++$cursor->depth; + $cursor->hashType = $hashType; + $cursor->hashIndex = 0; + $cursor->hashLength = count($children); + $cursor->hashCut = $hashCut; + foreach ($children as $cursor->hashKey => $child) { + $this->dumpItem($dumper, $cursor, $refs, $child); + ++$cursor->hashIndex; + if ($cursor->stop) { + $parentCursor->stop = true; + + return $hashCut >= 0 ? $hashCut + $children - $cursor->hashIndex : $hashCut; + } + } + } + + return $hashCut; + } + + /** + * Portable variant of utf8_encode() + * + * @param string $s + * + * @return string + * + * @internal + */ + public static function utf8Encode($s) + { + if (function_exists('iconv')) { + return iconv('CP1252', 'UTF-8', $s); + } else { + $s .= $s; + $len = strlen($s); + + for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) { + switch (true) { + case $s[$i] < "\x80": $s[$j] = $s[$i]; break; + case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break; + default: $s[$j] = "\xC3"; $s[++$j] = chr(ord($s[$i]) - 64); break; + } + } + + return substr($s, 0, $j); + } + } } diff --git a/src/Symfony/Component/VarDumper/Dumper/Cursor.php b/src/Symfony/Component/VarDumper/Dumper/Cursor.php new file mode 100644 index 0000000000000..6e96e1fd058b3 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Dumper/Cursor.php @@ -0,0 +1,36 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +/** + * Represents the current state of a dumper while dumping. + * + * @author Nicolas Grekas + */ +class Cursor +{ + const HASH_INDEXED = 'indexed-array'; + const HASH_ASSOC = 'associative-array'; + const HASH_OBJECT = 'object'; + const HASH_RESOURCE = 'resource'; + + public $depth = 0; + public $refIndex = false; + public $refTo = false; + public $refIsHard = false; + public $hashType; + public $hashKey; + public $hashIndex = 0; + public $hashLength = 0; + public $hashCut = 0; + public $stop = false; +} diff --git a/src/Symfony/Component/VarDumper/Dumper/DataDumperInterface.php b/src/Symfony/Component/VarDumper/Dumper/DataDumperInterface.php new file mode 100644 index 0000000000000..ee6060cebf647 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Dumper/DataDumperInterface.php @@ -0,0 +1,29 @@ + + * + * 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; + +/** + * DataDumperInterface for dumping Data objects. + * + * @author Nicolas Grekas + */ +interface DataDumperInterface +{ + /** + * Dumps a Data object. + * + * @param Data $data A Data object. + */ + public function dump(Data $data); +} diff --git a/src/Symfony/Component/VarDumper/Dumper/DumperInternalsInterface.php b/src/Symfony/Component/VarDumper/Dumper/DumperInternalsInterface.php new file mode 100644 index 0000000000000..d60974efdda0b --- /dev/null +++ b/src/Symfony/Component/VarDumper/Dumper/DumperInternalsInterface.php @@ -0,0 +1,98 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +/** + * DumperInterface used by Data objects. + * + * @author Nicolas Grekas + */ +interface DumperInternalsInterface +{ + /** + * Dumps a scalar value. + * + * @param Cursor $cursor The Cursor position in the dump. + * @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); + + /** + * Dumps a string. + * + * @param Cursor $cursor The Cursor position in the dump. + * @param string $str The string being dumped. + * @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); + + /** + * Dumps while entering an array. + * + * @param Cursor $cursor The Cursor position in the dump. + * @param int $count The number of items in the original array. + * @param bool $indexed When the array is indexed or associative. + * @param bool $hasChild When the dump of the array has child item. + */ + public function enterArray(Cursor $cursor, $count, $indexed, $hasChild); + + /** + * Dumps while leaving an array. + * + * @param Cursor $cursor The Cursor position in the dump. + * @param int $count The number of items in the original array. + * @param bool $indexed Whether the array is indexed or associative. + * @param bool $hasChild When the dump of the array has child item. + * @param int $cut The number of items the array has been cut by. + */ + public function leaveArray(Cursor $cursor, $count, $indexed, $hasChild, $cut); + + /** + * Dumps while entering an object. + * + * @param Cursor $cursor The Cursor position in the dump. + * @param string $class The class of the object. + * @param bool $hasChild When the dump of the object has child item. + */ + public function enterObject(Cursor $cursor, $class, $hasChild); + + /** + * Dumps while leaving an object. + * + * @param Cursor $cursor The Cursor position in the dump. + * @param string $class The class of the object. + * @param bool $hasChild When the dump of the object has child item. + * @param int $cut The number of items the object has been cut by. + */ + public function leaveObject(Cursor $cursor, $class, $hasChild, $cut); + + /** + * Dumps while entering a resource. + * + * @param Cursor $cursor The Cursor position in the dump. + * @param string $res The resource type. + * @param bool $hasChild When the dump of the resource has child item. + */ + public function enterResource(Cursor $cursor, $res, $hasChild); + + /** + * Dumps while leaving a resource. + * + * @param Cursor $cursor The Cursor position in the dump. + * @param string $res The resource type. + * @param bool $hasChild When the dump of the resource has child item. + * @param int $cut The number of items the resource has been cut by. + */ + public function leaveResource(Cursor $cursor, $res, $hasChild, $cut); +} From fa81544075ec2657bdea3a50933072ed77c35a66 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 5 Apr 2014 21:19:44 +0200 Subject: [PATCH 12/31] [VarDumper] CLI dedicated dumper and related abstract --- .../VarDumper/Dumper/AbstractDumper.php | 123 +++++ .../Component/VarDumper/Dumper/CliDumper.php | 434 ++++++++++++++++++ 2 files changed, 557 insertions(+) create mode 100644 src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php create mode 100644 src/Symfony/Component/VarDumper/Dumper/CliDumper.php diff --git a/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php b/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php new file mode 100644 index 0000000000000..708dc493ec23f --- /dev/null +++ b/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php @@ -0,0 +1,123 @@ + + * + * 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; + +/** + * Abstract mechanism for dumping a Data object. + * + * @author Nicolas Grekas + */ +abstract class AbstractDumper implements DataDumperInterface, DumperInternalsInterface +{ + public static $defaultOutputStream = 'php://output'; + + protected $line = ''; + protected $lineDumper; + protected $outputStream; + protected $decimalPoint; // This is locale dependent + protected $indentPad = ' '; + + /** + * @param callable|resource|string|null $outputStream A line dumper callable, an opened stream or an output path, defaults to static::$defaultOutputStream. + */ + public function __construct($outputStream = null) + { + $this->decimalPoint = (string) 0.5; + $this->decimalPoint = $this->decimalPoint[1]; + if (is_callable($outputStream)) { + $this->setLineDumper($outputStream); + } else { + if (null === $outputStream) { + $outputStream =& static::$defaultOutputStream; + } + if (is_string($outputStream)) { + $outputStream = fopen($outputStream, 'wb'); + } + $this->outputStream = $outputStream; + $this->setLineDumper(array($this, 'echoLine')); + } + } + + /** + * Sets a line dumper callback. + * + * @param callable $callback A callback responsible for writing the dump, one line at a time. + * + * @return callable|null The previous line dumper. + */ + public function setLineDumper($callback) + { + $prev = $this->lineDumper; + $this->lineDumper = $callback; + + return $prev; + } + + /** + * Sets the indentation pad string. + * + * @param string $pad A string the will be prepended to dumped lines, repeated by nesting level. + * + * @return string The indent pad. + */ + public function setIndentPad($pad) + { + $prev = $this->indentPad; + $this->indentPad = $pad; + + return $prev; + } + + /** + * Dumps a Data object. + * + * @param Data $data A Data object. + * @param callable|null $lineDumper A callback for writing dump's lines. + */ + public function dump(Data $data, $lineDumper = null) + { + $this->decimalPoint = (string) 0.5; + $this->decimalPoint = $this->decimalPoint[1]; + $dumper = clone $this; + if ($lineDumper) { + $dumper->setLineDumper($lineDumper); + } + $data->dump($dumper); + $dumper->dumpLine(false); + } + + /** + * Dumps the current line. + * + * @param int $depth The recursive depth in the dumped structure for the line being dumped. + */ + protected function dumpLine($depth) + { + call_user_func($this->lineDumper, $this->line, $depth); + $this->line = ''; + } + + /** + * Generic line dumper callback. + * + * @param string $line The line to write. + * @param int $depth The recursive depth in the dumped structure. + */ + protected function echoLine($line, $depth) + { + if (false !== $depth) { + fwrite($this->outputStream, str_repeat($this->indentPad, $depth).$line."\n"); + } + } +} diff --git a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php new file mode 100644 index 0000000000000..8b2020211e83a --- /dev/null +++ b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php @@ -0,0 +1,434 @@ + + * + * 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; + +/** + * CliDumper dumps variables for command line output. + * + * @author Nicolas Grekas + */ +class CliDumper extends AbstractDumper +{ + public static $defaultColors; + public static $defaultOutputStream = 'php://stdout'; + + protected $colors; + protected $maxStringWidth = 0; + protected $styles = array( + // See http://en.wikipedia.org/wiki/ANSI_escape_code#graphics + 'num' => '1;38;5;33', + 'const' => '1;38;5;33', + 'str' => '1;38;5;37', + 'cchr' => '7', + 'note' => '38;5;178', + 'ref' => '38;5;245', + 'public' => '38;5;28', + 'protected' => '38;5;166', + 'private' => '38;5;160', + 'meta' => '38;5;27', + ); + + protected static $controlChars = array( + "\x1B", // ESC must be the first + "\x00", "\x01", "\x02", "\x03", "\x04", "\x05", "\x06", "\x07", + "\x08", "\x09", "\x0A", "\x0B", "\x0C", "\x0D", "\x0E", "\x0F", + "\x10", "\x11", "\x12", "\x13", "\x14", "\x15", "\x16", "\x17", + "\x18", "\x19", "\x1A", "\x1C", "\x1D", "\x1E", "\x1F", "\x7F", + ); + + /** + * Enables/disables colored output. + * + * @param bool $colors + */ + public function setColors($colors) + { + $this->colors = (bool) $colors; + } + + /** + * Sets the maximum number of characters per line for dumped strings. + * + * @param int $maxStringWidth + */ + public function setMaxStringWidth($maxStringWidth) + { + if (function_exists('iconv')) { + $this->maxStringWidth = (int) $maxStringWidth; + } + } + + /** + * Configures styles. + * + * @param array $styles A map of style namaes to style definitions. + */ + public function setStyles(array $styles) + { + $this->styles = $styles + $this->styles; + } + + /** + * {@inheritdoc} + */ + public function dumpScalar(Cursor $cursor, $type, $val) + { + if ('string' === $type) { + return $this->dumpString($cursor, $val, false, 0); + } + + $this->dumpKey($cursor); + + $style = 'const'; + + switch ($type) { + case 'int': + $style = 'num'; + break; + + case 'double': + $style = 'num'; + + switch (true) { + case INF === $val: $val = 'INF'; break; + case -INF === $val: $val = '-INF'; break; + case is_nan($val): $val = 'NAN'; break; + default: + $val = (string) $val; + if (false === strpos($val, $this->decimalPoint)) { + $val .= $this->decimalPoint.'0'; + } + break; + } + break; + + case 'NULL': + $val = 'null'; + break; + + case 'boolean': + $val = $val ? 'true' : 'false'; + break; + } + + $this->line .= $this->style($style, $val); + + if (false !== $cursor->refTo) { + $this->line .= ' '.$this->style('ref', '&'.$cursor->refTo); + } + + $this->endLine($cursor); + } + + /** + * {@inheritdoc} + */ + public function dumpString(Cursor $cursor, $str, $bin, $cut) + { + $this->dumpKey($cursor); + + if ('' === $str) { + $this->line .= '""'; + if (false !== $cursor->refTo) { + $this->line .= ' '.$this->style('ref', '&'.$cursor->refTo); + } + $this->endLine($cursor); + } else { + $str = explode("\n", $str); + $m = count($str) - 1; + $i = 0; + + if ($bin) { + $this->line .= 'b'; + } + + if ($m) { + $this->line .= '"""'; + if (false !== $cursor->refTo) { + $this->line .= $this->style('ref', '&'.$cursor->refTo); + } + $this->endLine($cursor); + } else { + $this->line .= '"'; + } + + foreach ($str as $str) { + if (0 < $this->maxStringWidth && $this->maxStringWidth < $len = iconv_strlen($str, 'UTF-8')) { + $str = iconv_substr($str, 0, $this->maxStringWidth - 1, 'UTF-8'); + $str = $this->style('str', $str).'…'; + } else { + $str = $this->style('str', $str); + } + + if ($m) { + $this->line .= $this->indentPad; + } + $this->line .= $str; + + if ($i++ == $m) { + if ($cut) { + if (0 >= $this->maxStringWidth || $this->maxStringWidth >= $len) { + $this->line .= '…'; + } + $this->line .= $cut; + } elseif ($m) { + $this->line .= '"""'; + } else { + $this->line .= '"'; + } + if (!$m && false !== $cursor->refTo) { + $this->line .= $this->style('ref', '&'.$cursor->refTo); + } + } + + $this->endLine($cursor, !$m); + } + } + } + + /** + * {@inheritdoc} + */ + public function enterArray(Cursor $cursor, $count, $indexed, $hasChild) + { + $this->enterHash($cursor, $count ? $this->style('note', 'array:'.$count).' [' : '[', $hasChild); + } + + /** + * {@inheritdoc} + */ + public function leaveArray(Cursor $cursor, $count, $indexed, $hasChild, $cut) + { + $this->leaveHash($cursor, ']', $hasChild, $cut); + } + + /** + * {@inheritdoc} + */ + public function enterObject(Cursor $cursor, $class, $hasChild) + { + $this->enterHash($cursor, 'stdClass' !== $class ? $this->style('note', $class).' {' : '{', $hasChild); + } + + /** + * {@inheritdoc} + */ + public function leaveObject(Cursor $cursor, $class, $hasChild, $cut) + { + $this->leaveHash($cursor, '}', $hasChild, $cut); + } + + /** + * {@inheritdoc} + */ + public function enterResource(Cursor $cursor, $res, $hasChild) + { + $this->enterHash($cursor, 'resource:'.$this->style('note', $res).' {', $hasChild); + } + + /** + * {@inheritdoc} + */ + public function leaveResource(Cursor $cursor, $res, $hasChild, $cut) + { + $this->leaveHash($cursor, '}', $hasChild, $cut); + } + + /** + * Generic dumper used while entering any hash-style structure. + * + * @param Cursor $cursor The Cursor position in the dump. + * @param string $prefix The string that starts the next dumped line. + * @param bool $hasChild When the dump of the hash has child item. + */ + protected function enterHash(Cursor $cursor, $prefix, $hasChild) + { + $this->dumpKey($cursor); + + $this->line .= $prefix; + if (false !== $cursor->refTo) { + $this->line .= $this->style('ref', ($cursor->refIsHard ? '&' : '@').$cursor->refTo); + } elseif ($hasChild) { + $this->endLine($cursor); + } + } + + /** + * Generic dumper used while leaving any hash-style structure. + * + * @param Cursor $cursor The Cursor position in the dump. + * @param string $suffix The string that ends the next dumped line. + * @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 leaveHash(Cursor $cursor, $suffix, $hasChild, $cut) + { + if ($cut && false === $cursor->refTo) { + $this->line .= '…'; + if (0 < $cut) { + $this->line .= $cut; + } + if ($hasChild) { + $this->dumpLine($cursor->depth+1); + } + } + $this->line .= $suffix; + $this->endLine($cursor, !$hasChild); + } + + /** + * Dumps a key in a hash structure. + * + * @param Cursor $cursor The Cursor position in the dump. + */ + protected function dumpKey(Cursor $cursor) + { + if (null !== $key = $cursor->hashKey) { + if ($bin = isset($key[0]) && !preg_match('//u', $key)) { + $key = Data::utf8Encode($key); + $bin = 'b'; + } + switch ($cursor->hashType) { + default: + case Cursor::HASH_INDEXED: + case Cursor::HASH_ASSOC: + if (is_int($key)) { + $this->line .= $this->style('meta', $key).' => '; + } else { + $this->line .= $bin.'"'.$this->style('meta', $key).'" => '; + } + break; + + case Cursor::HASH_RESOURCE: + $key = "\0~\0".$key; + // No break; + case Cursor::HASH_OBJECT: + if (!isset($key[0]) || "\0" !== $key[0]) { + $this->line .= $bin.$this->style('public', $key).': '; + } elseif (0 < strpos($key, "\0", 1)) { + $key = explode("\0", substr($key, 1), 2); + + switch ($key[0]) { + case '+': // User inserted keys + $this->line .= $bin.'"'.$this->style('public', $key[1]).'": '; + break 2; + + case '~': $style = 'meta'; break; + case '*': $style = 'protected'; break; + default: $style = 'private'; break; + } + + $this->line .= $bin.$this->style($style, $key[1]).': '; + } else { + // This case should not happen + $this->line .= $bin.'"'.$this->style('private', $key).'": '; + } + break; + } + } + } + + /** + * Finishes a line and dumps it. + * + * @param Cursor $cursor The current Cursor position. + * @param bool $showRef Show/hide the current ref index. + */ + protected function endLine(Cursor $cursor, $showRef = true) + { + if ($showRef && false !== $cursor->refIndex) { + $this->line .= ' '.$this->style('ref', '#'.$cursor->refIndex); + } + $this->dumpLine($cursor->depth); + } + + /** + * Decorates a value with some style. + * + * @param string $style The type of style being applied. + * @param string $val The value being styled. + * + * @return string The value with style decoration. + */ + protected function style($style, $val) + { + if (null === $this->colors) { + $this->colors = $this->supportsColors($this->outputStream); + } + + if (!$this->colors || '' === $val) { + return $val; + } + + if ('str' === $style || 'meta' === $style || 'public' === $style) { + foreach (static::$controlChars as $c) { + if (false !== strpos($val, $c)) { + $r = "\x7F" === $c ? '?' : chr(64 + ord($c)); + $r = "\033[{$this->styles[$style]};{$this->styles['cchr']}m{$r}\033[m"; + $r = "\033[m{$r}\033[{$this->styles[$style]}m"; + $val = str_replace($c, $r, $val); + } + } + } + + return sprintf("\033[%sm%s\033[m", $this->styles[$style], $val); + } + + /** + * @return bool Tells if the current output stream supports ANSI colors or not. + */ + protected function supportsColors() + { + if ($this->outputStream !== static::$defaultOutputStream) { + return @(is_resource($this->outputStream) && function_exists('posix_isatty') && posix_isatty($this->outputStream)); + } + if (null !== static::$defaultColors) { + return static::$defaultColors; + } + if (isset($_SERVER['argv'][1])) { + $colors = $_SERVER['argv']; + $i = count($colors); + while (--$i > 0) { + if (isset($colors[$i][5])) { + switch ($colors[$i]) { + case '--ansi': + case '--color': + case '--color=yes': + case '--color=force': + case '--color=always': + return static::$defaultColors = true; + + case '--no-ansi': + case '--color=no': + case '--color=none': + case '--color=never': + return static::$defaultColors = false; + } + } + } + } + + if (defined('PHP_WINDOWS_VERSION_MAJOR')) { + static::$defaultColors = @(false !== getenv('ANSICON') || 'ON' === getenv('ConEmuANSI')); + } 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; + } +} From e6dde3394012633e5e98bc654c7f351fc5d05d96 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 5 Apr 2014 21:20:25 +0200 Subject: [PATCH 13/31] [VarDumper] HTML variant of the CLI dumper --- .../Component/VarDumper/Dumper/HtmlDumper.php | 170 ++++++++++++++++++ 1 file changed, 170 insertions(+) create mode 100644 src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php diff --git a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php new file mode 100644 index 0000000000000..3182dc4cb185d --- /dev/null +++ b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php @@ -0,0 +1,170 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Dumper; + +/** + * HtmlDumper dumps variables as HTML. + * + * @author Nicolas Grekas + */ +class HtmlDumper extends CliDumper +{ + public static $defaultOutputStream = 'php://output'; + + protected $dumpHeader; + protected $dumpPrefix = '
';
+    protected $dumpSuffix = '
'; + protected $colors = true; + protected $headerIsDumped = false; + protected $lastDepth = -1; + protected $styles = array( + 'num' => 'font-weight:bold;color:#0087FF', + 'const' => 'font-weight:bold;color:#0087FF', + 'str' => 'font-weight:bold;color:#00D7FF', + 'cchr' => 'font-style: italic', + 'note' => 'color:#D7AF00', + 'ref' => 'color:#444444', + 'public' => 'color:#008700', + 'protected' => 'color:#D75F00', + 'private' => 'color:#D70000', + 'meta' => 'color:#005FFF', + ); + + /** + * {@inheritdoc} + */ + public function setLineDumper($callback) + { + $this->headerIsDumped = false; + + return parent::setLineDumper($callback); + } + + /** + * {@inheritdoc} + */ + public function setStyles(array $styles) + { + $this->headerIsDumped = false; + $this->styles = $styles + $this->styles; + } + + /** + * Sets an HTML header the will be dumped once in the output stream. + * + * @param string $header An HTML string. + */ + public function setDumpHeader($header) + { + $this->dumpHeader = $header; + } + + /** + * Sets an HTML prefix and suffix that will encapse every single dump. + * + * @param string $prefix The prepended HTML string. + * @param string $suffix The appended HTML string. + */ + public function setDumpBoudaries($prefix, $suffix) + { + $this->dumpPrefix = $prefix; + $this->dumpSuffix = $suffix; + } + + /** + * Dumps the HTML header. + */ + protected function dumpHeader() + { + $this->headerIsDumped = true; + $line = $this->line; + + $p = 'sf-var-debug'; + $this->line = ''; + parent::dumpLine(0); + $this->line .= $this->dumpHeader; + parent::dumpLine(0); + + $this->line = $line; + } + + /** + * {@inheritdoc} + */ + protected function style($style, $val) + { + if ('' === $val) { + return ''; + } + + if ('ref' === $style) { + $ref = substr($val, 1); + if ('#' === $val[0]) { + return "$val"; + } else { + return "$val"; + } + } + + $val = htmlspecialchars($val, ENT_QUOTES, 'UTF-8'); + + if ('str' === $style || 'meta' === $style || 'public' === $style) { + foreach (static::$controlChars as $c) { + if (false !== strpos($val, $c)) { + $r = "\x7F" === $c ? '?' : chr(64 + ord($c)); + $val = str_replace($c, "$r", $val); + } + } + } + + return "$val"; + } + + /** + * {@inheritdoc} + */ + protected function dumpLine($depth) + { + if (!$this->headerIsDumped) { + $this->dumpHeader(); + } + + switch ($this->lastDepth - $depth) { + case +1: $this->line = ''.$this->line; break; + case -1: $this->line = "$this->line"; break; + } + + if (-1 === $this->lastDepth) { + $this->line = $this->dumpPrefix.$this->line; + } + + if (false === $depth) { + $this->lastDepth = -1; + $this->line .= $this->dumpSuffix; + parent::dumpLine(0); + } else { + $this->lastDepth = $depth; + } + + parent::dumpLine($depth); + } +} From 5eaa187f8b9951eab6e9ff248f0e1a4f6a53d532 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 5 Apr 2014 21:31:16 +0200 Subject: [PATCH 14/31] [VarDumper] tests for CliDumper --- .../VarDumper/Tests/CliDumperTest.php | 101 ++++++++++++++++++ 1 file changed, 101 insertions(+) create mode 100644 src/Symfony/Component/VarDumper/Tests/CliDumperTest.php diff --git a/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php b/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php new file mode 100644 index 0000000000000..580c63160dba6 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php @@ -0,0 +1,101 @@ + + * + * 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\PhpCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; + +/** + * @author Nicolas Grekas + */ +class CliDumperTest extends \PHPUnit_Framework_TestCase +{ + public function testGet() + { + require __DIR__.'/Fixtures/dumb-var.php'; + + $dumper = new CliDumper('php://output'); + $dumper->setColors(false); + $cloner = new PhpCloner(); + $data = $cloner->cloneVar($var); + + ob_start(); + $dumper->dump($data); + $out = ob_get_clean(); + $closureLabel = PHP_VERSION_ID >= 50400 ? 'public method' : 'function'; + $out = preg_replace('/[ \t]+$/m', '', $out); + + $this->assertSame( + << 1 + 0 => null #1 + "const" => 1.1 + 1 => true + 2 => false + 3 => NAN + 4 => INF + 5 => -INF + 6 => 9223372036854775807 + "str" => "déjà" + 7 => b"é" + "[]" => [] + "res" => resource:stream { + wrapper_type: "plainfile" + stream_type: "dir" + mode: "r" + unread_bytes: 0 + seekable: true + timed_out: false + blocked: true + eof: false + options: [] + } + 8 => resource:Unknown {} + "obj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo { #2 + foo: "foo" + "bar": "bar" + } + "closure" => Closure { + reflection: """ + Closure [ {$closureLabel} Symfony\Component\VarDumper\Tests\Fixture\{closure} ] { + @@ {$var['file']} {$var['line']} - {$var['line']} + + - Parameters [2] { + Parameter #0 [ \$a ] + Parameter #1 [ PDO or NULL &\$b = NULL ] + } + } + """ + } + "line" => {$var['line']} + "nobj" => array:1 [ + 0 => {} #3 + ] + "recurs" => array:1 [ #4 + 0 => array:1 [&4] + ] + 9 => null &1 + "sobj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {@2} + "snobj" => {&3} + "snobj2" => {@3} + "file" => "{$var['file']}" + b"bin-key-é" => "" +] + +EOTXT + , + + $out + ); + } +} From a69e9622093db9ec7ed8ee24033cf5a06efdf13e Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 13 Jun 2014 11:14:12 +0200 Subject: [PATCH 15/31] [VarDumper] tests for HtmlDumper --- .../VarDumper/Tests/HtmlDumperTest.php | 117 ++++++++++++++++++ 1 file changed, 117 insertions(+) create mode 100644 src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php diff --git a/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php b/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php new file mode 100644 index 0000000000000..645bdbef30b31 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php @@ -0,0 +1,117 @@ + + * + * 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\PhpCloner; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; + +/** + * @author Nicolas Grekas + */ +class HtmlDumperTest extends \PHPUnit_Framework_TestCase +{ + public function testGet() + { + require __DIR__.'/Fixtures/dumb-var.php'; + + $dumper = new HtmlDumper('php://output'); + $dumper->setColors(false); + $cloner = new PhpCloner(); + $data = $cloner->cloneVar($var); + + ob_start(); + $dumper->dump($data); + $out = ob_get_clean(); + $closureLabel = PHP_VERSION_ID >= 50400 ? 'public method' : 'function'; + $out = preg_replace('/[ \t]+$/m', '', $out); + $var['file'] = htmlspecialchars($var['file'], ENT_QUOTES, 'UTF-8'); + + $this->assertSame( + << + +
array:25 [
+  "number" => 1
+  0 => null #1
+  "const" => 1.1
+  1 => true
+  2 => false
+  3 => NAN
+  4 => INF
+  5 => -INF
+  6 => 9223372036854775807
+  "str" => "déjà"
+  7 => b"é"
+  "[]" => []
+  "res" => resource:stream {
+    wrapper_type: "plainfile"
+    stream_type: "dir"
+    mode: "r"
+    unread_bytes: 0
+    seekable: true
+    timed_out: false
+    blocked: true
+    eof: false
+    options: []
+  }
+  8 => resource:Unknown {}
+  "obj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo { #2
+    foo: "foo"
+    "bar": "bar"
+  }
+  "closure" => Closure {
+    reflection: """
+      Closure [ <user> {$closureLabel} Symfony\Component\VarDumper\Tests\Fixture\{closure} ] {
+        @@ {$var['file']} {$var['line']} - {$var['line']}
+
+        - Parameters [2] {
+          Parameter #0 [ <required> \$a ]
+          Parameter #1 [ <optional> PDO or NULL &\$b = NULL ]
+        }
+      }
+      """
+  }
+  "line" => {$var['line']}
+  "nobj" => array:1 [
+    0 => {} #3
+  ]
+  "recurs" => array:1 [ #4
+    0 => array:1 [&4]
+  ]
+  9 => null &1
+  "sobj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {@2}
+  "snobj" => {&3}
+  "snobj2" => {@3}
+  "file" => "{$var['file']}"
+  b"bin-key-é" => ""
+]
+
+ +EOTXT + , + + $out + ); + } +} From 297d3734f8278aab6c6e97f3b76bdf865c5742e5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Sat, 5 Apr 2014 21:42:32 +0200 Subject: [PATCH 16/31] [VarDumper] README, LICENSE and composer.json --- src/Symfony/Component/VarDumper/LICENSE | 19 +++++++++++++++ src/Symfony/Component/VarDumper/README.md | 14 +++++++++++ src/Symfony/Component/VarDumper/composer.json | 24 +++++++++++++++++++ 3 files changed, 57 insertions(+) create mode 100644 src/Symfony/Component/VarDumper/LICENSE create mode 100644 src/Symfony/Component/VarDumper/README.md create mode 100644 src/Symfony/Component/VarDumper/composer.json diff --git a/src/Symfony/Component/VarDumper/LICENSE b/src/Symfony/Component/VarDumper/LICENSE new file mode 100644 index 0000000000000..0b3292cf90235 --- /dev/null +++ b/src/Symfony/Component/VarDumper/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2014 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Component/VarDumper/README.md b/src/Symfony/Component/VarDumper/README.md new file mode 100644 index 0000000000000..b38d82f9d0f6c --- /dev/null +++ b/src/Symfony/Component/VarDumper/README.md @@ -0,0 +1,14 @@ +Symfony mechanim for exploring and dumping PHP variables +======================================================== + +This component provides a mechanism that allows exploring then dumping +any PHP variable. + +It handles scalar, objects and resources properly, taking hard and soft +references into account. More than being immune to inifinite recursion +problems, it allows dumping where references link to each other. +It explores recursive structures using a breadth-first algorithm. + +The component exposes all the parts involved in the different steps of +cloning then dumping a PHP variable, while applying size limits and having +specialized output formats and methods. diff --git a/src/Symfony/Component/VarDumper/composer.json b/src/Symfony/Component/VarDumper/composer.json new file mode 100644 index 0000000000000..8b1c37e7bc917 --- /dev/null +++ b/src/Symfony/Component/VarDumper/composer.json @@ -0,0 +1,24 @@ +{ + "name": "symfony/var-dumper", + "type": "library", + "description": "Symfony mechanism for exploring and dumping PHP variables", + "keywords": ["dump", "debug"], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Nicolas Grekas", + "email": "p@tchwork.com" + } + ], + "require": { + "php": ">=5.3.3" + }, + "suggest": { + "ext-symfony_debug": "" + }, + "autoload": { + "psr-0": { "Symfony\\Component\\VarDumper\\": "" } + }, + "target-dir": "Symfony/Component/VarDumper" +} From 9dea601234627d964b47457ff342e2b39d36c205 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Thu, 29 May 2014 11:06:11 +0200 Subject: [PATCH 17/31] [DebugBundle] global dump() function for daily use --- composer.json | 7 +- src/Symfony/Bundle/DebugBundle/.gitignore | 3 + .../Bundle/DebugBundle/DebugBundle.php | 42 ++++++++++ .../DependencyInjection/Configuration.php | 48 ++++++++++++ .../DependencyInjection/DebugExtension.php | 46 +++++++++++ src/Symfony/Bundle/DebugBundle/README.md | 22 ++++++ .../DebugBundle/Resources/config/services.xml | 27 +++++++ .../Bundle/DebugBundle/Resources/meta/LICENSE | 19 +++++ .../Resources/views/Profiler/dump.html.twig | 78 +++++++++++++++++++ .../DebugExtensionTest.php | 56 +++++++++++++ src/Symfony/Bundle/DebugBundle/composer.json | 38 +++++++++ .../Bundle/DebugBundle/phpunit.xml.dist | 26 +++++++ src/Symfony/Component/VarDumper/composer.json | 8 +- 13 files changed, 418 insertions(+), 2 deletions(-) create mode 100644 src/Symfony/Bundle/DebugBundle/.gitignore create mode 100644 src/Symfony/Bundle/DebugBundle/DebugBundle.php create mode 100644 src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php create mode 100644 src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php create mode 100644 src/Symfony/Bundle/DebugBundle/README.md create mode 100644 src/Symfony/Bundle/DebugBundle/Resources/config/services.xml create mode 100644 src/Symfony/Bundle/DebugBundle/Resources/meta/LICENSE create mode 100644 src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig create mode 100644 src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php create mode 100644 src/Symfony/Bundle/DebugBundle/composer.json create mode 100644 src/Symfony/Bundle/DebugBundle/phpunit.xml.dist diff --git a/composer.json b/composer.json index fe40170481c7b..07e4ee4254915 100644 --- a/composer.json +++ b/composer.json @@ -30,6 +30,7 @@ "symfony/css-selector": "self.version", "symfony/dependency-injection": "self.version", "symfony/debug": "self.version", + "symfony/debug-bundle": "self.version", "symfony/doctrine-bridge": "self.version", "symfony/dom-crawler": "self.version", "symfony/event-dispatcher": "self.version", @@ -63,6 +64,7 @@ "symfony/twig-bridge": "self.version", "symfony/twig-bundle": "self.version", "symfony/validator": "self.version", + "symfony/var-dumper": "self.version", "symfony/web-profiler-bundle": "self.version", "symfony/yaml": "self.version" }, @@ -83,7 +85,10 @@ "src/Symfony/Component/HttpFoundation/Resources/stubs", "src/Symfony/Component/Intl/Resources/stubs" ], - "files": [ "src/Symfony/Component/Intl/Resources/stubs/functions.php" ] + "files": [ + "src/Symfony/Component/Intl/Resources/stubs/functions.php", + "src/Symfony/Bundle/DebugBundle/Resources/functions/debug.php" + ] }, "minimum-stability": "dev", "extra": { diff --git a/src/Symfony/Bundle/DebugBundle/.gitignore b/src/Symfony/Bundle/DebugBundle/.gitignore new file mode 100644 index 0000000000000..c49a5d8df5c65 --- /dev/null +++ b/src/Symfony/Bundle/DebugBundle/.gitignore @@ -0,0 +1,3 @@ +vendor/ +composer.lock +phpunit.xml diff --git a/src/Symfony/Bundle/DebugBundle/DebugBundle.php b/src/Symfony/Bundle/DebugBundle/DebugBundle.php new file mode 100644 index 0000000000000..0399e63a9badf --- /dev/null +++ b/src/Symfony/Bundle/DebugBundle/DebugBundle.php @@ -0,0 +1,42 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\DebugBundle; + +use Symfony\Component\HttpKernel\Bundle\Bundle; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\VarDumper; + +/** + * @author Nicolas Grekas + */ +class DebugBundle extends Bundle +{ + public function boot() + { + if ($this->container->getParameter('kernel.debug')) { + $container = $this->container; + + // This code is here to lazy load the dump stack. This default + // configuration for CLI mode is overridden in HTTP mode on + // 'kernel.request' event + VarDumper::setHandler(function ($var) use ($container) { + $dumper = new CliDumper(); + $cloner = $container->get('var_dumper.cloner'); + $handler = function ($var) use ($dumper, $cloner) { + $dumper->dump($cloner->cloneVar($var)); + }; + VarDumper::setHandler($handler); + $handler($var); + }); + } + } +} diff --git a/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php b/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php new file mode 100644 index 0000000000000..176211ad70940 --- /dev/null +++ b/src/Symfony/Bundle/DebugBundle/DependencyInjection/Configuration.php @@ -0,0 +1,48 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\DebugBundle\DependencyInjection; + +use Symfony\Component\Config\Definition\Builder\TreeBuilder; +use Symfony\Component\Config\Definition\ConfigurationInterface; + +/** + * DebugExtension configuration structure. + * + * @author Nicolas Grekas + */ +class Configuration implements ConfigurationInterface +{ + /** + * {@inheritdoc} + */ + public function getConfigTreeBuilder() + { + $treeBuilder = new TreeBuilder(); + $rootNode = $treeBuilder->root('debug'); + + $rootNode + ->children() + ->integerNode('max_items') + ->info('Max number of displayed items past the first level, -1 means no limit') + ->min(-1) + ->defaultValue(250) + ->end() + ->integerNode('max_string_length') + ->info('Max length of displayed strings, -1 means no limit') + ->min(-1) + ->defaultValue(-1) + ->end() + ->end(); + + return $treeBuilder; + } +} diff --git a/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php b/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php new file mode 100644 index 0000000000000..4b136c89454b8 --- /dev/null +++ b/src/Symfony/Bundle/DebugBundle/DependencyInjection/DebugExtension.php @@ -0,0 +1,46 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\DebugBundle\DependencyInjection; + +use Symfony\Component\Config\FileLocator; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\Loader\XmlFileLoader; +use Symfony\Component\HttpKernel\DependencyInjection\Extension; + +/** + * DebugExtension. + * + * @author Nicolas Grekas + */ +class DebugExtension extends Extension +{ + /** + * {@inheritdoc} + */ + public function load(array $configs, ContainerBuilder $container) + { + $configuration = new Configuration(); + $config = $this->processConfiguration($configuration, $configs); + + $loader = new XmlFileLoader($container, new FileLocator(__DIR__.'/../Resources/config')); + $loader->load('services.xml'); + + $container->setParameter( + 'var_dumper.cloner.class', + 'Symfony\Component\VarDumper\Cloner\\'.(function_exists('symfony_zval_info') ? 'Ext' : 'Php').'Cloner' + ); + + $container->getDefinition('var_dumper.cloner') + ->addMethodCall('setMaxItems', array($config['max_items'])) + ->addMethodCall('setMaxString', array($config['max_string_length'])); + } +} diff --git a/src/Symfony/Bundle/DebugBundle/README.md b/src/Symfony/Bundle/DebugBundle/README.md new file mode 100644 index 0000000000000..36935ba33ed5c --- /dev/null +++ b/src/Symfony/Bundle/DebugBundle/README.md @@ -0,0 +1,22 @@ +dump() function +================ + +This bundle provides a better `dump()` function, that you can use instead of +`var_dump()`, *better* meaning: + +- per object and resource types specialized view: e.g. filter out Doctrine noise + while dumping a single proxy entity, or get more insight on opened files with + `stream_get_meta_data()`. Add your own dedicated `Dumper\Caster` and get the + view *you* need. +- configurable output format: HTML, command line with colors or [a dedicated high + accuracy JSON format](Resource/doc/json-spec.md). +- ability to dump internal references, either soft ones (objects or resources) + or hard ones (`=&` on arrays or objects properties). Repeated occurrences of + the same object/array/resource won't appear again and again anymore. Moreover, + you'll be able to inspect the reference structure of your data. +- ability to operate in the context of an output buffering handler. +- full exposure of the internal mechanisms used for walking through an arbitrary + PHP data structure. + +Calling `dump($myVvar)` works in all PHP code and `{% dump myVar %}` or +`{{ dump(myVar) }}` in Twig templates. diff --git a/src/Symfony/Bundle/DebugBundle/Resources/config/services.xml b/src/Symfony/Bundle/DebugBundle/Resources/config/services.xml new file mode 100644 index 0000000000000..788a63cb4c222 --- /dev/null +++ b/src/Symfony/Bundle/DebugBundle/Resources/config/services.xml @@ -0,0 +1,27 @@ + + + + + + Symfony\Component\HttpKernel\DataCollector\DumpDataCollector + Symfony\Component\HttpKernel\EventListener\DumpListener + + + + + + + + + + + + data_collector.dump + + + + + + diff --git a/src/Symfony/Bundle/DebugBundle/Resources/meta/LICENSE b/src/Symfony/Bundle/DebugBundle/Resources/meta/LICENSE new file mode 100644 index 0000000000000..0b3292cf90235 --- /dev/null +++ b/src/Symfony/Bundle/DebugBundle/Resources/meta/LICENSE @@ -0,0 +1,19 @@ +Copyright (c) 2004-2014 Fabien Potencier + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is furnished +to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig b/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig new file mode 100644 index 0000000000000..d024ee1d062b3 --- /dev/null +++ b/src/Symfony/Bundle/DebugBundle/Resources/views/Profiler/dump.html.twig @@ -0,0 +1,78 @@ +{% extends '@WebProfiler/Profiler/layout.html.twig' %} + +{% block toolbar %} + {% set dumps_count = collector.dumpsCount %} + + {% if dumps_count %} + {% set icon %} + dump() + {{ dumps_count }} + {% endset %} + + {% set text %} +
+
+ dump() +
+ {% for dump in collector.getDumps('html') %} +
+ in {% if dump.file %}{{ dump.name }}{% else %}{{ dump.name }}{% endif %} + line {{ dump.line }}: + {{ dump.data|raw }} +
+ {% endfor %} + +
+ {% endset %} + + {% include '@WebProfiler/Profiler/toolbar_item.html.twig' with { 'link': true } %} + {% endif %} +{% endblock %} + +{% block menu %} + + + {{- "" -}} + dump() + {{- "" -}} + + dump() + + {{ collector.dumpsCount }} + + +{% endblock %} + +{% block panel %} +

dump()

+ + + +
    + {% for dump in collector.getDumps('html') %} +
  • + in {% if dump.file %}{{ dump.name }}{% else %}{{ dump.name }}{% endif %} + line {{ dump.line }}: + + + {% if dump.fileExcerpt %}{{ dump.fileExcerpt|raw }}{% else %}{{ dump.file|file_excerpt(dump.line) }}{% endif %} + + + {{ dump.data|raw }} +
  • + {% endfor %} +
+{% endblock %} diff --git a/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php b/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php new file mode 100644 index 0000000000000..4d22f59ce9fa8 --- /dev/null +++ b/src/Symfony/Bundle/DebugBundle/Tests/DependencyInjection/DebugExtensionTest.php @@ -0,0 +1,56 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bundle\DebugBundle\Tests\DependencyInjection; + +use Symfony\Bundle\DebugBundle\DependencyInjection\DebugExtension; +use Symfony\Component\DependencyInjection\ContainerBuilder; +use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; + +class DebugExtensionTest extends \PHPUnit_Framework_TestCase +{ + public function testLoadWithoutConfiguration() + { + $container = $this->createContainer(); + $container->registerExtension(new DebugExtension()); + $container->loadFromExtension('debug', array()); + $this->compileContainer($container); + + $expectedTags = array( + array( + "id" => "dump", + "template" => "@Debug/Profiler/dump.html.twig", + ), + ); + + $this->assertSame($expectedTags, $container->getDefinition('data_collector.dump')->getTag('data_collector')); + } + + private function createContainer() + { + $container = new ContainerBuilder(new ParameterBag(array( + 'kernel.cache_dir' => __DIR__, + 'kernel.root_dir' => __DIR__.'/Fixtures', + 'kernel.charset' => 'UTF-8', + 'kernel.debug' => true, + 'kernel.bundles' => array('DebugBundle' => 'Symfony\\Bundle\\DebugBundle\\DebugBundle'), + ))); + + return $container; + } + + private function compileContainer(ContainerBuilder $container) + { + $container->getCompilerPassConfig()->setOptimizationPasses(array()); + $container->getCompilerPassConfig()->setRemovingPasses(array()); + $container->compile(); + } +} diff --git a/src/Symfony/Bundle/DebugBundle/composer.json b/src/Symfony/Bundle/DebugBundle/composer.json new file mode 100644 index 0000000000000..6412fdeaf5c44 --- /dev/null +++ b/src/Symfony/Bundle/DebugBundle/composer.json @@ -0,0 +1,38 @@ +{ + "name": "symfony/debug-bundle", + "type": "symfony-bundle", + "description": "Symfony DebugBundle", + "keywords": [], + "homepage": "http://symfony.com", + "license": "MIT", + "authors": [ + { + "name": "Fabien Potencier", + "email": "fabien@symfony.com" + }, + { + "name": "Symfony Community", + "homepage": "http://symfony.com/contributors" + } + ], + "require": { + "php": ">=5.3.3", + "symfony/http-kernel": "~2.6", + "symfony/twig-bridge": "~2.6", + "symfony/var-dumper": "~2.6" + }, + "suggest": { + "symfony/config": "For service container configuration", + "symfony/dependency-injection": "For using as a service from the container" + }, + "autoload": { + "psr-0": { "Symfony\\Bundle\\DebugBundle\\": "" } + }, + "target-dir": "Symfony/Bundle/DebugBundle", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + } +} diff --git a/src/Symfony/Bundle/DebugBundle/phpunit.xml.dist b/src/Symfony/Bundle/DebugBundle/phpunit.xml.dist new file mode 100644 index 0000000000000..f33d475b2926e --- /dev/null +++ b/src/Symfony/Bundle/DebugBundle/phpunit.xml.dist @@ -0,0 +1,26 @@ + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./Resources + ./vendor + + + + diff --git a/src/Symfony/Component/VarDumper/composer.json b/src/Symfony/Component/VarDumper/composer.json index 8b1c37e7bc917..b7cd312b98a61 100644 --- a/src/Symfony/Component/VarDumper/composer.json +++ b/src/Symfony/Component/VarDumper/composer.json @@ -20,5 +20,11 @@ "autoload": { "psr-0": { "Symfony\\Component\\VarDumper\\": "" } }, - "target-dir": "Symfony/Component/VarDumper" + "target-dir": "Symfony/Component/VarDumper", + "minimum-stability": "dev", + "extra": { + "branch-alias": { + "dev-master": "2.6-dev" + } + } } From eb98c8175465a30b5758a67ee92f802a8f8b7652 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Tue, 26 Aug 2014 21:36:24 +0200 Subject: [PATCH 18/31] [DebugBundle] dump() + better Symfony glue --- composer.json | 2 +- .../Bridge/Twig/Extension/DumpExtension.php | 32 +++ src/Symfony/Bridge/Twig/Node/DumpNode.php | 89 +++++++ .../Twig/TokenParser/DumpTokenParser.php | 51 ++++ src/Symfony/Bridge/Twig/composer.json | 2 + src/Symfony/Component/Debug/Debug.php | 44 +++ src/Symfony/Component/Debug/composer.json | 2 + .../DataCollector/DumpDataCollector.php | 252 ++++++++++++++++++ .../HttpKernel/EventListener/DumpListener.php | 61 +++++ .../Component/VarDumper/Dumper/HtmlDumper.php | 14 +- .../VarDumper/Tests/HtmlDumperTest.php | 116 ++++---- 11 files changed, 599 insertions(+), 66 deletions(-) create mode 100644 src/Symfony/Bridge/Twig/Extension/DumpExtension.php create mode 100644 src/Symfony/Bridge/Twig/Node/DumpNode.php create mode 100644 src/Symfony/Bridge/Twig/TokenParser/DumpTokenParser.php create mode 100644 src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php create mode 100644 src/Symfony/Component/HttpKernel/EventListener/DumpListener.php diff --git a/composer.json b/composer.json index 07e4ee4254915..0a14b8ff746cb 100644 --- a/composer.json +++ b/composer.json @@ -87,7 +87,7 @@ ], "files": [ "src/Symfony/Component/Intl/Resources/stubs/functions.php", - "src/Symfony/Bundle/DebugBundle/Resources/functions/debug.php" + "src/Symfony/Bundle/DebugBundle/Resources/functions/dump.php" ] }, "minimum-stability": "dev", diff --git a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php new file mode 100644 index 0000000000000..a0e895dd87665 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php @@ -0,0 +1,32 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Extension; + +use Symfony\Bridge\Twig\TokenParser\DumpTokenParser; + +/** + * Provides integration of the dump() function with Twig. + * + * @author Nicolas Grekas + */ +class DumpExtension extends \Twig_Extension +{ + public function getTokenParsers() + { + return array(new DumpTokenParser()); + } + + public function getName() + { + return 'dump'; + } +} diff --git a/src/Symfony/Bridge/Twig/Node/DumpNode.php b/src/Symfony/Bridge/Twig/Node/DumpNode.php new file mode 100644 index 0000000000000..ca64660243d73 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Node/DumpNode.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Node; + +/** + * @author Julien Galenski + */ +class DumpNode extends \Twig_Node +{ + public function __construct(\Twig_NodeInterface $values = null, $lineno, $tag = null) + { + parent::__construct(array('values' => $values), array(), $lineno, $tag); + } + + /** + * {@inheritdoc} + */ + public function compile(\Twig_Compiler $compiler) + { + $compiler + ->write("if (\$this->env->isDebug()) {\n") + ->indent() + ; + + $values = $this->getNode('values'); + + if (null === $values) { + // remove embedded templates (macros) from the context + $compiler + ->write("\$vars = array();\n") + ->write("foreach (\$context as \$key => \$value) {\n") + ->indent() + ->write("if (!\$value instanceof Twig_Template) {\n") + ->indent() + ->write("\$vars[\$key] = \$value;\n") + ->outdent() + ->write("}\n") + ->outdent() + ->write("}\n") + ->addDebugInfo($this) + ->write('\Symfony\Component\Debug\Debug::dump($vars);'."\n") + ; + } elseif (1 === $values->count()) { + $compiler + ->addDebugInfo($this) + ->write('\Symfony\Component\Debug\Debug::dump(') + ->subcompile($values->getNode(0)) + ->raw(");\n") + ; + } else { + $compiler + ->addDebugInfo($this) + ->write('\Symfony\Component\Debug\Debug::dump(array(') + ->indent() + ; + foreach ($values as $node) { + $compiler->addIndentation(); + if ($node->hasAttribute('name')) { + $compiler + ->string($node->getAttribute('name')) + ->raw(' => ') + ; + } + $compiler + ->subcompile($node) + ->raw(",\n") + ; + } + $compiler + ->outdent() + ->raw("));\n") + ; + } + + $compiler + ->outdent() + ->write("}\n") + ; + } +} diff --git a/src/Symfony/Bridge/Twig/TokenParser/DumpTokenParser.php b/src/Symfony/Bridge/Twig/TokenParser/DumpTokenParser.php new file mode 100644 index 0000000000000..1505e5bccacc9 --- /dev/null +++ b/src/Symfony/Bridge/Twig/TokenParser/DumpTokenParser.php @@ -0,0 +1,51 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\TokenParser; + +use Symfony\Bridge\Twig\Node\DumpNode; + +/** + * Token Parser for the 'dump' tag. + * + * Dump variables with: + *
+ *  {% dump %}
+ *  {% dump foo %}
+ *  {% dump foo, bar %}
+ * 
+ * + * @author Julien Galenski + */ +class DumpTokenParser extends \Twig_TokenParser +{ + /** + * {@inheritdoc} + */ + public function parse(\Twig_Token $token) + { + $values = null; + if (!$this->parser->getStream()->test(\Twig_Token::BLOCK_END_TYPE)) { + $values = $this->parser->getExpressionParser()->parseMultitargetExpression(); + } + $this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE); + + return new DumpNode($values, $token->getLine(), $this->getTag()); + } + + /** + * {@inheritdoc} + */ + public function getTag() + { + return 'dump'; + } +} diff --git a/src/Symfony/Bridge/Twig/composer.json b/src/Symfony/Bridge/Twig/composer.json index 59db37b44f68d..d9a864b919641 100644 --- a/src/Symfony/Bridge/Twig/composer.json +++ b/src/Symfony/Bridge/Twig/composer.json @@ -30,6 +30,7 @@ "symfony/security": "~2.4", "symfony/stopwatch": "~2.2", "symfony/console": "~2.2", + "symfony/var-dumper": "~2.6", "symfony/expression-language": "~2.4" }, "suggest": { @@ -41,6 +42,7 @@ "symfony/yaml": "For using the YamlExtension", "symfony/security": "For using the SecurityExtension", "symfony/stopwatch": "For using the StopwatchExtension", + "symfony/var-dumper": "For using the DumpExtension", "symfony/expression-language": "For using the ExpressionExtension" }, "autoload": { diff --git a/src/Symfony/Component/Debug/Debug.php b/src/Symfony/Component/Debug/Debug.php index fd308e80036b9..b6b2d15125f08 100644 --- a/src/Symfony/Component/Debug/Debug.php +++ b/src/Symfony/Component/Debug/Debug.php @@ -11,6 +11,11 @@ namespace Symfony\Component\Debug; +use Symfony\Component\VarDumper\Cloner\ExtCloner; +use Symfony\Component\VarDumper\Cloner\PhpCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; + /** * Registers all the debug tools. * @@ -19,6 +24,7 @@ class Debug { private static $enabled = false; + private static $dumpHandler; /** * Enables the debug tools. @@ -59,4 +65,42 @@ public static function enable($errorReportingLevel = null, $displayErrors = true DebugClassLoader::enable(); } + + public static function dump($var) + { + if (null === self::$dumpHandler) { + $cloner = extension_loaded('symfony_debug') ? new ExtCloner() : new PhpCloner(); + $dumper = 'cli' === PHP_SAPI ? new CliDumper() : new HtmlDumper(); + self::$dumpHandler = function ($var) use ($cloner, $dumper) { + $dumper->dump($cloner->cloneVar($var)); + }; + } + + $h = self::$dumpHandler; + + if (is_array($h)) { + return $h[0]->{$h[1]}($var); + } + + return $h($var); + } + + public static function setDumpHandler($callable) + { + if (!is_callable($callable)) { + throw new \InvalidArgumentException('Invalid PHP callback.'); + } + + $prevHandler = self::$dumpHandler; + + if (is_array($callable)) { + if (!is_object($callable[0])) { + self::$dumpHandler = $callable[0].'::'.$callable[1]; + } + } else { + self::$dumpHandler = $callable; + } + + return $prevHandler; + } } diff --git a/src/Symfony/Component/Debug/composer.json b/src/Symfony/Component/Debug/composer.json index b919aa4fd6cea..54e7a9cab36ae 100644 --- a/src/Symfony/Component/Debug/composer.json +++ b/src/Symfony/Component/Debug/composer.json @@ -20,10 +20,12 @@ "psr/log": "~1.0" }, "require-dev": { + "symfony/var-dumper": "~2.6", "symfony/http-kernel": "~2.1", "symfony/http-foundation": "~2.1" }, "suggest": { + "symfony/var-dumper": "For using Debug::dump()", "symfony/http-foundation": "", "symfony/http-kernel": "" }, diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php new file mode 100644 index 0000000000000..298573c586706 --- /dev/null +++ b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php @@ -0,0 +1,252 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\DataCollector; + +use Symfony\Component\HttpFoundation\Request; +use Symfony\Component\HttpFoundation\Response; +use Symfony\Component\Stopwatch\Stopwatch; +use Symfony\Component\VarDumper\Cloner\Data; +use Symfony\Component\VarDumper\Dumper\JsonDumper; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; +use Symfony\Component\VarDumper\Dumper\DataDumperInterface; + +/** + * @author Nicolas Grekas + */ +class DumpDataCollector extends DataCollector implements DataDumperInterface +{ + private $stopwatch; + private $isCollected = true; + private $clonesRoot; + private $clonesCount = 0; + + public function __construct(Stopwatch $stopwatch = null) + { + $this->stopwatch = $stopwatch; + $this->clonesRoot = $this; + } + + public function __clone() + { + $this->data = array(); + $this->clonesRoot->clonesCount++; + } + + public function dump(Data $data) + { + if ($this->stopwatch) { + $this->stopwatch->start('dump'); + } + if ($this->clonesRoot->isCollected) { + $this->clonesRoot->isCollected = false; + register_shutdown_function(array($this->clonesRoot, 'flushDumps')); + } + + $trace = PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS : true; + if (PHP_VERSION_ID >= 50400) { + $trace = debug_backtrace($trace, 6); + } else { + $trace = debug_backtrace($trace); + } + + $file = $trace[0]['file']; + $line = $trace[0]['line']; + $name = false; + $fileExcerpt = false; + + for ($i = 1; $i < 6; ++$i) { + if (isset($trace[$i]['class'], $trace[$i]['function']) + && 'dump' === $trace[$i]['function'] + && 'Symfony\Bundle\DebugBundle\DebugBundle' === $trace[$i]['class'] + ) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + + while (++$i < 6) { + if (isset($trace[$i]['function']) && empty($trace[$i]['class'])) { + $file = $trace[$i]['file']; + $line = $trace[$i]['line']; + break; + } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof \Twig_Template) { + $info = $trace[$i]['object']; + $name = $info->getTemplateName(); + $src = $info->getEnvironment()->getLoader()->getSource($name); + $info = $info->getDebugInfo(); + if (isset($info[$trace[$i-1]['line']])) { + $file = false; + $line = $info[$trace[$i-1]['line']]; + $src = explode("\n", $src); + $fileExcerpt = array(); + + for ($i = max($line - 3, 1), $max = min($line + 3, count($src)); $i <= $max; ++$i) { + $fileExcerpt[] = ''.htmlspecialchars($src[$i - 1]).''; + } + + $fileExcerpt = '
    '.implode("\n", $fileExcerpt).'
'; + } + break; + } + } + break; + } + } + + if (false === $name) { + $name = strtr($file, '\\', '/'); + $name = substr($file, strrpos($file, '/') + 1); + } + + $this->clonesRoot->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt'); + + if ($this->stopwatch) { + $this->stopwatch->stop('dump'); + } + } + + public function collect(Request $request, Response $response, \Exception $exception = null) + { + } + + public function serialize() + { + $ser = serialize($this->clonesRoot->data); + $this->clonesRoot->data = array(); + $this->clonesRoot->isCollected = true; + + return $ser; + } + + public function unserialize($data) + { + parent::unserialize($data); + + $this->clonesRoot = $this; + } + + public function getDumpsCount() + { + return count($this->clonesRoot->data); + } + + public function getDumpsExcerpts() + { + $dumps = array(); + + foreach ($this->data as $dump) { + $data = $dump['data']->getRawData(); + unset($dump['data']); + + $data = $data[0][0]; + + if (isset($data->val)) { + $data = $data->val; + } + + if (isset($data->bin)) { + $data = 'b"'.$data->bin.'"'; + } elseif (isset($data->str)) { + $data = '"'.$data->str.'"'; + } elseif (isset($data->count)) { + $data = 'array('.$data->count.')'; + } elseif (isset($data->class)) { + $data = $data->class.'{...}'; + } elseif (isset($data->res)) { + $data = 'resource:'.$data->res.'{...}'; + } elseif (is_array($data)) { + $data = 'array()'; + } elseif (null === $data) { + $data = 'null'; + } elseif (false === $data) { + $data = 'false'; + } elseif (INF === $data) { + $data = 'INF'; + } elseif (-INF === $data) { + $data = '-INF'; + } elseif (NAN === $data) { + $data = 'NAN'; + } elseif (true === $data) { + $data = 'true'; + } + + $dump['dataExcerpt'] = $data; + $dumps[] = $dump; + } + + return $dumps; + } + + public function getDumps($getData = false) + { + if ($getData) { + $dumper = new JsonDumper(); + } + $dumps = array(); + + foreach ($this->clonesRoot->data as $dump) { + $json = ''; + if ($getData) { + $dumper->dump($dump['data'], function ($line) use (&$json) {$json .= $line;}); + } + $dump['data'] = $json; + $dumps[] = $dump; + } + + return $dumps; + } + + public function getName() + { + return 'dump'; + } + + public function flushDumps() + { + if (0 === $this->clonesRoot->clonesCount-- && !$this->clonesRoot->isCollected && $this->clonesRoot->data) { + $this->clonesRoot->clonesCount = 0; + $this->clonesRoot->isCollected = true; + + $h = headers_list(); + $i = count($h); + array_unshift($h, 'Content-Type: ' . ini_get('default_mimetype')); + while (0 !== stripos($h[$i], 'Content-Type:')) { + --$i; + } + + if (stripos($h[$i], 'html')) { + echo ''; + $dumper = new HtmlDumper(); + } else { + $dumper = new CliDumper(); + $dumper->setColors(false); + } + + foreach ($this->clonesRoot->data as $i => $dump) { + $this->clonesRoot->data[$i] = null; + + if ($dumper instanceof HtmlDumper) { + $dump['name'] = htmlspecialchars($dump['name'], ENT_QUOTES, 'UTF-8'); + $dump['file'] = htmlspecialchars($dump['file'], ENT_QUOTES, 'UTF-8'); + if ('' !== $dump['file']) { + $dump['name'] = "{$dump['name']}"; + } + echo "\n
in {$dump['name']} on line {$dump['line']}:"; + } else { + echo "\nin {$dump['name']} on line {$dump['line']}:\n\n"; + } + $dumper->dump($dump['data']); + } + + $this->clonesRoot->data = array(); + } + } +} diff --git a/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php b/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php new file mode 100644 index 0000000000000..89dee034b510d --- /dev/null +++ b/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php @@ -0,0 +1,61 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\EventListener; + +use Symfony\Component\Debug\Debug; +use Symfony\Component\EventDispatcher\EventSubscriberInterface; +use Symfony\Component\HttpKernel\KernelEvents; +use Symfony\Component\DependencyInjection\ContainerInterface; + +/** + * Configures dump() handler. + * + * @author Nicolas Grekas + */ +class DumpListener implements EventSubscriberInterface +{ + private $container; + private $dumper; + + /** + * @param ContainerInterface $container Service container, for lazy loading. + * @param string $dumper var_dumper dumper service to use. + */ + public function __construct(ContainerInterface $container, $dumper) + { + $this->container = $container; + $this->dumper = $dumper; + } + + public function configure() + { + if ($this->container) { + $container = $this->container; + $dumper = $this->dumper; + $this->container = null; + + Debug::setDumpHandler(function ($var) use ($container, $dumper) { + $dumper = $container->get($dumper); + $cloner = $container->get('var_dumper.cloner'); + $handler = function ($var) use ($dumper, $cloner) {$dumper->dump($cloner->cloneVar($var));}; + Debug::setDumpHandler($handler); + $handler($var); + }); + } + } + + public static function getSubscribedEvents() + { + // Register early to have a working dump() as early as possible + return array(KernelEvents::REQUEST => array('configure', 1024)); + } +} diff --git a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php index 3182dc4cb185d..e333eb8539554 100644 --- a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php @@ -21,7 +21,7 @@ class HtmlDumper extends CliDumper public static $defaultOutputStream = 'php://output'; protected $dumpHeader; - protected $dumpPrefix = '
';
+    protected $dumpPrefix = '
';
     protected $dumpSuffix = '
'; protected $colors = true; protected $headerIsDumped = false; @@ -88,7 +88,7 @@ protected function dumpHeader() $this->headerIsDumped = true; $line = $this->line; - $p = 'sf-var-debug'; + $p = 'sf-dump'; $this->line = ' -
array:25 [
-  "number" => 1
-  0 => null #1
-  "const" => 1.1
-  1 => true
-  2 => false
-  3 => NAN
-  4 => INF
-  5 => -INF
-  6 => 9223372036854775807
-  "str" => "déjà"
-  7 => b"é"
-  "[]" => []
-  "res" => resource:stream {
-    wrapper_type: "plainfile"
-    stream_type: "dir"
-    mode: "r"
-    unread_bytes: 0
-    seekable: true
-    timed_out: false
-    blocked: true
-    eof: false
-    options: []
+
array:25 [
+  "number" => 1
+  0 => null #1
+  "const" => 1.1
+  1 => true
+  2 => false
+  3 => NAN
+  4 => INF
+  5 => -INF
+  6 => 9223372036854775807
+  "str" => "déjà"
+  7 => b"é"
+  "[]" => []
+  "res" => resource:stream {
+    wrapper_type: "plainfile"
+    stream_type: "dir"
+    mode: "r"
+    unread_bytes: 0
+    seekable: true
+    timed_out: false
+    blocked: true
+    eof: false
+    options: []
   }
-  8 => resource:Unknown {}
-  "obj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo { #2
-    foo: "foo"
-    "bar": "bar"
+  8 => resource:Unknown {}
+  "obj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo { #2
+    foo: "foo"
+    "bar": "bar"
   }
-  "closure" => Closure {
-    reflection: """
-      Closure [ <user> {$closureLabel} Symfony\Component\VarDumper\Tests\Fixture\{closure} ] {
-        @@ {$var['file']} {$var['line']} - {$var['line']}
+  "closure" => Closure {
+    reflection: """
+      Closure [ <user> {$closureLabel} Symfony\Component\VarDumper\Tests\Fixture\{closure} ] {
+        @@ {$var['file']} {$var['line']} - {$var['line']}
 
-        - Parameters [2] {
-          Parameter #0 [ <required> \$a ]
-          Parameter #1 [ <optional> PDO or NULL &\$b = NULL ]
-        }
-      }
+        - Parameters [2] {
+          Parameter #0 [ <required> \$a ]
+          Parameter #1 [ <optional> PDO or NULL &\$b = NULL ]
+        }
+      }
       """
   }
-  "line" => {$var['line']}
-  "nobj" => array:1 [
-    0 => {} #3
+  "line" => {$var['line']}
+  "nobj" => array:1 [
+    0 => {} #3
   ]
-  "recurs" => array:1 [ #4
-    0 => array:1 [&4]
+  "recurs" => array:1 [ #4
+    0 => array:1 [&4]
   ]
-  9 => null &1
-  "sobj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {@2}
-  "snobj" => {&3}
-  "snobj2" => {@3}
-  "file" => "{$var['file']}"
-  b"bin-key-é" => ""
+  9 => null &1
+  "sobj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {@2}
+  "snobj" => {&3}
+  "snobj2" => {@3}
+  "file" => "{$var['file']}"
+  b"bin-key-é" => ""
 ]
 
From 8d5d970eea9804982dda976f5f66ac1b02a2a529 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 27 Aug 2014 17:29:38 +0200 Subject: [PATCH 19/31] [DebugBundle] adjust after review --- composer.json | 2 +- src/Symfony/Bridge/Twig/Node/DumpNode.php | 19 ++++--- .../Twig/TokenParser/DumpTokenParser.php | 2 +- .../TwigBundle/Resources/config/debug.xml | 5 ++ src/Symfony/Component/Debug/Debug.php | 44 ---------------- src/Symfony/Component/Debug/composer.json | 2 - .../DataCollector/DumpDataCollector.php | 4 +- .../Component/VarDumper/Caster/DOMCaster.php | 4 ++ .../Component/VarDumper/Cloner/Data.php | 24 ++++----- .../VarDumper/Resources/functions/dump.php | 24 +++++++++ src/Symfony/Component/VarDumper/VarDumper.php | 50 +++++++++++++++++++ src/Symfony/Component/VarDumper/composer.json | 1 + 12 files changed, 111 insertions(+), 70 deletions(-) create mode 100644 src/Symfony/Component/VarDumper/Resources/functions/dump.php create mode 100644 src/Symfony/Component/VarDumper/VarDumper.php diff --git a/composer.json b/composer.json index 0a14b8ff746cb..97a5dd5b73118 100644 --- a/composer.json +++ b/composer.json @@ -87,7 +87,7 @@ ], "files": [ "src/Symfony/Component/Intl/Resources/stubs/functions.php", - "src/Symfony/Bundle/DebugBundle/Resources/functions/dump.php" + "src/Symfony/Component/VarDumper/Resources/functions/dump.php" ] }, "minimum-stability": "dev", diff --git a/src/Symfony/Bridge/Twig/Node/DumpNode.php b/src/Symfony/Bridge/Twig/Node/DumpNode.php index ca64660243d73..2bae8fd38779e 100644 --- a/src/Symfony/Bridge/Twig/Node/DumpNode.php +++ b/src/Symfony/Bridge/Twig/Node/DumpNode.php @@ -16,9 +16,12 @@ */ class DumpNode extends \Twig_Node { - public function __construct(\Twig_NodeInterface $values = null, $lineno, $tag = null) + private $varPrefix; + + public function __construct($varPrefix, \Twig_NodeInterface $values = null, $lineno, $tag = null) { parent::__construct(array('values' => $values), array(), $lineno, $tag); + $this->varPrefix = $varPrefix; } /** @@ -36,30 +39,30 @@ public function compile(\Twig_Compiler $compiler) if (null === $values) { // remove embedded templates (macros) from the context $compiler - ->write("\$vars = array();\n") - ->write("foreach (\$context as \$key => \$value) {\n") + ->write(sprintf('$%svars = array();'."\n", $this->varPrefix)) + ->write(sprintf('foreach ($context as $%1$skey => $%1$sval) {'."\n", $this->varPrefix)) ->indent() - ->write("if (!\$value instanceof Twig_Template) {\n") + ->write(sprintf('if (!$%sval instanceof \Twig_Template) {'."\n", $this->varPrefix)) ->indent() - ->write("\$vars[\$key] = \$value;\n") + ->write(sprintf('$%1$svars[$%1$skey] = $%1$sval;'."\n", $this->varPrefix)) ->outdent() ->write("}\n") ->outdent() ->write("}\n") ->addDebugInfo($this) - ->write('\Symfony\Component\Debug\Debug::dump($vars);'."\n") + ->write(sprintf('\Symfony\Component\VarDumper\VarDumper::dump($%svars);'."\n", $this->varPrefix)) ; } elseif (1 === $values->count()) { $compiler ->addDebugInfo($this) - ->write('\Symfony\Component\Debug\Debug::dump(') + ->write('\Symfony\Component\VarDumper\VarDumper::dump(') ->subcompile($values->getNode(0)) ->raw(");\n") ; } else { $compiler ->addDebugInfo($this) - ->write('\Symfony\Component\Debug\Debug::dump(array(') + ->write('\Symfony\Component\VarDumper\VarDumper::dump(array(') ->indent() ; foreach ($values as $node) { diff --git a/src/Symfony/Bridge/Twig/TokenParser/DumpTokenParser.php b/src/Symfony/Bridge/Twig/TokenParser/DumpTokenParser.php index 1505e5bccacc9..269ead64f5d40 100644 --- a/src/Symfony/Bridge/Twig/TokenParser/DumpTokenParser.php +++ b/src/Symfony/Bridge/Twig/TokenParser/DumpTokenParser.php @@ -38,7 +38,7 @@ public function parse(\Twig_Token $token) } $this->parser->getStream()->expect(\Twig_Token::BLOCK_END_TYPE); - return new DumpNode($values, $token->getLine(), $this->getTag()); + return new DumpNode($this->parser->getVarName(), $values, $token->getLine(), $this->getTag()); } /** diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/debug.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/debug.xml index c44ae6312155e..2d12710bb83af 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/debug.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/debug.xml @@ -6,6 +6,7 @@ Symfony\Bundle\TwigBundle\Debug\TimedTwigEngine + Symfony\Bridge\Twig\Extension\DumpExtension @@ -19,5 +20,9 @@ + + + + diff --git a/src/Symfony/Component/Debug/Debug.php b/src/Symfony/Component/Debug/Debug.php index b6b2d15125f08..fd308e80036b9 100644 --- a/src/Symfony/Component/Debug/Debug.php +++ b/src/Symfony/Component/Debug/Debug.php @@ -11,11 +11,6 @@ namespace Symfony\Component\Debug; -use Symfony\Component\VarDumper\Cloner\ExtCloner; -use Symfony\Component\VarDumper\Cloner\PhpCloner; -use Symfony\Component\VarDumper\Dumper\CliDumper; -use Symfony\Component\VarDumper\Dumper\HtmlDumper; - /** * Registers all the debug tools. * @@ -24,7 +19,6 @@ class Debug { private static $enabled = false; - private static $dumpHandler; /** * Enables the debug tools. @@ -65,42 +59,4 @@ public static function enable($errorReportingLevel = null, $displayErrors = true DebugClassLoader::enable(); } - - public static function dump($var) - { - if (null === self::$dumpHandler) { - $cloner = extension_loaded('symfony_debug') ? new ExtCloner() : new PhpCloner(); - $dumper = 'cli' === PHP_SAPI ? new CliDumper() : new HtmlDumper(); - self::$dumpHandler = function ($var) use ($cloner, $dumper) { - $dumper->dump($cloner->cloneVar($var)); - }; - } - - $h = self::$dumpHandler; - - if (is_array($h)) { - return $h[0]->{$h[1]}($var); - } - - return $h($var); - } - - public static function setDumpHandler($callable) - { - if (!is_callable($callable)) { - throw new \InvalidArgumentException('Invalid PHP callback.'); - } - - $prevHandler = self::$dumpHandler; - - if (is_array($callable)) { - if (!is_object($callable[0])) { - self::$dumpHandler = $callable[0].'::'.$callable[1]; - } - } else { - self::$dumpHandler = $callable; - } - - return $prevHandler; - } } diff --git a/src/Symfony/Component/Debug/composer.json b/src/Symfony/Component/Debug/composer.json index 54e7a9cab36ae..b919aa4fd6cea 100644 --- a/src/Symfony/Component/Debug/composer.json +++ b/src/Symfony/Component/Debug/composer.json @@ -20,12 +20,10 @@ "psr/log": "~1.0" }, "require-dev": { - "symfony/var-dumper": "~2.6", "symfony/http-kernel": "~2.1", "symfony/http-foundation": "~2.1" }, "suggest": { - "symfony/var-dumper": "For using Debug::dump()", "symfony/http-foundation": "", "symfony/http-kernel": "" }, diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php index 298573c586706..1412934bacef1 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php @@ -64,10 +64,10 @@ public function dump(Data $data) $name = false; $fileExcerpt = false; - for ($i = 1; $i < 6; ++$i) { + for ($i = 1; $i < 7; ++$i) { if (isset($trace[$i]['class'], $trace[$i]['function']) && 'dump' === $trace[$i]['function'] - && 'Symfony\Bundle\DebugBundle\DebugBundle' === $trace[$i]['class'] + && 'Symfony\Component\VarDumper\VarDumper' === $trace[$i]['class'] ) { $file = $trace[$i]['file']; $line = $trace[$i]['line']; diff --git a/src/Symfony/Component/VarDumper/Caster/DOMCaster.php b/src/Symfony/Component/VarDumper/Caster/DOMCaster.php index 7d227d8a71d74..248154143b3a0 100644 --- a/src/Symfony/Component/VarDumper/Caster/DOMCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/DOMCaster.php @@ -68,6 +68,8 @@ public static function castImplementation($dom, array $a, $isNested, &$cut) public static function castNode(\DOMNode $dom, array $a, $isNested, &$cut) { + // Commented lines denote properties that exist but are better not dumped for clarity. + $a += array( 'nodeName' => $dom->nodeName, //'nodeValue' => $dom->nodeValue, @@ -93,6 +95,8 @@ public static function castNode(\DOMNode $dom, array $a, $isNested, &$cut) public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, $isNested, &$cut) { + // Commented lines denote properties that exist but are better not dumped for clarity. + $a += array( 'nodeName' => $dom->nodeName, //'nodeValue' => $dom->nodeValue, diff --git a/src/Symfony/Component/VarDumper/Cloner/Data.php b/src/Symfony/Component/VarDumper/Cloner/Data.php index 7361b839877d0..5da5916dea5dc 100644 --- a/src/Symfony/Component/VarDumper/Cloner/Data.php +++ b/src/Symfony/Component/VarDumper/Cloner/Data.php @@ -183,19 +183,19 @@ public static function utf8Encode($s) { if (function_exists('iconv')) { return iconv('CP1252', 'UTF-8', $s); - } else { - $s .= $s; - $len = strlen($s); - - for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) { - switch (true) { - case $s[$i] < "\x80": $s[$j] = $s[$i]; break; - case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break; - default: $s[$j] = "\xC3"; $s[++$j] = chr(ord($s[$i]) - 64); break; - } - } + } - return substr($s, 0, $j); + $s .= $s; + $len = strlen($s); + + for ($i = $len >> 1, $j = 0; $i < $len; ++$i, ++$j) { + switch (true) { + case $s[$i] < "\x80": $s[$j] = $s[$i]; break; + case $s[$i] < "\xC0": $s[$j] = "\xC2"; $s[++$j] = $s[$i]; break; + default: $s[$j] = "\xC3"; $s[++$j] = chr(ord($s[$i]) - 64); break; + } } + + return substr($s, 0, $j); } } diff --git a/src/Symfony/Component/VarDumper/Resources/functions/dump.php b/src/Symfony/Component/VarDumper/Resources/functions/dump.php new file mode 100644 index 0000000000000..b6c243c8b6e2f --- /dev/null +++ b/src/Symfony/Component/VarDumper/Resources/functions/dump.php @@ -0,0 +1,24 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +use Symfony\Component\VarDumper\VarDumper; + +if (!function_exists('dump')) { + /** + * @author Nicolas Grekas + */ + function dump($var) + { + foreach (func_get_args() as $var) { + VarDumper::dump($var); + } + } +} diff --git a/src/Symfony/Component/VarDumper/VarDumper.php b/src/Symfony/Component/VarDumper/VarDumper.php new file mode 100644 index 0000000000000..40c05f5f31abc --- /dev/null +++ b/src/Symfony/Component/VarDumper/VarDumper.php @@ -0,0 +1,50 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper; + +use Symfony\Component\VarDumper\Cloner\ExtCloner; +use Symfony\Component\VarDumper\Cloner\PhpCloner; +use Symfony\Component\VarDumper\Dumper\CliDumper; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; + +/** + * @author Nicolas Grekas + */ +class VarDumper +{ + private static $handler; + + public static function dump($var) + { + if (null === self::$handler) { + $cloner = extension_loaded('symfony_debug') ? new ExtCloner() : new PhpCloner(); + $dumper = 'cli' === PHP_SAPI ? new CliDumper() : new HtmlDumper(); + self::$handler = function ($var) use ($cloner, $dumper) { + $dumper->dump($cloner->cloneVar($var)); + }; + } + + return call_user_func(self::$handler, $h); + } + + public static function setHandler($callable) + { + if (!is_callable($callable, true)) { + throw new \InvalidArgumentException('Invalid PHP callback.'); + } + + $prevHandler = self::$handler; + self::$handler = $callable; + + return $prevHandler; + } +} diff --git a/src/Symfony/Component/VarDumper/composer.json b/src/Symfony/Component/VarDumper/composer.json index b7cd312b98a61..4a22fc2098aa1 100644 --- a/src/Symfony/Component/VarDumper/composer.json +++ b/src/Symfony/Component/VarDumper/composer.json @@ -18,6 +18,7 @@ "ext-symfony_debug": "" }, "autoload": { + "files": [ "Resources/functions/dump.php" ], "psr-0": { "Symfony\\Component\\VarDumper\\": "" } }, "target-dir": "Symfony/Component/VarDumper", From c8746a43c9275413e0ebdbb8f91aa86d9eecb7a5 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Wed, 27 Aug 2014 22:14:19 +0200 Subject: [PATCH 20/31] [DebugBundle] add tests for twig and for the bundle --- src/Symfony/Bridge/Twig/Node/DumpNode.php | 6 +- .../Tests/Extension/DumpExtensionTest.php | 57 ++++++++++++ .../Bridge/Twig/Tests/Node/DumpNodeTest.php | 89 +++++++++++++++++++ .../DataCollector/DumpDataCollector.php | 7 +- .../HttpKernel/EventListener/DumpListener.php | 8 +- src/Symfony/Component/VarDumper/VarDumper.php | 4 +- .../Component/VarDumper/phpunit.xml.dist | 26 ++++++ 7 files changed, 185 insertions(+), 12 deletions(-) create mode 100644 src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php create mode 100644 src/Symfony/Bridge/Twig/Tests/Node/DumpNodeTest.php create mode 100644 src/Symfony/Component/VarDumper/phpunit.xml.dist diff --git a/src/Symfony/Bridge/Twig/Node/DumpNode.php b/src/Symfony/Bridge/Twig/Node/DumpNode.php index 2bae8fd38779e..23ab1125d5c71 100644 --- a/src/Symfony/Bridge/Twig/Node/DumpNode.php +++ b/src/Symfony/Bridge/Twig/Node/DumpNode.php @@ -62,7 +62,7 @@ public function compile(\Twig_Compiler $compiler) } else { $compiler ->addDebugInfo($this) - ->write('\Symfony\Component\VarDumper\VarDumper::dump(array(') + ->write('\Symfony\Component\VarDumper\VarDumper::dump(array('."\n") ->indent() ; foreach ($values as $node) { @@ -80,13 +80,13 @@ public function compile(\Twig_Compiler $compiler) } $compiler ->outdent() - ->raw("));\n") + ->write("));\n") ; } $compiler ->outdent() - ->write("}\n") + ->raw("}\n") ; } } diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php new file mode 100644 index 0000000000000..6f2d44aa51eec --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php @@ -0,0 +1,57 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\Extension; + +use Symfony\Bridge\Twig\Extension\DumpExtension; +use Symfony\Component\VarDumper\VarDumper; + +class DumpExtensionTest extends \PHPUnit_Framework_TestCase +{ + /** + * @dataProvider getDumpParams + */ + public function testDebugDump($template, $debug, $expectedOutput, $expectedDumped) + { + $twig = new \Twig_Environment(new \Twig_Loader_String(), array( + 'debug' => $debug, + 'cache' => false, + 'optimizations' => 0, + )); + $twig->addExtension(new DumpExtension()); + + $dumped = null; + $exception = null; + $prevDumper = VarDumper::setHandler(function ($var) use (&$dumped) {$dumped = $var;}); + + try { + $this->assertEquals($expectedOutput, $twig->render($template)); + } catch (\Exception $exception) { + } + + VarDumper::setHandler($prevDumper); + + if (null !== $exception) { + throw $exception; + } + + $this->assertSame($expectedDumped, $dumped); + } + + public function getDumpParams() + { + return array( + array('A{% dump %}B', true, 'AB', array()), + array('A{% set foo="bar"%}B{% dump %}C', true, 'ABC', array('foo' => 'bar')), + array('A{% dump %}B', false, 'AB', null), + ); + } +} diff --git a/src/Symfony/Bridge/Twig/Tests/Node/DumpNodeTest.php b/src/Symfony/Bridge/Twig/Tests/Node/DumpNodeTest.php new file mode 100644 index 0000000000000..1efe52d126045 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Tests/Node/DumpNodeTest.php @@ -0,0 +1,89 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Bridge\Twig\Tests\Node; + +use Symfony\Bridge\Twig\Node\DumpNode; + +class DumpNodeTest extends \PHPUnit_Framework_TestCase +{ + public function testNoVar() + { + $node = new DumpNode('bar', null, 7); + + $env = new \Twig_Environment(); + $compiler = new \Twig_Compiler($env); + + $expected = <<<'EOTXT' +if ($this->env->isDebug()) { + $barvars = array(); + foreach ($context as $barkey => $barval) { + if (!$barval instanceof \Twig_Template) { + $barvars[$barkey] = $barval; + } + } + // line 7 + \Symfony\Component\VarDumper\VarDumper::dump($barvars); +} + +EOTXT; + + $this->assertSame($expected, $compiler->compile($node)->getSource()); + } + + public function testOneVar() + { + $vars = new \Twig_Node(array( + new \Twig_Node_Expression_Name('foo', 7), + )); + $node = new DumpNode('bar', $vars, 7); + + $env = new \Twig_Environment(); + $compiler = new \Twig_Compiler($env); + + $expected = <<<'EOTXT' +if ($this->env->isDebug()) { + // line 7 + \Symfony\Component\VarDumper\VarDumper::dump(%foo%); +} + +EOTXT; + $expected = preg_replace('/%(.*?)%/', version_compare(PHP_VERSION, '5.4.0') >= 0 ? '(isset($context["$1"]) ? $context["$1"] : null)' : '$this->getContext($context, "$1")', $expected); + + $this->assertSame($expected, $compiler->compile($node)->getSource()); + } + + public function testMultiVars() + { + $vars = new \Twig_Node(array( + new \Twig_Node_Expression_Name('foo', 7), + new \Twig_Node_Expression_Name('bar', 7), + )); + $node = new DumpNode('bar', $vars, 7); + + $env = new \Twig_Environment(); + $compiler = new \Twig_Compiler($env); + + $expected = <<<'EOTXT' +if ($this->env->isDebug()) { + // line 7 + \Symfony\Component\VarDumper\VarDumper::dump(array( + "foo" => %foo%, + "bar" => %bar%, + )); +} + +EOTXT; + $expected = preg_replace('/%(.*?)%/', version_compare(PHP_VERSION, '5.4.0') >= 0 ? '(isset($context["$1"]) ? $context["$1"] : null)' : '$this->getContext($context, "$1")', $expected); + + $this->assertSame($expected, $compiler->compile($node)->getSource()); + } +} diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php index 1412934bacef1..29cd4454a176f 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php @@ -54,7 +54,7 @@ public function dump(Data $data) $trace = PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS : true; if (PHP_VERSION_ID >= 50400) { - $trace = debug_backtrace($trace, 6); + $trace = debug_backtrace($trace, 7); } else { $trace = debug_backtrace($trace); } @@ -72,10 +72,11 @@ public function dump(Data $data) $file = $trace[$i]['file']; $line = $trace[$i]['line']; - while (++$i < 6) { - if (isset($trace[$i]['function']) && empty($trace[$i]['class'])) { + while (++$i < 7) { + if (isset($trace[$i]['function']) && empty($trace[$i]['class']) && 'call_user_func' !== $trace[$i]['function']) { $file = $trace[$i]['file']; $line = $trace[$i]['line']; + break; } elseif (isset($trace[$i]['object']) && $trace[$i]['object'] instanceof \Twig_Template) { $info = $trace[$i]['object']; diff --git a/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php b/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php index 89dee034b510d..46833959b20dc 100644 --- a/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php +++ b/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php @@ -11,10 +11,10 @@ namespace Symfony\Component\HttpKernel\EventListener; -use Symfony\Component\Debug\Debug; +use Symfony\Component\DependencyInjection\ContainerInterface; use Symfony\Component\EventDispatcher\EventSubscriberInterface; use Symfony\Component\HttpKernel\KernelEvents; -use Symfony\Component\DependencyInjection\ContainerInterface; +use Symfony\Component\VarDumper\VarDumper; /** * Configures dump() handler. @@ -43,11 +43,11 @@ public function configure() $dumper = $this->dumper; $this->container = null; - Debug::setDumpHandler(function ($var) use ($container, $dumper) { + VarDumper::setHandler(function ($var) use ($container, $dumper) { $dumper = $container->get($dumper); $cloner = $container->get('var_dumper.cloner'); $handler = function ($var) use ($dumper, $cloner) {$dumper->dump($cloner->cloneVar($var));}; - Debug::setDumpHandler($handler); + VarDumper::setHandler($handler); $handler($var); }); } diff --git a/src/Symfony/Component/VarDumper/VarDumper.php b/src/Symfony/Component/VarDumper/VarDumper.php index 40c05f5f31abc..b92f5bafd2cf6 100644 --- a/src/Symfony/Component/VarDumper/VarDumper.php +++ b/src/Symfony/Component/VarDumper/VarDumper.php @@ -33,12 +33,12 @@ public static function dump($var) }; } - return call_user_func(self::$handler, $h); + return call_user_func(self::$handler, $var); } public static function setHandler($callable) { - if (!is_callable($callable, true)) { + if (null !== $callable && !is_callable($callable, true)) { throw new \InvalidArgumentException('Invalid PHP callback.'); } diff --git a/src/Symfony/Component/VarDumper/phpunit.xml.dist b/src/Symfony/Component/VarDumper/phpunit.xml.dist new file mode 100644 index 0000000000000..a75c8fc69c008 --- /dev/null +++ b/src/Symfony/Component/VarDumper/phpunit.xml.dist @@ -0,0 +1,26 @@ + + + + + + + ./Tests/ + + + + + + ./ + + ./Tests + ./Resources + ./vendor + + + + From 0d8a942cfa8971be767455041b19dc17a6c737f0 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Mon, 8 Sep 2014 20:39:52 +0200 Subject: [PATCH 21/31] [VarDumper] add Stub objects for cutting cleanly and dumping consts --- .../DataCollector/DumpDataCollector.php | 2 +- .../Component/VarDumper/Caster/CasterStub.php | 62 ++++++++++++ .../Component/VarDumper/Caster/DOMCaster.php | 93 ++++++++++------- .../VarDumper/Caster/DoctrineCaster.php | 34 ++----- .../VarDumper/Caster/ExceptionCaster.php | 11 ++- .../Component/VarDumper/Caster/PdoCaster.php | 8 +- .../VarDumper/Caster/ReflectionCaster.php | 8 +- .../VarDumper/Caster/ResourceCaster.php | 18 ++-- .../Component/VarDumper/Caster/SplCaster.php | 16 +-- .../Component/VarDumper/Caster/StubCaster.php | 45 +++++++++ .../VarDumper/Cloner/AbstractCloner.php | 39 +++++--- .../Component/VarDumper/Cloner/Data.php | 99 +++++++++---------- .../Component/VarDumper/Cloner/ExtCloner.php | 92 ++++++++++------- .../Component/VarDumper/Cloner/PhpCloner.php | 98 +++++++++++------- .../Component/VarDumper/Cloner/Stub.php | 39 ++++++++ .../Component/VarDumper/Dumper/CliDumper.php | 57 +++++------ .../Component/VarDumper/Dumper/Cursor.php | 14 +-- .../VarDumper/Tests/CliDumperTest.php | 6 +- .../VarDumper/Tests/HtmlDumperTest.php | 14 +-- 19 files changed, 483 insertions(+), 272 deletions(-) create mode 100644 src/Symfony/Component/VarDumper/Caster/CasterStub.php create mode 100644 src/Symfony/Component/VarDumper/Caster/StubCaster.php create mode 100644 src/Symfony/Component/VarDumper/Cloner/Stub.php diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php index 29cd4454a176f..95526115c333f 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php @@ -218,7 +218,7 @@ public function flushDumps() $h = headers_list(); $i = count($h); - array_unshift($h, 'Content-Type: ' . ini_get('default_mimetype')); + array_unshift($h, 'Content-Type: '.ini_get('default_mimetype')); while (0 !== stripos($h[$i], 'Content-Type:')) { --$i; } diff --git a/src/Symfony/Component/VarDumper/Caster/CasterStub.php b/src/Symfony/Component/VarDumper/Caster/CasterStub.php new file mode 100644 index 0000000000000..aaf8ed1035af1 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Caster/CasterStub.php @@ -0,0 +1,62 @@ + + * + * 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 the main properties of a PHP variable, pre-casted by a caster. + * + * @author Nicolas Grekas + */ +class CasterStub extends Stub +{ + public function __construct($value, $class = '') + { + switch (gettype($value)) { + case 'object': + $this->type = self::TYPE_OBJECT; + $this->class = get_class($value); + $this->value = spl_object_hash($value); + $this->cut = -1; + break; + + case 'array': + $this->type = self::TYPE_ARRAY; + $this->class = self::ARRAY_ASSOC; + $this->cut = $this->value = count($value); + break; + + case 'resource': + case 'unknown type': + $this->type = self::TYPE_RESOURCE; + $this->class = @get_resource_type($value); + $this->value = (int) $value; + $this->cut = -1; + break; + + case 'string': + if ('' === $class) { + $this->type = self::TYPE_STRING; + $this->class = preg_match('//u', $value) ? self::STRING_UTF8 : self::STRING_BINARY; + $this->cut = self::STRING_BINARY === $this->class ? strlen($value) : (function_exists('iconv_strlen') ? iconv_strlen($value, 'UTF-8') : -1); + break; + } + // No break; + + default: + $this->class = $class; + $this->value = $value; + break; + } + } +} diff --git a/src/Symfony/Component/VarDumper/Caster/DOMCaster.php b/src/Symfony/Component/VarDumper/Caster/DOMCaster.php index 248154143b3a0..2a0dd2b9a61e8 100644 --- a/src/Symfony/Component/VarDumper/Caster/DOMCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/DOMCaster.php @@ -11,6 +11,8 @@ namespace Symfony\Component\VarDumper\Caster; +use Symfony\Component\VarDumper\Cloner\Stub; + /** * Casts DOM related classes to array representation. * @@ -38,16 +40,37 @@ class DOMCaster DOM_VALIDATION_ERR => 'DOM_VALIDATION_ERR', ); - public static function castException(\DOMException $e, array $a, $isNested, &$cut) + private static $nodeTypes = array( + XML_ELEMENT_NODE => 'XML_ELEMENT_NODE', + XML_ATTRIBUTE_NODE => 'XML_ATTRIBUTE_NODE', + XML_TEXT_NODE => 'XML_TEXT_NODE', + XML_CDATA_SECTION_NODE => 'XML_CDATA_SECTION_NODE', + XML_ENTITY_REF_NODE => 'XML_ENTITY_REF_NODE', + XML_ENTITY_NODE => 'XML_ENTITY_NODE', + XML_PI_NODE => 'XML_PI_NODE', + XML_COMMENT_NODE => 'XML_COMMENT_NODE', + XML_DOCUMENT_NODE => 'XML_DOCUMENT_NODE', + XML_DOCUMENT_TYPE_NODE => 'XML_DOCUMENT_TYPE_NODE', + XML_DOCUMENT_FRAG_NODE => 'XML_DOCUMENT_FRAG_NODE', + XML_NOTATION_NODE => 'XML_NOTATION_NODE', + XML_HTML_DOCUMENT_NODE => 'XML_HTML_DOCUMENT_NODE', + XML_DTD_NODE => 'XML_DTD_NODE', + XML_ELEMENT_DECL_NODE => 'XML_ELEMENT_DECL_NODE', + XML_ATTRIBUTE_DECL_NODE => 'XML_ATTRIBUTE_DECL_NODE', + XML_ENTITY_DECL_NODE => 'XML_ENTITY_DECL_NODE', + XML_NAMESPACE_DECL_NODE => 'XML_NAMESPACE_DECL_NODE', + ); + + public static function castException(\DOMException $e, array $a, Stub $stub, $isNested) { if (isset($a["\0*\0code"], self::$errorCodes[$a["\0*\0code"]])) { - $a["\0*\0code"] .= ' ('.self::$errorCodes[$a["\0*\0code"]].')'; + $a["\0*\0code"] = new CasterStub(self::$errorCodes[$a["\0*\0code"]], 'const'); } return $a; } - public static function castLength($dom, array $a, $isNested, &$cut) + public static function castLength($dom, array $a, Stub $stub, $isNested) { $a += array( 'length' => $dom->length, @@ -56,7 +79,7 @@ public static function castLength($dom, array $a, $isNested, &$cut) return $a; } - public static function castImplementation($dom, array $a, $isNested, &$cut) + public static function castImplementation($dom, array $a, Stub $stub, $isNested) { $a += array( "\0~\0Core" => '1.0', @@ -66,53 +89,49 @@ public static function castImplementation($dom, array $a, $isNested, &$cut) return $a; } - public static function castNode(\DOMNode $dom, array $a, $isNested, &$cut) + public static function castNode(\DOMNode $dom, array $a, Stub $stub, $isNested) { - // Commented lines denote properties that exist but are better not dumped for clarity. - $a += array( 'nodeName' => $dom->nodeName, - //'nodeValue' => $dom->nodeValue, - 'nodeType' => $dom->nodeType, - //'parentNode' => $dom->parentNode, + 'nodeValue' => new CasterStub($dom->nodeValue), + 'nodeType' => new CasterStub(self::$nodeTypes[$dom->nodeType], 'const'), + 'parentNode' => new CasterStub($dom->parentNode), 'childNodes' => $dom->childNodes, - //'firstChild' => $dom->firstChild, - //'lastChild' => $dom->lastChild, - //'previousSibling' => $dom->previousSibling, - //'nextSibling' => $dom->nextSibling, + 'firstChild' => new CasterStub($dom->firstChild), + 'lastChild' => new CasterStub($dom->lastChild), + 'previousSibling' => new CasterStub($dom->previousSibling), + 'nextSibling' => new CasterStub($dom->nextSibling), 'attributes' => $dom->attributes, - //'ownerDocument' => $dom->ownerDocument, + 'ownerDocument' => new CasterStub($dom->ownerDocument), 'namespaceURI' => $dom->namespaceURI, 'prefix' => $dom->prefix, 'localName' => $dom->localName, 'baseURI' => $dom->baseURI, - //'textContent' => $dom->textContent, + 'textContent' => new CasterStub($dom->textContent), ); - $cut += 8; return $a; } - public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, $isNested, &$cut) + public static function castNameSpaceNode(\DOMNameSpaceNode $dom, array $a, Stub $stub, $isNested) { // Commented lines denote properties that exist but are better not dumped for clarity. $a += array( 'nodeName' => $dom->nodeName, - //'nodeValue' => $dom->nodeValue, - 'nodeType' => $dom->nodeType, + 'nodeValue' => new CasterStub($dom->nodeValue), + 'nodeType' => new CasterStub(self::$nodeTypes[$dom->nodeType], 'const'), 'prefix' => $dom->prefix, 'localName' => $dom->localName, 'namespaceURI' => $dom->namespaceURI, - //'ownerDocument' => $dom->ownerDocument, - //'parentNode' => $dom->parentNode, + 'ownerDocument' => new CasterStub($dom->ownerDocument), + 'parentNode' => new CasterStub($dom->parentNode), ); - $cut += 3; return $a; } - public static function castDocument(\DOMDocument $dom, array $a, $isNested, &$cut) + public static function castDocument(\DOMDocument $dom, array $a, Stub $stub, $isNested) { $formatOutput = $dom->formatOutput; $dom->formatOutput = true; @@ -120,7 +139,7 @@ public static function castDocument(\DOMDocument $dom, array $a, $isNested, &$cu $a += array( 'doctype' => $dom->doctype, 'implementation' => $dom->implementation, - 'documentElement' => $dom->documentElement, + 'documentElement' => new CasterStub($dom->documentElement), 'actualEncoding' => $dom->actualEncoding, 'encoding' => $dom->encoding, 'xmlEncoding' => $dom->xmlEncoding, @@ -145,7 +164,7 @@ public static function castDocument(\DOMDocument $dom, array $a, $isNested, &$cu return $a; } - public static function castCharacterData(\DOMCharacterData $dom, array $a, $isNested, &$cut) + public static function castCharacterData(\DOMCharacterData $dom, array $a, Stub $stub, $isNested) { $a += array( 'data' => $dom->data, @@ -155,7 +174,7 @@ public static function castCharacterData(\DOMCharacterData $dom, array $a, $isNe return $a; } - public static function castAttr(\DOMAttr $dom, array $a, $isNested, &$cut) + public static function castAttr(\DOMAttr $dom, array $a, Stub $stub, $isNested) { $a += array( 'name' => $dom->name, @@ -168,7 +187,7 @@ public static function castAttr(\DOMAttr $dom, array $a, $isNested, &$cut) return $a; } - public static function castElement(\DOMElement $dom, array $a, $isNested, &$cut) + public static function castElement(\DOMElement $dom, array $a, Stub $stub, $isNested) { $a += array( 'tagName' => $dom->tagName, @@ -178,7 +197,7 @@ public static function castElement(\DOMElement $dom, array $a, $isNested, &$cut) return $a; } - public static function castText(\DOMText $dom, array $a, $isNested, &$cut) + public static function castText(\DOMText $dom, array $a, Stub $stub, $isNested) { $a += array( 'wholeText' => $dom->wholeText, @@ -187,7 +206,7 @@ public static function castText(\DOMText $dom, array $a, $isNested, &$cut) return $a; } - public static function castTypeinfo(\DOMTypeinfo $dom, array $a, $isNested, &$cut) + public static function castTypeinfo(\DOMTypeinfo $dom, array $a, Stub $stub, $isNested) { $a += array( 'typeName' => $dom->typeName, @@ -197,7 +216,7 @@ public static function castTypeinfo(\DOMTypeinfo $dom, array $a, $isNested, &$cu return $a; } - public static function castDomError(\DOMDomError $dom, array $a, $isNested, &$cut) + public static function castDomError(\DOMDomError $dom, array $a, Stub $stub, $isNested) { $a += array( 'severity' => $dom->severity, @@ -211,7 +230,7 @@ public static function castDomError(\DOMDomError $dom, array $a, $isNested, &$cu return $a; } - public static function castLocator(\DOMLocator $dom, array $a, $isNested, &$cut) + public static function castLocator(\DOMLocator $dom, array $a, Stub $stub, $isNested) { $a += array( 'lineNumber' => $dom->lineNumber, @@ -224,7 +243,7 @@ public static function castLocator(\DOMLocator $dom, array $a, $isNested, &$cut) return $a; } - public static function castDocumentType(\DOMDocumentType $dom, array $a, $isNested, &$cut) + public static function castDocumentType(\DOMDocumentType $dom, array $a, Stub $stub, $isNested) { $a += array( 'name' => $dom->name, @@ -238,7 +257,7 @@ public static function castDocumentType(\DOMDocumentType $dom, array $a, $isNest return $a; } - public static function castNotation(\DOMNotation $dom, array $a, $isNested, &$cut) + public static function castNotation(\DOMNotation $dom, array $a, Stub $stub, $isNested) { $a += array( 'publicId' => $dom->publicId, @@ -248,7 +267,7 @@ public static function castNotation(\DOMNotation $dom, array $a, $isNested, &$cu return $a; } - public static function castEntity(\DOMEntity $dom, array $a, $isNested, &$cut) + public static function castEntity(\DOMEntity $dom, array $a, Stub $stub, $isNested) { $a += array( 'publicId' => $dom->publicId, @@ -262,7 +281,7 @@ public static function castEntity(\DOMEntity $dom, array $a, $isNested, &$cut) return $a; } - public static function castProcessingInstruction(\DOMProcessingInstruction $dom, array $a, $isNested, &$cut) + public static function castProcessingInstruction(\DOMProcessingInstruction $dom, array $a, Stub $stub, $isNested) { $a += array( 'target' => $dom->target, @@ -272,7 +291,7 @@ public static function castProcessingInstruction(\DOMProcessingInstruction $dom, return $a; } - public static function castXPath(\DOMXPath $dom, array $a, $isNested, &$cut) + public static function castXPath(\DOMXPath $dom, array $a, Stub $stub, $isNested) { $a += array( 'document' => $dom->document, diff --git a/src/Symfony/Component/VarDumper/Caster/DoctrineCaster.php b/src/Symfony/Component/VarDumper/Caster/DoctrineCaster.php index 49e1b7bbed3f7..30d7ca6ef1a92 100644 --- a/src/Symfony/Component/VarDumper/Caster/DoctrineCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/DoctrineCaster.php @@ -11,10 +11,10 @@ namespace Symfony\Component\VarDumper\Caster; -use Doctrine\Common\Persistence\ObjectManager; use Doctrine\Common\Proxy\Proxy as CommonProxy; use Doctrine\ORM\Proxy\Proxy as OrmProxy; use Doctrine\ORM\PersistentCollection; +use Symfony\Component\VarDumper\Cloner\Stub; /** * Casts Doctrine related classes to array representation. @@ -23,50 +23,36 @@ */ class DoctrineCaster { - public static function castCommonProxy(CommonProxy $proxy, array $a, $isNested, &$cut) + public static function castCommonProxy(CommonProxy $proxy, array $a, Stub $stub, $isNested) { unset( $a['__cloner__'], $a['__initializer__'] ); - $cut += 2; + $stub->cut += 2; return $a; } - public static function castOrmProxy(OrmProxy $proxy, array $a, $isNested, &$cut) + public static function castOrmProxy(OrmProxy $proxy, array $a, Stub $stub, $isNested) { $prefix = "\0Doctrine\\ORM\\Proxy\\Proxy\0"; unset( $a[$prefix.'_entityPersister'], $a[$prefix.'_identifier'] ); - $cut += 2; + $stub->cut += 2; return $a; } - public static function castObjectManager(ObjectManager $manager, array $a, $isNested, &$cut) - { - if ($isNested) { - $cut += count($a); - - return array(); - } - - return $a; - } - - public static function castPersistentCollection(PersistentCollection $coll, array $a, $isNested, &$cut) + public static function castPersistentCollection(PersistentCollection $coll, array $a, Stub $stub, $isNested) { $prefix = "\0Doctrine\\ORM\\PersistentCollection\0"; - unset( - $a[$prefix.'snapshot'], - $a[$prefix.'association'], - $a[$prefix.'em'], - $a[$prefix.'typeClass'] - ); - $cut += 4; + + $a[$prefix.'snapshot'] = new CasterStub($a[$prefix.'snapshot']); + $a[$prefix.'association'] = new CasterStub($a[$prefix.'association']); + $a[$prefix.'typeClass'] = new CasterStub($a[$prefix.'typeClass']); return $a; } diff --git a/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php index 4abc2c53c8737..120eae1d2a030 100644 --- a/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php @@ -12,6 +12,7 @@ namespace Symfony\Component\VarDumper\Caster; use Symfony\Component\VarDumper\Exception\ThrowingCasterException; +use Symfony\Component\VarDumper\Cloner\Stub; /** * Casts common Exception classes to array representation. @@ -39,7 +40,7 @@ class ExceptionCaster E_STRICT => 'E_STRICT', ); - public static function castException(\Exception $e, array $a) + public static function castException(\Exception $e, array $a, Stub $stub, $isNested) { $trace = $a["\0Exception\0trace"]; unset($a["\0Exception\0trace"]); // Ensures the trace is always last @@ -57,16 +58,16 @@ public static function castException(\Exception $e, array $a) return $a; } - public static function castErrorException(\ErrorException $e, array $a) + public static function castErrorException(\ErrorException $e, array $a, Stub $stub, $isNested) { if (isset($a[$s = "\0*\0severity"], self::$errorTypes[$a[$s]])) { - $a[$s] = self::$errorTypes[$a[$s]]; + $a[$s] = new CasterStub(self::$errorTypes[$a[$s]], 'const'); } return $a; } - public static function castThrowingCasterException(ThrowingCasterException $e, array $a) + public static function castThrowingCasterException(ThrowingCasterException $e, array $a, Stub $stub, $isNested) { $b = (array) $a["\0Exception\0previous"]; @@ -74,7 +75,7 @@ public static function castThrowingCasterException(ThrowingCasterException $e, a $t = static::$traceArgs; static::$traceArgs = false; - $b = static::castException($a["\0Exception\0previous"], $b); + $b = static::castException($a["\0Exception\0previous"], $b, $stub, $isNested); static::$traceArgs = $t; if (empty($a["\0*\0message"])) { diff --git a/src/Symfony/Component/VarDumper/Caster/PdoCaster.php b/src/Symfony/Component/VarDumper/Caster/PdoCaster.php index 0b1c2d17efcd4..90dac4708d5f3 100644 --- a/src/Symfony/Component/VarDumper/Caster/PdoCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/PdoCaster.php @@ -11,6 +11,8 @@ namespace Symfony\Component\VarDumper\Caster; +use Symfony\Component\VarDumper\Cloner\Stub; + /** * Casts PDO related classes to array representation. * @@ -55,7 +57,7 @@ class PdoCaster ), ); - public static function castPdo(\PDO $c, array $a) + public static function castPdo(\PDO $c, array $a, Stub $stub, $isNested) { $a = array(); $errmode = $c->getAttribute(\PDO::ATTR_ERRMODE); @@ -70,7 +72,7 @@ public static function castPdo(\PDO $c, array $a) try { $a[$attr] = 'ERRMODE' === $attr ? $errmode : $c->getAttribute(constant("PDO::ATTR_{$attr}")); if (isset($values[$a[$attr]])) { - $a[$attr] = $values[$a[$attr]]; + $a[$attr] = new CasterStub($values[$a[$attr]], 'const'); } } catch (\Exception $m) { } @@ -98,7 +100,7 @@ public static function castPdo(\PDO $c, array $a) return $a; } - public static function castPdoStatement(\PDOStatement $c, array $a) + public static function castPdoStatement(\PDOStatement $c, array $a, Stub $stub, $isNested) { $m = "\0~\0"; $a[$m.'errorInfo'] = $c->errorInfo(); diff --git a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php index 0c39c460ebd43..a3acb93670bf2 100644 --- a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php @@ -11,6 +11,8 @@ namespace Symfony\Component\VarDumper\Caster; +use Symfony\Component\VarDumper\Cloner\Stub; + /** * Casts Reflector related classes to array representation. * @@ -18,16 +20,16 @@ */ class ReflectionCaster { - public static function castReflector(\Reflector $c, array $a) + public static function castReflector(\Reflector $c, array $a, Stub $stub, $isNested) { $a["\0~\0reflection"] = $c->__toString(); return $a; } - public static function castClosure(\Closure $c, array $a) + public static function castClosure(\Closure $c, array $a, Stub $stub, $isNested) { - $a = static::castReflector(new \ReflectionFunction($c), $a); + $a = static::castReflector(new \ReflectionFunction($c), $a, $stub, $isNested); unset($a["\0+\0000"], $a['name']); return $a; diff --git a/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php b/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php index c381bf73ab2d6..419280ee4b9fd 100644 --- a/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php @@ -11,6 +11,8 @@ namespace Symfony\Component\VarDumper\Caster; +use Symfony\Component\VarDumper\Cloner\Stub; + /** * Casts common resource types to array representation. * @@ -18,12 +20,12 @@ */ class ResourceCaster { - public static function castCurl($h, array $a) + public static function castCurl($h, array $a, Stub $stub, $isNested) { return curl_getinfo($h); } - public static function castDba($dba, array $a) + public static function castDba($dba, array $a, Stub $stub, $isNested) { $list = dba_list(); $a['file'] = $list[substr((string) $dba, 13)]; @@ -31,22 +33,22 @@ public static function castDba($dba, array $a) return $a; } - public static function castProcess($process, array $a) + public static function castProcess($process, array $a, Stub $stub, $isNested) { return proc_get_status($process); } - public static function castStream($stream, array $a) + public static function castStream($stream, array $a, Stub $stub, $isNested) { - return stream_get_meta_data($stream) + static::castStreamContext($stream, $a); + return stream_get_meta_data($stream) + static::castStreamContext($stream, $a, $stub, $isNested); } - public static function castStreamContext($stream, array $a) + public static function castStreamContext($stream, array $a, Stub $stub, $isNested) { return stream_context_get_params($stream); } - public static function castGd($gd, array $a) + public static function castGd($gd, array $a, Stub $stub, $isNested) { $a['size'] = imagesx($gd).'x'.imagesy($gd); $a['trueColor'] = imageistruecolor($gd); @@ -54,7 +56,7 @@ public static function castGd($gd, array $a) return $a; } - public static function castMysqlLink($h, array $a) + public static function castMysqlLink($h, array $a, Stub $stub, $isNested) { $a['host'] = mysql_get_host_info($h); $a['protocol'] = mysql_get_proto_info($h); diff --git a/src/Symfony/Component/VarDumper/Caster/SplCaster.php b/src/Symfony/Component/VarDumper/Caster/SplCaster.php index 9f4680294d6c1..9b5377195ecbb 100644 --- a/src/Symfony/Component/VarDumper/Caster/SplCaster.php +++ b/src/Symfony/Component/VarDumper/Caster/SplCaster.php @@ -11,6 +11,8 @@ namespace Symfony\Component\VarDumper\Caster; +use Symfony\Component\VarDumper\Cloner\Stub; + /** * Casts SPL related classes to array representation. * @@ -18,9 +20,9 @@ */ class SplCaster { - public static function castArrayObject(\ArrayObject $c, array $a) + public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, $isNested) { - $class = get_class($c); + $class = $stub->class; $flags = $c->getFlags(); $b = array( @@ -55,7 +57,7 @@ public static function castArrayObject(\ArrayObject $c, array $a) return $a; } - public static function castHeap(\Iterator $c, array $a) + public static function castHeap(\Iterator $c, array $a, Stub $stub, $isNested) { $a += array( "\0~\0heap" => iterator_to_array(clone $c), @@ -64,13 +66,13 @@ public static function castHeap(\Iterator $c, array $a) return $a; } - public static function castDoublyLinkedList(\SplDoublyLinkedList $c, array $a) + public static function castDoublyLinkedList(\SplDoublyLinkedList $c, array $a, Stub $stub, $isNested) { $mode = $c->getIteratorMode(); $c->setIteratorMode(\SplDoublyLinkedList::IT_MODE_KEEP | $mode & ~\SplDoublyLinkedList::IT_MODE_DELETE); $a += array( - "\0~\0mode" => (($mode & \SplDoublyLinkedList::IT_MODE_LIFO) ? 'IT_MODE_LIFO' : 'IT_MODE_FIFO').' | '.(($mode & \SplDoublyLinkedList::IT_MODE_KEEP) ? 'IT_MODE_KEEP' : 'IT_MODE_DELETE'), + "\0~\0mode" => new CasterStub((($mode & \SplDoublyLinkedList::IT_MODE_LIFO) ? 'IT_MODE_LIFO' : 'IT_MODE_FIFO').' | '.(($mode & \SplDoublyLinkedList::IT_MODE_KEEP) ? 'IT_MODE_KEEP' : 'IT_MODE_DELETE'), 'const'), "\0~\0dllist" => iterator_to_array($c), ); $c->setIteratorMode($mode); @@ -78,7 +80,7 @@ public static function castDoublyLinkedList(\SplDoublyLinkedList $c, array $a) return $a; } - public static function castFixedArray(\SplFixedArray $c, array $a) + public static function castFixedArray(\SplFixedArray $c, array $a, Stub $stub, $isNested) { $a += array( "\0~\0storage" => $c->toArray(), @@ -87,7 +89,7 @@ public static function castFixedArray(\SplFixedArray $c, array $a) return $a; } - public static function castObjectStorage(\SplObjectStorage $c, array $a) + public static function castObjectStorage(\SplObjectStorage $c, array $a, Stub $stub, $isNested) { $storage = array(); unset($a["\0+\0\0gcdata"]); // Don't hit https://bugs.php.net/65967 diff --git a/src/Symfony/Component/VarDumper/Caster/StubCaster.php b/src/Symfony/Component/VarDumper/Caster/StubCaster.php new file mode 100644 index 0000000000000..294a13c562c2b --- /dev/null +++ b/src/Symfony/Component/VarDumper/Caster/StubCaster.php @@ -0,0 +1,45 @@ + + * + * 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 a CasterStub. + * + * @author Nicolas Grekas + */ +class StubCaster +{ + public static function castStub(CasterStub $c, array $a, Stub $stub, $isNested) + { + if ($isNested) { + $stub->type = $c->type; + $stub->class = $c->class; + $stub->value = $c->value; + $stub->cut = $c->cut; + + return array(); + } + } + + public static function castNestedFat($obj, array $a, Stub $stub, $isNested) + { + if ($isNested) { + $stub->cut += count($a); + + return array(); + } + + return $a; + } +} diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php index 69fd84e37ef70..7daf58fb25d68 100644 --- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php @@ -21,10 +21,12 @@ abstract class AbstractCloner implements ClonerInterface { public static $defaultCasters = array( + 'o:Symfony\Component\VarDumper\Caster\CasterStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castStub', + 'o:Closure' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castClosure', 'o:Reflector' => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castReflector', - 'o:Doctrine\Common\Persistence\ObjectManager' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castObjectManager', + 'o:Doctrine\Common\Persistence\ObjectManager' => 'Symfony\Component\VarDumper\Caster\StubCaster::castNestedFat', 'o:Doctrine\Common\Proxy\Proxy' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castCommonProxy', 'o:Doctrine\ORM\Proxy\Proxy' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castOrmProxy', 'o:Doctrine\ORM\PersistentCollection' => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castPersistentCollection', @@ -54,6 +56,8 @@ abstract class AbstractCloner implements ClonerInterface 'o:ErrorException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castErrorException', 'o:Exception' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castException', + 'o:Symfony\Component\DependencyInjection\ContainerInterface' + => 'Symfony\Component\VarDumper\Caster\StubCaster::castNestedFat', 'o:Symfony\Component\VarDumper\Exception\ThrowingCasterException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castThrowingCasterException', @@ -171,19 +175,22 @@ abstract protected function doClone($var); /** * Casts an object to an array representation. * - * @param string $class The class of the object. * @param object $obj The object itself. + * @param Stub $stub The Stub for the casted object. * @param bool $isNested True if the object is nested in the dumped structure. - * @param int &$cut After the cast, number of items removed from $obj. * * @return array The object casted as array. */ - protected function castObject($class, $obj, $isNested, &$cut) + protected function castObject($obj, Stub $stub, $isNested) { + $class = $stub->class; + if (isset($this->classInfo[$class])) { $classInfo = $this->classInfo[$class]; + $stub->class = $classInfo[0]; } else { $classInfo = array( + $class, method_exists($class, '__debugInfo'), new \ReflectionClass($class), array_reverse(array($class => $class) + class_parents($class) + class_implements($class) + array('*' => '*')), @@ -192,24 +199,23 @@ protected function castObject($class, $obj, $isNested, &$cut) $this->classInfo[$class] = $classInfo; } - if ($classInfo[0]) { - $a = $this->callCaster(array($obj, '__debugInfo'), $obj, array(), $isNested); + if ($classInfo[1]) { + $a = $this->callCaster(array($obj, '__debugInfo'), $obj, array(), null, $isNested); } else { $a = (array) $obj; } - $cut = 0; foreach ($a as $k => $p) { - if (!isset($k[0]) || ("\0" !== $k[0] && !$classInfo[1]->hasProperty($k))) { + if (!isset($k[0]) || ("\0" !== $k[0] && !$classInfo[2]->hasProperty($k))) { unset($a[$k]); $a["\0+\0".$k] = $p; } } - foreach ($classInfo[2] as $p) { + foreach ($classInfo[3] as $p) { if (!empty($this->casters[$p = 'o:'.strtolower($p)])) { foreach ($this->casters[$p] as $p) { - $a = $this->callCaster($p, $obj, $a, $isNested, $cut); + $a = $this->callCaster($p, $obj, $a, $stub, $isNested); } } } @@ -220,19 +226,20 @@ protected function castObject($class, $obj, $isNested, &$cut) /** * Casts a resource to an array representation. * - * @param string $type The type of the resource. * @param resource $res The resource. + * @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. */ - protected function castResource($type, $res, $isNested) + protected function castResource($res, Stub $stub, $isNested) { $a = array(); + $type = $stub->class; if (!empty($this->casters['r:'.$type])) { foreach ($this->casters['r:'.$type] as $c) { - $a = $this->callCaster($c, $res, $a, $isNested); + $a = $this->callCaster($c, $res, $a, $stub, $isNested); } } @@ -245,15 +252,15 @@ protected function castResource($type, $res, $isNested) * @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. - * @param int &$cut After the cast, number of items removed from $obj. * * @return array The casted object/resource. */ - private function callCaster($callback, $obj, $a, $isNested, &$cut = 0) + private function callCaster($callback, $obj, $a, $stub, $isNested) { try { - $cast = call_user_func_array($callback, array($obj, $a, $isNested, &$cut)); + $cast = call_user_func($callback, $obj, $a, $stub, $isNested); if (is_array($cast)) { $a = $cast; diff --git a/src/Symfony/Component/VarDumper/Cloner/Data.php b/src/Symfony/Component/VarDumper/Cloner/Data.php index 5da5916dea5dc..0df6997873ab3 100644 --- a/src/Symfony/Component/VarDumper/Cloner/Data.php +++ b/src/Symfony/Component/VarDumper/Cloner/Data.php @@ -49,29 +49,34 @@ public function dump(DumperInternalsInterface $dumper) * @param DumperInternalsInterface $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 stdClass or the original value being dumped. + * @param mixed $item A Stub object or the original value being dumped. */ private function dumpItem($dumper, $cursor, &$refs, $item) { - $cursor->refIndex = $cursor->refTo = $cursor->refIsHard = false; - - if ($item instanceof \stdClass) { - if (property_exists($item, 'val')) { - if (isset($item->ref)) { - if (isset($refs[$r = $item->ref])) { - $cursor->refTo = $refs[$r]; - $cursor->refIsHard = true; - } else { - $cursor->refIndex = $refs[$r] = ++$refs[0]; - } + $cursor->refIndex = $cursor->softRefTo = $cursor->hardRefTo = false; + + if (!$item instanceof Stub) { + $type = gettype($item); + } elseif (Stub::TYPE_REF === $item->type) { + if ($item->ref) { + if (isset($refs[$r = $item->ref])) { + $cursor->hardRefTo = $refs[$r]; + } else { + $cursor->refIndex = $refs[$r] = ++$refs[0]; } - $item = $item->val; } - if (isset($item->ref)) { + $type = $item->class ?: gettype($item->value); + $item = $item->value; + } + if ($item instanceof Stub) { + if ($item->ref) { if (isset($refs[$r = $item->ref])) { - if (false === $cursor->refTo) { - $cursor->refTo = $refs[$r]; - $cursor->refIsHard = isset($item->count); + if (Stub::TYPE_ARRAY === $item->type) { + if (false === $cursor->hardRefTo) { + $cursor->hardRefTo = $refs[$r]; + } + } elseif (false === $cursor->softRefTo) { + $cursor->softRefTo = $refs[$r]; } } elseif (false !== $cursor->refIndex) { $refs[$r] = $cursor->refIndex; @@ -79,10 +84,10 @@ private function dumpItem($dumper, $cursor, &$refs, $item) $cursor->refIndex = $refs[$r] = ++$refs[0]; } } - $cut = isset($item->cut) ? $item->cut : 0; + $cut = $item->cut; - if (isset($item->pos) && false === $cursor->refTo) { - $children = $this->data[$item->pos]; + if ($item->position && false === $cursor->softRefTo && false === $cursor->hardRefTo) { + $children = $this->data[$item->position]; if ($cursor->stop) { if ($cut >= 0) { @@ -93,41 +98,33 @@ private function dumpItem($dumper, $cursor, &$refs, $item) } else { $children = array(); } - switch (true) { - case isset($item->bin): - $dumper->dumpString($cursor, $item->bin, true, $cut); - - return; - - case isset($item->str): - $dumper->dumpString($cursor, $item->str, false, $cut); - - return; - - case isset($item->count): - $dumper->enterArray($cursor, $item->count, !empty($item->indexed), (bool) $children); - $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, empty($item->indexed) ? $cursor::HASH_ASSOC : $cursor::HASH_INDEXED); - $dumper->leaveArray($cursor, $item->count, !empty($item->indexed), (bool) $children, $cut); - - return; - - case isset($item->class): + switch ($item->type) { + case Stub::TYPE_STRING: + $dumper->dumpString($cursor, $item->value, Stub::STRING_BINARY === $item->class, $cut); + break; + + case Stub::TYPE_ARRAY: + $dumper->enterArray($cursor, $item->value, Stub::ARRAY_INDEXED === $item->class, (bool) $children); + $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $item->class); + $dumper->leaveArray($cursor, $item->value, Stub::ARRAY_INDEXED === $item->class, (bool) $children, $cut); + break; + + case Stub::TYPE_OBJECT: $dumper->enterObject($cursor, $item->class, (bool) $children); - $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $cursor::HASH_OBJECT); + $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, Cursor::HASH_OBJECT); $dumper->leaveObject($cursor, $item->class, (bool) $children, $cut); + break; - return; - - case isset($item->res): - $dumper->enterResource($cursor, $item->res, (bool) $children); - $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, $cursor::HASH_RESOURCE); - $dumper->leaveResource($cursor, $item->res, (bool) $children, $cut); + case Stub::TYPE_RESOURCE: + $dumper->enterResource($cursor, $item->class, (bool) $children); + $cut = $this->dumpChildren($dumper, $cursor, $refs, $children, $cut, Cursor::HASH_RESOURCE); + $dumper->leaveResource($cursor, $item->class, (bool) $children, $cut); + break; - return; + default: + throw new \RuntimeException(sprintf('Unexpected Stub type: %s', $item->type)); } - } - - if ('array' === $type = gettype($item)) { + } elseif ('array' === $type) { $dumper->enterArray($cursor, 0, true, 0, 0); $dumper->leaveArray($cursor, 0, true, 0, 0); } else { @@ -143,7 +140,7 @@ private function dumpItem($dumper, $cursor, &$refs, $item) * @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 int $hashType A Cursor::HASH_* const. + * @param string $hashType A Cursor::HASH_* const. * * @return int The final number of removed items. */ diff --git a/src/Symfony/Component/VarDumper/Cloner/ExtCloner.php b/src/Symfony/Component/VarDumper/Cloner/ExtCloner.php index 00fa51efbaf1a..9aefcc9f4aa3b 100644 --- a/src/Symfony/Component/VarDumper/Cloner/ExtCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/ExtCloner.php @@ -32,7 +32,7 @@ protected function doClone($var) $maxItems = $this->maxItems; $maxString = $this->maxString; $a = null; // Array cast for nested structures - $stub = null; // stdClass 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 for ($i = 0; $i < $len; ++$i) { @@ -60,15 +60,20 @@ protected function doClone($var) switch ($zval['type']) { case 'string': if (isset($v[0]) && !preg_match('//u', $v)) { + $stub = new Stub(); + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_BINARY; if (0 <= $maxString && 0 < $cut = strlen($v) - $maxString) { - $stub = substr_replace($v, '', -$cut); - $stub = (object) array('cut' => $cut, 'bin' => Data::utf8Encode($stub)); - } else { - $stub = (object) array('bin' => Data::utf8Encode($v)); + $stub->cut = $cut; + $v = substr_replace($v, '', -$cut); } + $stub->value = Data::utf8Encode($v); } elseif (0 <= $maxString && isset($v[1+($maxString>>2)]) && 0 < $cut = iconv_strlen($v, 'UTF-8') - $maxString) { - $stub = iconv_substr($v, 0, $maxString, 'UTF-8'); - $stub = (object) array('cut' => $cut, 'str' => $stub); + $stub = new Stub(); + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_UTF8; + $stub->cut = $cut; + $stub->value = iconv_substr($v, 0, $maxString, 'UTF-8'); } break; @@ -77,23 +82,33 @@ protected function doClone($var) case 'array': if ($v) { - $stub = (object) array('count' => $zval['array_count']); - $arrayRefs[$len] = $stub; + $stub = $arrayRefs[$len] = new Stub(); + $stub->type = Stub::TYPE_ARRAY; + $stub->class = Stub::ARRAY_ASSOC; + $stub->value = $zval['array_count']; $a = $v; } break; case 'object': if (empty($softRefs[$h = $zval['object_hash']])) { - $stub = $softRefs[$h] = (object) array('class' => $zval['object_class']); - if (0 > $maxItems || $pos < $maxItems) { - $a = $this->castObject($stub->class, $v, 0 < $i, $cut); - if ($cut) { - $stub->cut = $cut; - } - } else { - $stub->cut = -1; + $stub = new Stub(); + $stub->type = Stub::TYPE_OBJECT; + $stub->class = $zval['object_class']; + $stub->value = $h; + $a = $this->castObject($v, $stub, 0 < $i); + if (Stub::TYPE_OBJECT !== $stub->type) { + break; + } + $h = $stub->value; + $stub->value = ''; + if (0 <= $maxItems && $maxItems <= $pos) { + $stub->cut = count($a); + $a = array(); } + } + if (empty($softRefs[$h])) { + $softRefs[$h] = $stub; } else { $stub = $softRefs[$h]; $stub->ref = ++$refs; @@ -101,13 +116,24 @@ protected function doClone($var) break; case 'resource': - if (empty($softRefs[$h = $zval['resource_id']])) { - $stub = $softRefs[$h] = (object) array('res' => $zval['resource_type']); - if (0 > $maxItems || $pos < $maxItems) { - $a = $this->castResource($stub->res, $v, 0 < $i); - } else { - $stub->cut = -1; + if (empty($softRefs[$h = (int) $v])) { + $stub = new Stub(); + $stub->type = Stub::TYPE_RESOURCE; + $stub->class = $zval['resource_type']; + $stub->value = $h; + $a = $this->castResource($v, $stub, 0 < $i); + if (Stub::TYPE_RESOURCE !== $stub->type) { + break; } + $h = $stub->value; + $stub->value = ''; + if (0 <= $maxItems && $maxItems <= $pos) { + $stub->cut = count($a); + $a = array(); + } + } + if (empty($softRefs[$h])) { + $softRefs[$h] = $stub; } else { $stub = $softRefs[$h]; $stub->ref = ++$refs; @@ -117,10 +143,11 @@ protected function doClone($var) if (isset($stub)) { if ($zval['zval_isref']) { - if (isset($stub->count)) { + if (Stub::TYPE_ARRAY === $stub->type) { $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = $stub; } else { - $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = (object) array('val' => $stub); + $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = $v = new Stub(); + $v->value = $stub; } } else { $queue[$i][$k] = $stub; @@ -132,16 +159,12 @@ protected function doClone($var) if ($pos < $maxItems) { if ($maxItems < $pos += $k) { $a = array_slice($a, 0, $maxItems - $pos); - if (empty($stub->cut)) { - $stub->cut = $pos - $maxItems; - } elseif ($stub->cut > 0) { + if ($stub->cut >= 0) { $stub->cut += $pos - $maxItems; } } } else { - if (empty($stub->cut)) { - $stub->cut = $k; - } elseif ($stub->cut > 0) { + if ($stub->cut >= 0) { $stub->cut += $k; } $stub = $a = null; @@ -150,17 +173,18 @@ protected function doClone($var) } } $queue[$len] = $a; - $stub->pos = $len++; + $stub->position = $len++; } $stub = $a = null; } elseif ($zval['zval_isref']) { - $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = (object) array('val' => $v); + $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = new Stub(); + $queue[$i][$k]->value = $v; } } if (isset($arrayRefs[$i])) { if ($indexed) { - $arrayRefs[$i]->indexed = 1; + $arrayRefs[$i]->class = Stub::ARRAY_INDEXED; } unset($arrayRefs[$i]); } diff --git a/src/Symfony/Component/VarDumper/Cloner/PhpCloner.php b/src/Symfony/Component/VarDumper/Cloner/PhpCloner.php index 19a5b6bf069ae..46fa6b0f0cbc3 100644 --- a/src/Symfony/Component/VarDumper/Cloner/PhpCloner.php +++ b/src/Symfony/Component/VarDumper/Cloner/PhpCloner.php @@ -35,7 +35,7 @@ protected function doClone($var) $cookie = (object) array(); // Unique object used to detect hard references $isRef = false; $a = null; // Array cast for nested structures - $stub = null; // stdClass 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 for ($i = 0; $i < $len; ++$i) { @@ -52,7 +52,7 @@ protected function doClone($var) if ($queue[$i][$k] === $cookie) { $queue[$i][$k] =& $stub; // Break hard references to make $queue completely unset($stub); // independent from the original structure - if ($v instanceof \stdClass && isset($hardRefs[spl_object_hash($v)])) { + if ($v instanceof Stub && isset($hardRefs[spl_object_hash($v)])) { $v->ref = ++$refs; $step[$k] = $queue[$i][$k] = $v; continue; @@ -64,15 +64,22 @@ protected function doClone($var) switch (gettype($v)) { case 'string': if (isset($v[0]) && !preg_match('//u', $v)) { + $stub = new Stub(); + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_BINARY; if (0 <= $maxString && 0 < $cut = strlen($v) - $maxString) { - $stub = substr_replace($v, '', -$cut); - $stub = (object) array('cut' => $cut, 'bin' => Data::utf8Encode($stub)); + $stub->cut = $cut; + $cut = substr_replace($v, '', -$cut); } else { - $stub = (object) array('bin' => Data::utf8Encode($v)); + $cut = $v; } + $stub->value = Data::utf8Encode($cut); } elseif (0 <= $maxString && isset($v[1+($maxString>>2)]) && 0 < $cut = iconv_strlen($v, 'UTF-8') - $maxString) { - $stub = iconv_substr($v, 0, $maxString, 'UTF-8'); - $stub = (object) array('cut' => $cut, 'str' => $stub); + $stub = new Stub(); + $stub->type = Stub::TYPE_STRING; + $stub->class = Stub::STRING_UTF8; + $stub->cut = $cut; + $stub->value = iconv_substr($v, 0, $maxString, 'UTF-8'); } break; @@ -81,23 +88,33 @@ protected function doClone($var) case 'array': if ($v) { - $stub = (object) array('count' => count($v)); - $arrayRefs[$len] = $stub; + $stub = $arrayRefs[$len] = new Stub(); + $stub->type = Stub::TYPE_ARRAY; + $stub->class = Stub::ARRAY_ASSOC; + $stub->value = count($v); $a = $v; } break; case 'object': if (empty($softRefs[$h = spl_object_hash($v)])) { - $stub = $softRefs[$h] = (object) array('class' => get_class($v)); - if (0 > $maxItems || $pos < $maxItems) { - $a = $this->castObject($stub->class, $v, 0 < $i, $cut); - if ($cut) { - $stub->cut = $cut; - } - } else { - $stub->cut = -1; + $stub = new Stub(); + $stub->type = Stub::TYPE_OBJECT; + $stub->class = get_class($v); + $stub->value = $h; + $a = $this->castObject($v, $stub, 0 < $i); + if (Stub::TYPE_OBJECT !== $stub->type) { + break; } + $h = $stub->value; + $stub->value = ''; + if (0 <= $maxItems && $maxItems <= $pos) { + $stub->cut = count($a); + $a = array(); + } + } + if (empty($softRefs[$h])) { + $softRefs[$h] = $stub; } else { $stub = $softRefs[$h]; $stub->ref = ++$refs; @@ -106,13 +123,24 @@ protected function doClone($var) case 'resource': case 'unknown type': - if (empty($softRefs[$h = (int) substr_replace($v, '', 0, 13)])) { - $stub = $softRefs[$h] = (object) array('res' => @get_resource_type($v)); - if (0 > $maxItems || $pos < $maxItems) { - $a = $this->castResource($stub->res, $v, 0 < $i); - } else { - $stub->cut = -1; + if (empty($softRefs[$h = (int) $v])) { + $stub = new Stub(); + $stub->type = Stub::TYPE_RESOURCE; + $stub->class = get_resource_type($v); + $stub->value = $h; + $a = $this->castResource($v, $stub, 0 < $i); + if (Stub::TYPE_RESOURCE !== $stub->type) { + break; + } + $h = $stub->value; + $stub->value = ''; + if (0 <= $maxItems && $maxItems <= $pos) { + $stub->cut = count($a); + $a = array(); } + } + if (empty($softRefs[$h])) { + $softRefs[$h] = $stub; } else { $stub = $softRefs[$h]; $stub->ref = ++$refs; @@ -122,10 +150,11 @@ protected function doClone($var) if (isset($stub)) { if ($isRef) { - if (isset($stub->count)) { + if (Stub::TYPE_ARRAY === $stub->type) { $step[$k] = $stub; } else { - $step[$k] = (object) array('val' => $stub); + $step[$k] = new Stub(); + $step[$k]->value = $stub; } $h = spl_object_hash($step[$k]); $queue[$i][$k] = $hardRefs[$h] =& $step[$k]; @@ -141,16 +170,12 @@ protected function doClone($var) if ($pos < $maxItems) { if ($maxItems < $pos += $k) { $a = array_slice($a, 0, $maxItems - $pos); - if (empty($stub->cut)) { - $stub->cut = $pos - $maxItems; - } elseif ($stub->cut > 0) { + if ($stub->cut >= 0) { $stub->cut += $pos - $maxItems; } } } else { - if (empty($stub->cut)) { - $stub->cut = $k; - } elseif ($stub->cut > 0) { + if ($stub->cut >= 0) { $stub->cut += $k; } $stub = $a = null; @@ -159,21 +184,22 @@ protected function doClone($var) } } $queue[$len] = $a; - $stub->pos = $len++; + $stub->position = $len++; } $stub = $a = null; } elseif ($isRef) { - $step[$k] = $queue[$i][$k] = $v = (object) array('val' => $v); - $h = spl_object_hash($v); + $step[$k] = $queue[$i][$k] = new Stub(); + $step[$k]->value = $v; + $h = spl_object_hash($step[$k]); $hardRefs[$h] =& $step[$k]; - $values[$h] = $v->val; + $values[$h] = $v; $isRef = false; } } if (isset($arrayRefs[$i])) { if ($indexed) { - $arrayRefs[$i]->indexed = 1; + $arrayRefs[$i]->class = Stub::ARRAY_INDEXED; } unset($arrayRefs[$i]); } diff --git a/src/Symfony/Component/VarDumper/Cloner/Stub.php b/src/Symfony/Component/VarDumper/Cloner/Stub.php new file mode 100644 index 0000000000000..9400a1e077d11 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Cloner/Stub.php @@ -0,0 +1,39 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\VarDumper\Cloner; + +/** + * Represents the main properties of a PHP variable. + * + * @author Nicolas Grekas + */ +class Stub +{ + const TYPE_REF = 'ref'; + const TYPE_STRING = 'string'; + const TYPE_ARRAY = 'array'; + const TYPE_OBJECT = 'object'; + const TYPE_RESOURCE = 'resource'; + + const STRING_BINARY = 'bin'; + const STRING_UTF8 = 'utf8'; + + const ARRAY_ASSOC = 'assoc'; + const ARRAY_INDEXED = 'indexed'; + + public $type = self::TYPE_REF; + public $class = ''; + public $value; + public $cut = 0; + public $ref = 0; + public $position = 0; +} diff --git a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php index 8b2020211e83a..fc4f7ae51b3aa 100644 --- a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php @@ -93,7 +93,7 @@ public function dumpScalar(Cursor $cursor, $type, $val) $style = 'const'; switch ($type) { - case 'int': + case 'integer': $style = 'num'; break; @@ -124,10 +124,6 @@ public function dumpScalar(Cursor $cursor, $type, $val) $this->line .= $this->style($style, $val); - if (false !== $cursor->refTo) { - $this->line .= ' '.$this->style('ref', '&'.$cursor->refTo); - } - $this->endLine($cursor); } @@ -140,14 +136,11 @@ public function dumpString(Cursor $cursor, $str, $bin, $cut) if ('' === $str) { $this->line .= '""'; - if (false !== $cursor->refTo) { - $this->line .= ' '.$this->style('ref', '&'.$cursor->refTo); - } $this->endLine($cursor); } else { $str = explode("\n", $str); $m = count($str) - 1; - $i = 0; + $i = $lineCut = 0; if ($bin) { $this->line .= 'b'; @@ -155,9 +148,6 @@ public function dumpString(Cursor $cursor, $str, $bin, $cut) if ($m) { $this->line .= '"""'; - if (false !== $cursor->refTo) { - $this->line .= $this->style('ref', '&'.$cursor->refTo); - } $this->endLine($cursor); } else { $this->line .= '"'; @@ -165,32 +155,31 @@ public function dumpString(Cursor $cursor, $str, $bin, $cut) foreach ($str as $str) { if (0 < $this->maxStringWidth && $this->maxStringWidth < $len = iconv_strlen($str, 'UTF-8')) { - $str = iconv_substr($str, 0, $this->maxStringWidth - 1, 'UTF-8'); - $str = $this->style('str', $str).'…'; - } else { - $str = $this->style('str', $str); + $str = iconv_substr($str, 0, $this->maxStringWidth, 'UTF-8'); + $lineCut = $len - $this->maxStringWidth; } if ($m) { $this->line .= $this->indentPad; } - $this->line .= $str; + $this->line .= $this->style('str', $str); if ($i++ == $m) { - if ($cut) { - if (0 >= $this->maxStringWidth || $this->maxStringWidth >= $len) { - $this->line .= '…'; - } - $this->line .= $cut; - } elseif ($m) { - $this->line .= '"""'; - } else { - $this->line .= '"'; + $this->line .= '"'; + if ($m) { + $this->line .= '""'; } - if (!$m && false !== $cursor->refTo) { - $this->line .= $this->style('ref', '&'.$cursor->refTo); + if ($cut < 0) { + $this->line .= '…'; + $lineCut = 0; + } elseif ($cut) { + $lineCut += $cut; } } + if ($lineCut) { + $this->line .= '…'.$lineCut; + $lineCut = 0; + } $this->endLine($cursor, !$m); } @@ -257,8 +246,10 @@ protected function enterHash(Cursor $cursor, $prefix, $hasChild) $this->dumpKey($cursor); $this->line .= $prefix; - if (false !== $cursor->refTo) { - $this->line .= $this->style('ref', ($cursor->refIsHard ? '&' : '@').$cursor->refTo); + if (false !== $cursor->softRefTo) { + $this->line .= $this->style('ref', '@'.$cursor->softRefTo); + } elseif (false !== $cursor->hardRefTo) { + $this->line .= $this->style('ref', '@'.$cursor->hardRefTo); } elseif ($hasChild) { $this->endLine($cursor); } @@ -274,7 +265,7 @@ protected function enterHash(Cursor $cursor, $prefix, $hasChild) */ protected function leaveHash(Cursor $cursor, $suffix, $hasChild, $cut) { - if ($cut && false === $cursor->refTo) { + if ($cut && false === $cursor->softRefTo && false === $cursor->hardRefTo) { $this->line .= '…'; if (0 < $cut) { $this->line .= $cut; @@ -336,6 +327,10 @@ protected function dumpKey(Cursor $cursor) } break; } + + if (false !== $cursor->hardRefTo) { + $this->line .= $this->style('ref', '&'.$cursor->hardRefTo).' '; + } } } diff --git a/src/Symfony/Component/VarDumper/Dumper/Cursor.php b/src/Symfony/Component/VarDumper/Dumper/Cursor.php index 6e96e1fd058b3..82aa13532fd22 100644 --- a/src/Symfony/Component/VarDumper/Dumper/Cursor.php +++ b/src/Symfony/Component/VarDumper/Dumper/Cursor.php @@ -11,6 +11,8 @@ namespace Symfony\Component\VarDumper\Dumper; +use Symfony\Component\VarDumper\Cloner\Stub; + /** * Represents the current state of a dumper while dumping. * @@ -18,15 +20,15 @@ */ class Cursor { - const HASH_INDEXED = 'indexed-array'; - const HASH_ASSOC = 'associative-array'; - const HASH_OBJECT = 'object'; - const HASH_RESOURCE = 'resource'; + const HASH_INDEXED = Stub::ARRAY_INDEXED; + const HASH_ASSOC = Stub::ARRAY_ASSOC; + const HASH_OBJECT = Stub::TYPE_OBJECT; + const HASH_RESOURCE = Stub::TYPE_RESOURCE; public $depth = 0; public $refIndex = false; - public $refTo = false; - public $refIsHard = false; + public $softRefTo = false; + public $hardRefTo = false; public $hashType; public $hashKey; public $hashIndex = 0; diff --git a/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php b/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php index 580c63160dba6..1bc32bda7c1ed 100644 --- a/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php +++ b/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php @@ -82,11 +82,11 @@ public function testGet() 0 => {} #3 ] "recurs" => array:1 [ #4 - 0 => array:1 [&4] + 0 => &4 array:1 [@4] ] - 9 => null &1 + 9 => &1 null "sobj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {@2} - "snobj" => {&3} + "snobj" => &3 {@3} "snobj2" => {@3} "file" => "{$var['file']}" b"bin-key-é" => "" diff --git a/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php b/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php index ecc48dbff3f34..c075126c789e4 100644 --- a/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php +++ b/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php @@ -52,7 +52,7 @@ public function testGet()
array:25 [
-  "number" => 1
+  "number" => 1
   0 => null #1
   "const" => 1.1
   1 => true
@@ -60,7 +60,7 @@ public function testGet()
   3 => NAN
   4 => INF
   5 => -INF
-  6 => 9223372036854775807
+  6 => 9223372036854775807
   "str" => "déjà"
   7 => b"é"
   "[]" => []
@@ -68,7 +68,7 @@ public function testGet()
     wrapper_type: "plainfile"
     stream_type: "dir"
     mode: "r"
-    unread_bytes: 0
+    unread_bytes: 0
     seekable: true
     timed_out: false
     blocked: true
@@ -92,16 +92,16 @@ public function testGet()
       }
       """
   }
-  "line" => {$var['line']}
+  "line" => {$var['line']}
   "nobj" => array:1 [
     0 => {} #3
   ]
   "recurs" => array:1 [ #4
-    0 => array:1 [&4]
+    0 => &4 array:1 [@4]
   ]
-  9 => null &1
+  9 => &1 null
   "sobj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {@2}
-  "snobj" => {&3}
+  "snobj" => &3 {@3}
   "snobj2" => {@3}
   "file" => "{$var['file']}"
   b"bin-key-é" => ""

From 081363cbd4cbc18a8e470de2b0b4e879d135a843 Mon Sep 17 00:00:00 2001
From: Nicolas Grekas 
Date: Mon, 8 Sep 2014 20:40:55 +0200
Subject: [PATCH 22/31] [HttpKernel] tests for DumpListener

---
 .../Tests/EventListener/DumpListenerTest.php  | 99 +++++++++++++++++++
 .../VarDumper/Cloner/AbstractCloner.php       |  2 +-
 2 files changed, 100 insertions(+), 1 deletion(-)
 create mode 100644 src/Symfony/Component/HttpKernel/Tests/EventListener/DumpListenerTest.php

diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/DumpListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/DumpListenerTest.php
new file mode 100644
index 0000000000000..11a84b91dac0e
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/DumpListenerTest.php
@@ -0,0 +1,99 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\HttpKernel\Tests\EventListener;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag;
+use Symfony\Component\HttpKernel\EventListener\DumpListener;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\VarDumper\VarDumper;
+
+/**
+ * DumpListenerTest
+ *
+ * @author Nicolas Grekas 
+ */
+class DumpListenerTest extends \PHPUnit_Framework_TestCase
+{
+    public function testSubscribedEvents()
+    {
+        $this->assertSame(
+            array(KernelEvents::REQUEST => array('configure', 1024)),
+            DumpListener::getSubscribedEvents()
+        );
+    }
+
+    public function testConfigure()
+    {
+        $prevDumper = $this->getDumpHandler();
+
+        $container = new ContainerBuilder();
+        $container->setDefinition('var_dumper.cloner', new Definition('Symfony\Component\HttpKernel\Tests\EventListener\MockCloner'));
+        $container->setDefinition('mock_dumper', new Definition('Symfony\Component\HttpKernel\Tests\EventListener\MockDumper'));
+
+        ob_start();
+        $exception = null;
+        $listener = new DumpListener($container, 'mock_dumper');
+
+        try {
+            $listener->configure();
+
+            $lazyDumper = $this->getDumpHandler();
+            VarDumper::dump('foo');
+
+            $loadedDumper = $this->getDumpHandler();
+            VarDumper::dump('bar');
+
+            $this->assertSame('+foo-+bar-', ob_get_clean());
+
+            $listenerReflector = new \ReflectionClass($listener);
+            $lazyReflector = new \ReflectionFunction($lazyDumper);
+            $loadedReflector = new \ReflectionFunction($loadedDumper);
+
+            $this->assertSame($listenerReflector->getFilename(), $lazyReflector->getFilename());
+            $this->assertSame($listenerReflector->getFilename(), $loadedReflector->getFilename());
+            $this->assertGreaterThan($lazyReflector->getStartLine(), $loadedReflector->getStartLine());
+        } catch (\Exception $exception) {
+        }
+
+        VarDumper::setHandler($prevDumper);
+
+        if (null !== $exception) {
+            throw $exception;
+        }
+    }
+
+    private function getDumpHandler()
+    {
+        $prevDumper = VarDumper::setHandler('var_dump');
+        VarDumper::setHandler($prevDumper );
+
+        return $prevDumper;
+    }
+}
+
+class MockCloner
+{
+    public function cloneVar($var)
+    {
+        return $var.'-';
+    }
+}
+
+class MockDumper
+{
+    public function dump($var)
+    {
+        echo '+'.$var;
+    }
+}
diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php
index 7daf58fb25d68..8622311d6f5a1 100644
--- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php
+++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php
@@ -200,7 +200,7 @@ protected function castObject($obj, Stub $stub, $isNested)
         }
 
         if ($classInfo[1]) {
-            $a = $this->callCaster(array($obj, '__debugInfo'), $obj, array(), null, $isNested);
+            $a = $this->callCaster(function ($obj) {return $obj->__debugInfo();}, $obj, array(), null, $isNested);
         } else {
             $a = (array) $obj;
         }

From 49f13c6eab4c67ae3a5599e44a6b1f4d3efdb7c9 Mon Sep 17 00:00:00 2001
From: Nicolas Grekas 
Date: Tue, 9 Sep 2014 10:59:20 +0200
Subject: [PATCH 23/31] [HttpKernel] add tests for DumpDataCollector

---
 .../DataCollector/DumpDataCollector.php       | 70 +++++++++++--------
 .../DataCollector/DumpDataCollectorTest.php   | 69 ++++++++++++++++++
 2 files changed, 111 insertions(+), 28 deletions(-)
 create mode 100644 src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php

diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php
index 95526115c333f..ad836a13a50e9 100644
--- a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php
+++ b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php
@@ -26,20 +26,28 @@
 class DumpDataCollector extends DataCollector implements DataDumperInterface
 {
     private $stopwatch;
+    private $dataCount = 0;
     private $isCollected = true;
-    private $clonesRoot;
     private $clonesCount = 0;
+    private $clonesIndex = 0;
+    private $rootRefs;
 
     public function __construct(Stopwatch $stopwatch = null)
     {
         $this->stopwatch = $stopwatch;
-        $this->clonesRoot = $this;
+
+        // All clones share these properties by reference:
+        $this->rootRefs = array(
+            &$this->data,
+            &$this->dataCount,
+            &$this->isCollected,
+            &$this->clonesCount,
+        );
     }
 
     public function __clone()
     {
-        $this->data = array();
-        $this->clonesRoot->clonesCount++;
+        $this->clonesIndex = ++$this->clonesCount;
     }
 
     public function dump(Data $data)
@@ -47,9 +55,8 @@ public function dump(Data $data)
         if ($this->stopwatch) {
            $this->stopwatch->start('dump');
         }
-        if ($this->clonesRoot->isCollected) {
-            $this->clonesRoot->isCollected = false;
-            register_shutdown_function(array($this->clonesRoot, 'flushDumps'));
+        if ($this->isCollected) {
+            $this->isCollected = false;
         }
 
         $trace = PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS : true;
@@ -66,7 +73,7 @@ public function dump(Data $data)
 
         for ($i = 1; $i < 7; ++$i) {
             if (isset($trace[$i]['class'], $trace[$i]['function'])
-                && 'dump' === $trace[$i]['function']
+                && ('dump' === $trace[$i]['function'] || 'debug' === $trace[$i]['function'])
                 && 'Symfony\Component\VarDumper\VarDumper' === $trace[$i]['class']
             ) {
                 $file = $trace[$i]['file'];
@@ -107,7 +114,8 @@ public function dump(Data $data)
             $name = substr($file, strrpos($file, '/') + 1);
         }
 
-        $this->clonesRoot->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt');
+        $this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt');
+        ++$this->dataCount;
 
         if ($this->stopwatch) {
             $this->stopwatch->stop('dump');
@@ -120,9 +128,14 @@ public function collect(Request $request, Response $response, \Exception $except
 
     public function serialize()
     {
-        $ser = serialize($this->clonesRoot->data);
-        $this->clonesRoot->data = array();
-        $this->clonesRoot->isCollected = true;
+        if ($this->clonesCount !== $this->clonesIndex) {
+            return 'a:0:{}';
+        }
+
+        $ser = serialize($this->data);
+        $this->data = array();
+        $this->dataCount = 0;
+        $this->isCollected = true;
 
         return $ser;
     }
@@ -130,13 +143,13 @@ public function serialize()
     public function unserialize($data)
     {
         parent::unserialize($data);
-
-        $this->clonesRoot = $this;
+        $this->dataCount = count($this->data);
+        self::__construct($this->stopwatch);
     }
 
     public function getDumpsCount()
     {
-        return count($this->clonesRoot->data);
+        return $this->dataCount;
     }
 
     public function getDumpsExcerpts()
@@ -193,7 +206,7 @@ public function getDumps($getData = false)
         }
         $dumps = array();
 
-        foreach ($this->clonesRoot->data as $dump) {
+        foreach ($this->data as $dump) {
             $json = '';
             if ($getData) {
                 $dumper->dump($dump['data'], function ($line) use (&$json) {$json .= $line;});
@@ -210,11 +223,11 @@ public function getName()
         return 'dump';
     }
 
-    public function flushDumps()
+    public function __destruct()
     {
-        if (0 === $this->clonesRoot->clonesCount-- && !$this->clonesRoot->isCollected && $this->clonesRoot->data) {
-            $this->clonesRoot->clonesCount = 0;
-            $this->clonesRoot->isCollected = true;
+        if (0 === $this->clonesCount-- && !$this->isCollected && $this->data) {
+            $this->clonesCount = 0;
+            $this->isCollected = true;
 
             $h = headers_list();
             $i = count($h);
@@ -223,16 +236,16 @@ public function flushDumps()
                 --$i;
             }
 
-            if (stripos($h[$i], 'html')) {
+            if ('cli' !== PHP_SAPI && stripos($h[$i], 'html')) {
                 echo '';
-                $dumper = new HtmlDumper();
+                $dumper = new HtmlDumper('php://output');
             } else {
-                $dumper = new CliDumper();
+                $dumper = new CliDumper('php://output');
                 $dumper->setColors(false);
             }
 
-            foreach ($this->clonesRoot->data as $i => $dump) {
-                $this->clonesRoot->data[$i] = null;
+            foreach ($this->data as $i => $dump) {
+                $this->data[$i] = null;
 
                 if ($dumper instanceof HtmlDumper) {
                     $dump['name'] = htmlspecialchars($dump['name'], ENT_QUOTES, 'UTF-8');
@@ -240,14 +253,15 @@ public function flushDumps()
                     if ('' !== $dump['file']) {
                         $dump['name'] = "{$dump['name']}";
                     }
-                    echo "\n
in {$dump['name']} on line {$dump['line']}:"; + echo "\n{$dump['name']} on line {$dump['line']}:"; } else { - echo "\nin {$dump['name']} on line {$dump['line']}:\n\n"; + echo "{$dump['name']} on line {$dump['line']}:\n"; } $dumper->dump($dump['data']); } - $this->clonesRoot->data = array(); + $this->data = array(); + $this->dataCount = 0; } } } diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php new file mode 100644 index 0000000000000..6dcf74e851b6b --- /dev/null +++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php @@ -0,0 +1,69 @@ + + * + * For the full copyright and license information, please view the LICENSE + * file that was distributed with this source code. + */ + +namespace Symfony\Component\HttpKernel\Tests\DataCollector; + +use Symfony\Component\HttpKernel\DataCollector\DumpDataCollector; +use Symfony\Component\VarDumper\Cloner\Data; + +/** + * DumpDataCollectorTest + * + * @author Nicolas Grekas + */ +class DumpDataCollectorTest extends \PHPUnit_Framework_TestCase +{ + public function testDump() + { + $data = new Data(array(array(123))); + + $collector = new DumpDataCollector(); + + $this->assertSame('dump', $collector->getName()); + + $collector->dump($data); $line = __LINE__; + $this->assertSame(1, $collector->getDumpsCount()); + + $xDump = array( + array( + 'data' => '', + 'name' => 'DumpDataCollectorTest.php', + 'file' => __FILE__, + 'line' => $line, + 'fileExcerpt' => false, + ), + ); + + $this->assertSame($xDump, $collector->getDumps()); + + $xDump[0]['data'] = '123'; + $this->assertSame($xDump, $collector->getDumps(true)); + + $this->assertStringStartsWith( + 'a:1:{i:0;a:5:{s:4:"data";O:39:"Symfony\Component\VarDumper\Cloner\Data":1:{s:45:"Symfony\Component\VarDumper\Cloner\Datadata";a:1:{i:0;a:1:{i:0;i:123;}}}s:4:"name";s:25:"DumpDataCollectorTest.php";', + str_replace("\0", '', $collector->serialize()) + ); + + $this->assertSame(0, $collector->getDumpsCount()); + $this->assertSame('a:0:{}', $collector->serialize()); + } + + public function testFlush() + { + $data = new Data(array(array(456))); + $collector = new DumpDataCollector(); + $collector->dump($data); $line = __LINE__; + + ob_start(); + $collector = null; + $this->assertSame("DumpDataCollectorTest.php on line {$line}:\n456\n", ob_get_clean()); + } +} From de05cd97b9fd8d4f241f27974e95d80d54ccf223 Mon Sep 17 00:00:00 2001 From: Nicolas Grekas Date: Fri, 12 Sep 2014 14:12:12 +0200 Subject: [PATCH 24/31] [DebugBundle] enhance dump excerpts --- .../DataCollector/DumpDataCollector.php | 73 +++++-------------- .../DataCollector/DumpDataCollectorTest.php | 10 +-- .../Component/VarDumper/Cloner/Data.php | 51 +++++++++---- .../Component/VarDumper/Dumper/HtmlDumper.php | 42 ++++++----- .../VarDumper/Tests/HtmlDumperTest.php | 16 +--- 5 files changed, 83 insertions(+), 109 deletions(-) diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php index ad836a13a50e9..3f8f2ef63f1fb 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php @@ -73,7 +73,7 @@ public function dump(Data $data) for ($i = 1; $i < 7; ++$i) { if (isset($trace[$i]['class'], $trace[$i]['function']) - && ('dump' === $trace[$i]['function'] || 'debug' === $trace[$i]['function']) + && 'dump' === $trace[$i]['function'] && 'Symfony\Component\VarDumper\VarDumper' === $trace[$i]['class'] ) { $file = $trace[$i]['file']; @@ -152,66 +152,29 @@ public function getDumpsCount() return $this->dataCount; } - public function getDumpsExcerpts() + public function getDumps($format, $maxDepthLimit = -1, $maxItemsPerDepth = -1) { - $dumps = array(); - - foreach ($this->data as $dump) { - $data = $dump['data']->getRawData(); - unset($dump['data']); - - $data = $data[0][0]; - - if (isset($data->val)) { - $data = $data->val; - } - - if (isset($data->bin)) { - $data = 'b"'.$data->bin.'"'; - } elseif (isset($data->str)) { - $data = '"'.$data->str.'"'; - } elseif (isset($data->count)) { - $data = 'array('.$data->count.')'; - } elseif (isset($data->class)) { - $data = $data->class.'{...}'; - } elseif (isset($data->res)) { - $data = 'resource:'.$data->res.'{...}'; - } elseif (is_array($data)) { - $data = 'array()'; - } elseif (null === $data) { - $data = 'null'; - } elseif (false === $data) { - $data = 'false'; - } elseif (INF === $data) { - $data = 'INF'; - } elseif (-INF === $data) { - $data = '-INF'; - } elseif (NAN === $data) { - $data = 'NAN'; - } elseif (true === $data) { - $data = 'true'; - } - - $dump['dataExcerpt'] = $data; - $dumps[] = $dump; - } - - return $dumps; - } - - public function getDumps($getData = false) - { - if ($getData) { + if ('html' === $format) { + $dumper = new HtmlDumper(); + } elseif ('json' === $format) { $dumper = new JsonDumper(); + } else { + throw new \InvalidArgumentException(sprintf('Invalid dump format: %s', $format)); + } $dumps = array(); foreach ($this->data as $dump) { - $json = ''; - if ($getData) { - $dumper->dump($dump['data'], function ($line) use (&$json) {$json .= $line;}); - } - $dump['data'] = $json; + $data = ''; + $dumper->dump( + $dump['data']->getLimitedClone($maxDepthLimit, $maxItemsPerDepth), + function ($line, $depth) use (&$data) { + if (false !==$depth) { + $data .= str_repeat(' ', $depth).$line."\n"; + } + } + ); + $dump['data'] = $data; $dumps[] = $dump; } diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php index 6dcf74e851b6b..a6d9669f6fc3e 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php @@ -34,21 +34,17 @@ public function testDump() $xDump = array( array( - 'data' => '', + 'data' => "
123\n
\n", 'name' => 'DumpDataCollectorTest.php', 'file' => __FILE__, 'line' => $line, 'fileExcerpt' => false, ), ); - - $this->assertSame($xDump, $collector->getDumps()); - - $xDump[0]['data'] = '123'; - $this->assertSame($xDump, $collector->getDumps(true)); + $this->assertSame($xDump, $collector->getDumps('html')); $this->assertStringStartsWith( - 'a:1:{i:0;a:5:{s:4:"data";O:39:"Symfony\Component\VarDumper\Cloner\Data":1:{s:45:"Symfony\Component\VarDumper\Cloner\Datadata";a:1:{i:0;a:1:{i:0;i:123;}}}s:4:"name";s:25:"DumpDataCollectorTest.php";', + 'a:1:{i:0;a:5:{s:4:"data";O:39:"Symfony\Component\VarDumper\Cloner\Data":3:{s:45:"Symfony\Component\VarDumper\Cloner\Datadata";a:1:{i:0;a:1:{i:0;i:123;}}s:49:"Symfony\Component\VarDumper\Cloner\DatamaxDepth";i:-1;s:57:"Symfony\Component\VarDumper\Cloner\DatamaxItemsPerDepth";i:-1;}s:4:"name";s:25:"DumpDataCollectorTest.php";s:4:"file";s:', str_replace("\0", '', $collector->serialize()) ); diff --git a/src/Symfony/Component/VarDumper/Cloner/Data.php b/src/Symfony/Component/VarDumper/Cloner/Data.php index 0df6997873ab3..ad2a2a2b06153 100644 --- a/src/Symfony/Component/VarDumper/Cloner/Data.php +++ b/src/Symfony/Component/VarDumper/Cloner/Data.php @@ -20,6 +20,8 @@ class Data { private $data; + private $maxDepth = -1; + private $maxItemsPerDepth = -1; /** * @param array $data A array as returned by ClonerInterface::cloneVar(). @@ -29,11 +31,31 @@ public function __construct(array $data) $this->data = $data; } + /** + * @return array The raw data structure. + */ public function getRawData() { return $this->data; } + /** + * Returns a depth limited clone of $this. + * + * @param int $maxDepth The max dumped depth level. + * @param int $maxItemsPerDepth The max number of items dumped per depth level. + * + * @return self A depth limited clone of $this. + */ + public function getLimitedClone($maxDepth, $maxItemsPerDepth) + { + $data = clone $this; + $data->maxDepth = (int) $maxDepth; + $data->maxItemsPerDepth = (int) $maxItemsPerDepth; + + return $data; + } + /** * Dumps data with a DumperInternalsInterface dumper. */ @@ -147,20 +169,23 @@ private function dumpItem($dumper, $cursor, &$refs, $item) private function dumpChildren($dumper, $parentCursor, &$refs, $children, $hashCut, $hashType) { if ($children) { - $cursor = clone $parentCursor; - ++$cursor->depth; - $cursor->hashType = $hashType; - $cursor->hashIndex = 0; - $cursor->hashLength = count($children); - $cursor->hashCut = $hashCut; - foreach ($children as $cursor->hashKey => $child) { - $this->dumpItem($dumper, $cursor, $refs, $child); - ++$cursor->hashIndex; - if ($cursor->stop) { - $parentCursor->stop = true; - - return $hashCut >= 0 ? $hashCut + $children - $cursor->hashIndex : $hashCut; + if ($parentCursor->depth !== $this->maxDepth && $this->maxItemsPerDepth) { + $cursor = clone $parentCursor; + ++$cursor->depth; + $cursor->hashType = $hashType; + $cursor->hashIndex = 0; + $cursor->hashLength = count($children); + $cursor->hashCut = $hashCut; + foreach ($children as $cursor->hashKey => $child) { + $this->dumpItem($dumper, $cursor, $refs, $child); + if (++$cursor->hashIndex === $this->maxItemsPerDepth || $cursor->stop) { + $parentCursor->stop = true; + + return $hashCut >= 0 ? $hashCut + $cursor->hashLength - $cursor->hashIndex : $hashCut; + } } + } elseif ($hashCut >= 0) { + $hashCut += count($children); } } diff --git a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php index e333eb8539554..39e15f95e80d8 100644 --- a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php +++ b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php @@ -21,7 +21,7 @@ class HtmlDumper extends CliDumper public static $defaultOutputStream = 'php://output'; protected $dumpHeader; - protected $dumpPrefix = '
';
+    protected $dumpPrefix = '
';
     protected $dumpSuffix = '
'; protected $colors = true; protected $headerIsDumped = false; @@ -59,7 +59,7 @@ public function setStyles(array $styles) } /** - * Sets an HTML header the 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. */ @@ -83,28 +83,32 @@ public function setDumpBoudaries($prefix, $suffix) /** * Dumps the HTML header. */ - protected function dumpHeader() + protected function getDumpHeader() { $this->headerIsDumped = true; - $line = $this->line; $p = 'sf-dump'; - $this->line = ''; - parent::dumpLine(0); - $this->line .= $this->dumpHeader; - parent::dumpLine(0); - - $this->line = $line; + return preg_replace('/\s+/', ' ', $line).''.$this->dumpHeader; } /** @@ -144,9 +148,6 @@ protected function style($style, $val) */ protected function dumpLine($depth) { - if (!$this->headerIsDumped) { - $this->dumpHeader(); - } switch ($this->lastDepth - $depth) { case +1: $this->line = ''.$this->line; break; @@ -156,6 +157,9 @@ protected function dumpLine($depth) if (-1 === $this->lastDepth) { $this->line = $this->dumpPrefix.$this->line; } + if (!$this->headerIsDumped) { + $this->line = $this->getDumpHeader().$this->line; + } if (false === $depth) { $this->lastDepth = -1; diff --git a/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php b/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php index c075126c789e4..d866aa83f9371 100644 --- a/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php +++ b/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php @@ -37,21 +37,7 @@ public function testGet() $this->assertSame( << - -
array:25 [
+
array:25 [
   "number" => 1
   0 => null #1
   "const" => 1.1

From e4e00ef78ee219246e5949ec4cdbaf45695ba1a8 Mon Sep 17 00:00:00 2001
From: julien Galenski 
Date: Fri, 12 Sep 2014 14:54:21 +0200
Subject: [PATCH 25/31] [TwigBridge] DumpNode and Token parser


From 5f59811c60361f61914f4672fd1faa2f12e04ae7 Mon Sep 17 00:00:00 2001
From: Maxime HERMOUET 
Date: Fri, 12 Sep 2014 14:55:09 +0200
Subject: [PATCH 26/31] [DebugBundle] Add doc example for Twig usage


From a8d81e4fd7c5654a5781afeea00fed0f2cedc891 Mon Sep 17 00:00:00 2001
From: Tony Cosentino 
Date: Fri, 12 Sep 2014 14:55:54 +0200
Subject: [PATCH 27/31] [DebugBundle] Inlined assets to avoid installation
 issues


From d43ae82027456ae0b7460a7b06106df1d87383b9 Mon Sep 17 00:00:00 2001
From: Romain Neutron 
Date: Fri, 12 Sep 2014 14:56:29 +0200
Subject: [PATCH 28/31] [VarDumper] Add workaround to
 https://bugs.php.net/65967


From 0f8d30fd89f98d9426d2b26637b5b61b416bbe11 Mon Sep 17 00:00:00 2001
From: Olivier Scherler 
Date: Fri, 12 Sep 2014 14:57:02 +0200
Subject: [PATCH 29/31] [VarDumper] Replace \e with \x1B in CliDumper to
 support colour in PHP < 5.4


From 2e167ba3517e5a2b0d7e984a975418c733e66e34 Mon Sep 17 00:00:00 2001
From: Nicolas Grekas 
Date: Wed, 17 Sep 2014 10:28:09 +0200
Subject: [PATCH 30/31] [TwigBridge] add Twig dump() function + tests and fixes

---
 composer.json                                 |   5 +-
 .../Bridge/Twig/Extension/DumpExtension.php   |  48 +++++++
 .../Tests/Extension/DumpExtensionTest.php     |  56 +++++++-
 .../TwigBundle/Resources/config/debug.xml     |   5 +-
 .../DataCollector/DumpDataCollector.php       |   2 +-
 .../DataCollector/DumpDataCollectorTest.php   |   7 +-
 .../VarDumper/Caster/ReflectionCaster.php     |   3 +-
 .../VarDumper/Caster/ResourceCaster.php       |   2 +-
 .../VarDumper/Cloner/AbstractCloner.php       | 121 +++++++++---------
 .../VarDumper/{Dumper => Cloner}/Cursor.php   |   4 +-
 .../Component/VarDumper/Cloner/Data.php       |  29 ++---
 .../DumperInterface.php}                      |   4 +-
 .../VarDumper/Dumper/AbstractDumper.php       |  25 ++--
 .../Component/VarDumper/Dumper/CliDumper.php  |   1 +
 .../Component/VarDumper/Dumper/HtmlDumper.php | 113 +++++++++++++---
 src/Symfony/Component/VarDumper/README.md     |   6 +-
 .../VarDumper/Tests/CliDumperTest.php         |   9 +-
 .../VarDumper/Tests/Fixtures/dumb-var.php     |  43 +++++++
 .../VarDumper/Tests/HtmlDumperTest.php        |  39 +++---
 19 files changed, 379 insertions(+), 143 deletions(-)
 rename src/Symfony/Component/VarDumper/{Dumper => Cloner}/Cursor.php (89%)
 rename src/Symfony/Component/VarDumper/{Dumper/DumperInternalsInterface.php => Cloner/DumperInterface.php} (97%)
 create mode 100644 src/Symfony/Component/VarDumper/Tests/Fixtures/dumb-var.php

diff --git a/composer.json b/composer.json
index 97a5dd5b73118..07aae60f9730d 100644
--- a/composer.json
+++ b/composer.json
@@ -85,10 +85,7 @@
             "src/Symfony/Component/HttpFoundation/Resources/stubs",
             "src/Symfony/Component/Intl/Resources/stubs"
         ],
-        "files": [
-            "src/Symfony/Component/Intl/Resources/stubs/functions.php",
-            "src/Symfony/Component/VarDumper/Resources/functions/dump.php"
-        ]
+        "files": [ "src/Symfony/Component/Intl/Resources/stubs/functions.php" ]
     },
     "minimum-stability": "dev",
     "extra": {
diff --git a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php
index a0e895dd87665..2fc02f3302e67 100644
--- a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php
+++ b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php
@@ -12,6 +12,8 @@
 namespace Symfony\Bridge\Twig\Extension;
 
 use Symfony\Bridge\Twig\TokenParser\DumpTokenParser;
+use Symfony\Component\VarDumper\Cloner\ClonerInterface;
+use Symfony\Component\VarDumper\Dumper\HtmlDumper;
 
 /**
  * Provides integration of the dump() function with Twig.
@@ -20,6 +22,18 @@
  */
 class DumpExtension extends \Twig_Extension
 {
+    public function __construct(ClonerInterface $cloner = null)
+    {
+        $this->cloner = $cloner;
+    }
+
+    public function getFunctions()
+    {
+        return array(
+            new \Twig_SimpleFunction('dump', array($this, 'dump'), array('is_safe' => array('html'), 'needs_context' => true, 'needs_environment' => true)),
+        );
+    }
+
     public function getTokenParsers()
     {
         return array(new DumpTokenParser());
@@ -29,4 +43,38 @@ public function getName()
     {
         return 'dump';
     }
+
+    public function dump(\Twig_Environment $env, $context)
+    {
+        if (!$env->isDebug() || !$this->cloner) {
+            return;
+        }
+
+        if (2 === func_num_args()) {
+            $vars = array();
+            foreach ($context as $key => $value) {
+                if (!$value instanceof \Twig_Template) {
+                    $vars[$key] = $value;
+                }
+            }
+
+            $vars = array($vars);
+        } else {
+            $vars = func_get_args();
+            unset($vars[0], $vars[1]);
+        }
+
+        $html = '';
+        $dumper = new HtmlDumper(function ($line, $depth) use (&$html) {
+            if (-1 !== $depth) {
+                $html .= str_repeat('  ', $depth).$line."\n";
+            }
+        });
+
+        foreach ($vars as $value) {
+            $dumper->dump($this->cloner->cloneVar($value));
+        }
+
+        return $html;
+    }
 }
diff --git a/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php b/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php
index 6f2d44aa51eec..3de9a0e91eee7 100644
--- a/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php
+++ b/src/Symfony/Bridge/Twig/Tests/Extension/DumpExtensionTest.php
@@ -13,20 +13,22 @@
 
 use Symfony\Bridge\Twig\Extension\DumpExtension;
 use Symfony\Component\VarDumper\VarDumper;
+use Symfony\Component\VarDumper\Cloner\PhpCloner;
 
 class DumpExtensionTest extends \PHPUnit_Framework_TestCase
 {
     /**
-     * @dataProvider getDumpParams
+     * @dataProvider getDumpTags
      */
-    public function testDebugDump($template, $debug, $expectedOutput, $expectedDumped)
+    public function testDumpTag($template, $debug, $expectedOutput, $expectedDumped)
     {
+        $extension = new DumpExtension(new PhpCloner());
         $twig = new \Twig_Environment(new \Twig_Loader_String(), array(
             'debug' => $debug,
             'cache' => false,
             'optimizations' => 0,
         ));
-        $twig->addExtension(new DumpExtension());
+        $twig->addExtension($extension);
 
         $dumped = null;
         $exception = null;
@@ -46,7 +48,7 @@ public function testDebugDump($template, $debug, $expectedOutput, $expectedDumpe
         $this->assertSame($expectedDumped, $dumped);
     }
 
-    public function getDumpParams()
+    public function getDumpTags()
     {
         return array(
             array('A{% dump %}B', true, 'AB', array()),
@@ -54,4 +56,50 @@ public function getDumpParams()
             array('A{% dump %}B', false, 'AB', null),
         );
     }
+
+    /**
+     * @dataProvider getDumpArgs
+     */
+    public function testDump($context, $args, $expectedOutput, $debug = true)
+    {
+        $extension = new DumpExtension(new PhpCloner());
+        $twig = new \Twig_Environment(new \Twig_Loader_String(), array(
+            'debug' => $debug,
+            'cache' => false,
+            'optimizations' => 0,
+        ));
+
+        array_unshift($args, $context);
+        array_unshift($args, $twig);
+
+        $dump = call_user_func_array(array($extension, 'dump'), $args);
+
+        if ($debug) {
+            $this->assertStringStartsWith('\n"),
+            array(
+                array(),
+                array(123, 456),
+                "
123\n
\n" + ."
456\n
\n", + ), + array( + array('foo' => 'bar'), + array(), + "
array:1 [\n"
+                ."  \"foo\" => \"bar\"\n"
+                ."]\n"
+                ."
\n", + ), + ); + } } diff --git a/src/Symfony/Bundle/TwigBundle/Resources/config/debug.xml b/src/Symfony/Bundle/TwigBundle/Resources/config/debug.xml index 2d12710bb83af..36ddc2a33d82a 100644 --- a/src/Symfony/Bundle/TwigBundle/Resources/config/debug.xml +++ b/src/Symfony/Bundle/TwigBundle/Resources/config/debug.xml @@ -17,12 +17,9 @@ - - - - + diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php index 3f8f2ef63f1fb..add239269c57c 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php @@ -169,7 +169,7 @@ public function getDumps($format, $maxDepthLimit = -1, $maxItemsPerDepth = -1) $dumper->dump( $dump['data']->getLimitedClone($maxDepthLimit, $maxItemsPerDepth), function ($line, $depth) use (&$data) { - if (false !==$depth) { + if (-1 !== $depth) { $data .= str_repeat(' ', $depth).$line."\n"; } } diff --git a/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php index a6d9669f6fc3e..1218ef67e3ace 100644 --- a/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php @@ -34,14 +34,17 @@ public function testDump() $xDump = array( array( - 'data' => "
123\n
\n", + 'data' => "
123\n
\n", 'name' => 'DumpDataCollectorTest.php', 'file' => __FILE__, 'line' => $line, 'fileExcerpt' => false, ), ); - $this->assertSame($xDump, $collector->getDumps('html')); + $dump = $collector->getDumps('html'); + $this->assertTrue(isset($dump[0]['data'])); + $dump[0]['data'] = preg_replace('/^.*?
assertSame($xDump, $dump);
 
         $this->assertStringStartsWith(
             'a:1:{i:0;a:5:{s:4:"data";O:39:"Symfony\Component\VarDumper\Cloner\Data":3:{s:45:"Symfony\Component\VarDumper\Cloner\Datadata";a:1:{i:0;a:1:{i:0;i:123;}}s:49:"Symfony\Component\VarDumper\Cloner\DatamaxDepth";i:-1;s:57:"Symfony\Component\VarDumper\Cloner\DatamaxItemsPerDepth";i:-1;}s:4:"name";s:25:"DumpDataCollectorTest.php";s:4:"file";s:',
diff --git a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php
index a3acb93670bf2..94a25a89e484d 100644
--- a/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php
+++ b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php
@@ -29,8 +29,9 @@ public static function castReflector(\Reflector $c, array $a, Stub $stub, $isNes
 
     public static function castClosure(\Closure $c, array $a, Stub $stub, $isNested)
     {
+        $stub->class = 'Closure'; // HHVM generates unique class names for closures
         $a = static::castReflector(new \ReflectionFunction($c), $a, $stub, $isNested);
-        unset($a["\0+\0000"], $a['name']);
+        unset($a["\0+\0000"], $a['name'], $a["\0+\0this"], $a["\0+\0parameter"]);
 
         return $a;
     }
diff --git a/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php b/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php
index 419280ee4b9fd..903641f69c636 100644
--- a/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php
+++ b/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php
@@ -28,7 +28,7 @@ public static function castCurl($h, array $a, Stub $stub, $isNested)
     public static function castDba($dba, array $a, Stub $stub, $isNested)
     {
         $list = dba_list();
-        $a['file'] = $list[substr((string) $dba, 13)];
+        $a['file'] = $list[(int) $dba];
 
         return $a;
     }
diff --git a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php
index 8622311d6f5a1..ef94af979e94a 100644
--- a/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php
+++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php
@@ -21,64 +21,64 @@
 abstract class AbstractCloner implements ClonerInterface
 {
     public static $defaultCasters = array(
-        'o:Symfony\Component\VarDumper\Caster\CasterStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castStub',
-
-        'o:Closure'        => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castClosure',
-        'o:Reflector'      => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castReflector',
-
-        'o:Doctrine\Common\Persistence\ObjectManager' => 'Symfony\Component\VarDumper\Caster\StubCaster::castNestedFat',
-        'o:Doctrine\Common\Proxy\Proxy'               => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castCommonProxy',
-        'o:Doctrine\ORM\Proxy\Proxy'                  => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castOrmProxy',
-        'o:Doctrine\ORM\PersistentCollection'         => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castPersistentCollection',
-
-        'o:DOMException'             => 'Symfony\Component\VarDumper\Caster\DOMCaster::castException',
-        'o:DOMStringList'            => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
-        'o:DOMNameList'              => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
-        'o:DOMImplementation'        => 'Symfony\Component\VarDumper\Caster\DOMCaster::castImplementation',
-        'o:DOMImplementationList'    => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
-        'o:DOMNode'                  => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNode',
-        'o:DOMNameSpaceNode'         => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNameSpaceNode',
-        'o:DOMDocument'              => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDocument',
-        'o:DOMNodeList'              => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
-        'o:DOMNamedNodeMap'          => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
-        'o:DOMCharacterData'         => 'Symfony\Component\VarDumper\Caster\DOMCaster::castCharacterData',
-        'o:DOMAttr'                  => 'Symfony\Component\VarDumper\Caster\DOMCaster::castAttr',
-        'o:DOMElement'               => 'Symfony\Component\VarDumper\Caster\DOMCaster::castElement',
-        'o:DOMText'                  => 'Symfony\Component\VarDumper\Caster\DOMCaster::castText',
-        'o:DOMTypeinfo'              => 'Symfony\Component\VarDumper\Caster\DOMCaster::castTypeinfo',
-        'o:DOMDomError'              => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDomError',
-        'o:DOMLocator'               => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLocator',
-        'o:DOMDocumentType'          => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDocumentType',
-        'o:DOMNotation'              => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNotation',
-        'o:DOMEntity'                => 'Symfony\Component\VarDumper\Caster\DOMCaster::castEntity',
-        'o:DOMProcessingInstruction' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castProcessingInstruction',
-        'o:DOMXPath'                 => 'Symfony\Component\VarDumper\Caster\DOMCaster::castXPath',
-
-        'o:ErrorException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castErrorException',
-        'o:Exception'      => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castException',
-        'o:Symfony\Component\DependencyInjection\ContainerInterface'
+        'Symfony\Component\VarDumper\Caster\CasterStub' => 'Symfony\Component\VarDumper\Caster\StubCaster::castStub',
+
+        'Closure'        => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castClosure',
+        'Reflector'      => 'Symfony\Component\VarDumper\Caster\ReflectionCaster::castReflector',
+
+        'Doctrine\Common\Persistence\ObjectManager' => 'Symfony\Component\VarDumper\Caster\StubCaster::castNestedFat',
+        'Doctrine\Common\Proxy\Proxy'               => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castCommonProxy',
+        'Doctrine\ORM\Proxy\Proxy'                  => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castOrmProxy',
+        'Doctrine\ORM\PersistentCollection'         => 'Symfony\Component\VarDumper\Caster\DoctrineCaster::castPersistentCollection',
+
+        'DOMException'             => 'Symfony\Component\VarDumper\Caster\DOMCaster::castException',
+        'DOMStringList'            => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
+        'DOMNameList'              => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
+        'DOMImplementation'        => 'Symfony\Component\VarDumper\Caster\DOMCaster::castImplementation',
+        'DOMImplementationList'    => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
+        'DOMNode'                  => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNode',
+        'DOMNameSpaceNode'         => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNameSpaceNode',
+        'DOMDocument'              => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDocument',
+        'DOMNodeList'              => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
+        'DOMNamedNodeMap'          => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLength',
+        'DOMCharacterData'         => 'Symfony\Component\VarDumper\Caster\DOMCaster::castCharacterData',
+        'DOMAttr'                  => 'Symfony\Component\VarDumper\Caster\DOMCaster::castAttr',
+        'DOMElement'               => 'Symfony\Component\VarDumper\Caster\DOMCaster::castElement',
+        'DOMText'                  => 'Symfony\Component\VarDumper\Caster\DOMCaster::castText',
+        'DOMTypeinfo'              => 'Symfony\Component\VarDumper\Caster\DOMCaster::castTypeinfo',
+        'DOMDomError'              => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDomError',
+        'DOMLocator'               => 'Symfony\Component\VarDumper\Caster\DOMCaster::castLocator',
+        'DOMDocumentType'          => 'Symfony\Component\VarDumper\Caster\DOMCaster::castDocumentType',
+        'DOMNotation'              => 'Symfony\Component\VarDumper\Caster\DOMCaster::castNotation',
+        'DOMEntity'                => 'Symfony\Component\VarDumper\Caster\DOMCaster::castEntity',
+        'DOMProcessingInstruction' => 'Symfony\Component\VarDumper\Caster\DOMCaster::castProcessingInstruction',
+        'DOMXPath'                 => 'Symfony\Component\VarDumper\Caster\DOMCaster::castXPath',
+
+        'ErrorException' => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castErrorException',
+        'Exception'      => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castException',
+        'Symfony\Component\DependencyInjection\ContainerInterface'
                            => 'Symfony\Component\VarDumper\Caster\StubCaster::castNestedFat',
-        'o:Symfony\Component\VarDumper\Exception\ThrowingCasterException'
+        'Symfony\Component\VarDumper\Exception\ThrowingCasterException'
                            => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castThrowingCasterException',
 
-        'o:PDO'            => 'Symfony\Component\VarDumper\Caster\PdoCaster::castPdo',
-        'o:PDOStatement'   => 'Symfony\Component\VarDumper\Caster\PdoCaster::castPdoStatement',
-
-        'o:ArrayObject'         => 'Symfony\Component\VarDumper\Caster\SplCaster::castArrayObject',
-        'o:SplDoublyLinkedList' => 'Symfony\Component\VarDumper\Caster\SplCaster::castDoublyLinkedList',
-        'o:SplFixedArray'       => 'Symfony\Component\VarDumper\Caster\SplCaster::castFixedArray',
-        'o:SplHeap'             => 'Symfony\Component\VarDumper\Caster\SplCaster::castHeap',
-        'o:SplObjectStorage'    => 'Symfony\Component\VarDumper\Caster\SplCaster::castObjectStorage',
-        'o:SplPriorityQueue'    => 'Symfony\Component\VarDumper\Caster\SplCaster::castHeap',
-
-        'r:curl'           => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castCurl',
-        'r:dba'            => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba',
-        'r:dba persistent' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castDba',
-        'r:gd'             => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castGd',
-        'r:mysql link'     => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castMysqlLink',
-        'r:process'        => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castProcess',
-        'r:stream'         => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStream',
-        'r:stream-context' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStreamContext',
+        'PDO'            => 'Symfony\Component\VarDumper\Caster\PdoCaster::castPdo',
+        'PDOStatement'   => 'Symfony\Component\VarDumper\Caster\PdoCaster::castPdoStatement',
+
+        'ArrayObject'         => 'Symfony\Component\VarDumper\Caster\SplCaster::castArrayObject',
+        'SplDoublyLinkedList' => 'Symfony\Component\VarDumper\Caster\SplCaster::castDoublyLinkedList',
+        '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',
+
+        ':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',
+        ':process'        => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castProcess',
+        ':stream'         => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStream',
+        ':stream-context' => 'Symfony\Component\VarDumper\Caster\ResourceCaster::castStreamContext',
     );
 
     protected $maxItems = 250;
@@ -107,8 +107,7 @@ public function __construct(array $casters = null)
      *
      * Maps resources or objects types to a callback.
      * Types are in the key, with a callable caster for value.
-     * Objects class are to be prefixed with a `o:`,
-     * resources type are to be prefixed with a `r:`,
+     * Resource types are to be prefixed with a `:`,
      * see e.g. static::$defaultCasters.
      *
      * @param callable[] $casters A map of casters.
@@ -193,7 +192,7 @@ protected function castObject($obj, Stub $stub, $isNested)
                 $class,
                 method_exists($class, '__debugInfo'),
                 new \ReflectionClass($class),
-                array_reverse(array($class => $class) + class_parents($class) + class_implements($class) + array('*' => '*')),
+                array_reverse(array($class => $class) + class_parents($class) + class_implements($class)),
             );
 
             $this->classInfo[$class] = $classInfo;
@@ -213,7 +212,7 @@ protected function castObject($obj, Stub $stub, $isNested)
         }
 
         foreach ($classInfo[3] as $p) {
-            if (!empty($this->casters[$p = 'o:'.strtolower($p)])) {
+            if (!empty($this->casters[$p = strtolower($p)])) {
                 foreach ($this->casters[$p] as $p) {
                     $a = $this->callCaster($p, $obj, $a, $stub, $isNested);
                 }
@@ -237,8 +236,8 @@ protected function castResource($res, Stub $stub, $isNested)
         $a = array();
         $type = $stub->class;
 
-        if (!empty($this->casters['r:'.$type])) {
-            foreach ($this->casters['r:'.$type] as $c) {
+        if (!empty($this->casters[':'.$type])) {
+            foreach ($this->casters[':'.$type] as $c) {
                 $a = $this->callCaster($c, $res, $a, $stub, $isNested);
             }
         }
diff --git a/src/Symfony/Component/VarDumper/Dumper/Cursor.php b/src/Symfony/Component/VarDumper/Cloner/Cursor.php
similarity index 89%
rename from src/Symfony/Component/VarDumper/Dumper/Cursor.php
rename to src/Symfony/Component/VarDumper/Cloner/Cursor.php
index 82aa13532fd22..50266ea52ab3e 100644
--- a/src/Symfony/Component/VarDumper/Dumper/Cursor.php
+++ b/src/Symfony/Component/VarDumper/Cloner/Cursor.php
@@ -9,9 +9,7 @@
  * file that was distributed with this source code.
  */
 
-namespace Symfony\Component\VarDumper\Dumper;
-
-use Symfony\Component\VarDumper\Cloner\Stub;
+namespace Symfony\Component\VarDumper\Cloner;
 
 /**
  * Represents the current state of a dumper while dumping.
diff --git a/src/Symfony/Component/VarDumper/Cloner/Data.php b/src/Symfony/Component/VarDumper/Cloner/Data.php
index ad2a2a2b06153..3394cf796fd5c 100644
--- a/src/Symfony/Component/VarDumper/Cloner/Data.php
+++ b/src/Symfony/Component/VarDumper/Cloner/Data.php
@@ -11,9 +11,6 @@
 
 namespace Symfony\Component\VarDumper\Cloner;
 
-use Symfony\Component\VarDumper\Dumper\DumperInternalsInterface;
-use Symfony\Component\VarDumper\Dumper\Cursor;
-
 /**
  * @author Nicolas Grekas 
  */
@@ -57,21 +54,21 @@ public function getLimitedClone($maxDepth, $maxItemsPerDepth)
     }
 
     /**
-     * Dumps data with a DumperInternalsInterface dumper.
+     * Dumps data with a DumperInterface dumper.
      */
-    public function dump(DumperInternalsInterface $dumper)
+    public function dump(DumperInterface $dumper)
     {
         $refs = array(0);
         $this->dumpItem($dumper, new Cursor, $refs, $this->data[0][0]);
     }
 
     /**
-     * Breadth-first dumping of items.
+     * Depth-first dumping of items.
      *
-     * @param DumperInternalsInterface $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.
+     * @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($dumper, $cursor, &$refs, $item)
     {
@@ -157,12 +154,12 @@ private function dumpItem($dumper, $cursor, &$refs, $item)
     /**
      * Dumps children of hash structures.
      *
-     * @param DumperInternalsInterface $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.
+     * @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.
      */
diff --git a/src/Symfony/Component/VarDumper/Dumper/DumperInternalsInterface.php b/src/Symfony/Component/VarDumper/Cloner/DumperInterface.php
similarity index 97%
rename from src/Symfony/Component/VarDumper/Dumper/DumperInternalsInterface.php
rename to src/Symfony/Component/VarDumper/Cloner/DumperInterface.php
index d60974efdda0b..d910834ddf96b 100644
--- a/src/Symfony/Component/VarDumper/Dumper/DumperInternalsInterface.php
+++ b/src/Symfony/Component/VarDumper/Cloner/DumperInterface.php
@@ -9,14 +9,14 @@
  * file that was distributed with this source code.
  */
 
-namespace Symfony\Component\VarDumper\Dumper;
+namespace Symfony\Component\VarDumper\Cloner;
 
 /**
  * DumperInterface used by Data objects.
  *
  * @author Nicolas Grekas 
  */
-interface DumperInternalsInterface
+interface DumperInterface
 {
     /**
      * Dumps a scalar value.
diff --git a/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php b/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php
index 708dc493ec23f..4b99c1f71ba8c 100644
--- a/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php
+++ b/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php
@@ -12,13 +12,14 @@
 namespace Symfony\Component\VarDumper\Dumper;
 
 use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Cloner\DumperInterface;
 
 /**
  * Abstract mechanism for dumping a Data object.
  *
  * @author Nicolas Grekas 
  */
-abstract class AbstractDumper implements DataDumperInterface, DumperInternalsInterface
+abstract class AbstractDumper implements DataDumperInterface, DumperInterface
 {
     public static $defaultOutputStream = 'php://output';
 
@@ -87,14 +88,22 @@ public function setIndentPad($pad)
      */
     public function dump(Data $data, $lineDumper = null)
     {
-        $this->decimalPoint = (string) 0.5;
-        $this->decimalPoint = $this->decimalPoint[1];
-        $dumper = clone $this;
+        $exception = null;
+        if ($lineDumper) {
+            $prevLineDumper = $this->setLineDumper($lineDumper);
+        }
+        try {
+            $data->dump($this);
+            $this->dumpLine(-1);
+        } catch (\Exception $exception) {
+            // Re-thrown below
+        }
         if ($lineDumper) {
-            $dumper->setLineDumper($lineDumper);
+            $this->setLineDumper($prevLineDumper);
+        }
+        if (null !== $exception) {
+            throw $exception;
         }
-        $data->dump($dumper);
-        $dumper->dumpLine(false);
     }
 
     /**
@@ -116,7 +125,7 @@ protected function dumpLine($depth)
      */
     protected function echoLine($line, $depth)
     {
-        if (false !== $depth) {
+        if (-1 !== $depth) {
             fwrite($this->outputStream, str_repeat($this->indentPad, $depth).$line."\n");
         }
     }
diff --git a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php
index fc4f7ae51b3aa..87680de5984c2 100644
--- a/src/Symfony/Component/VarDumper/Dumper/CliDumper.php
+++ b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php
@@ -12,6 +12,7 @@
 namespace Symfony\Component\VarDumper\Dumper;
 
 use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Cloner\Cursor;
 
 /**
  * CliDumper dumps variables for command line output.
diff --git a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php
index 39e15f95e80d8..6e740c9dcd15b 100644
--- a/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php
+++ b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php
@@ -11,6 +11,8 @@
 
 namespace Symfony\Component\VarDumper\Dumper;
 
+use Symfony\Component\VarDumper\Cloner\Cursor;
+
 /**
  * HtmlDumper dumps variables as HTML.
  *
@@ -21,8 +23,8 @@ class HtmlDumper extends CliDumper
     public static $defaultOutputStream = 'php://output';
 
     protected $dumpHeader;
-    protected $dumpPrefix = '
';
-    protected $dumpSuffix = '
'; + protected $dumpPrefix = '
';
+    protected $dumpSuffix = '
'; protected $colors = true; protected $headerIsDumped = false; protected $lastDepth = -1; @@ -74,7 +76,7 @@ public function setDumpHeader($header) * @param string $prefix The prepended HTML string. * @param string $suffix The appended HTML string. */ - public function setDumpBoudaries($prefix, $suffix) + public function setDumpBoundaries($prefix, $suffix) { $this->dumpPrefix = $prefix; $this->dumpSuffix = $suffix; @@ -87,28 +89,104 @@ protected function getDumpHeader() { $this->headerIsDumped = true; - $p = 'sf-dump'; - $line = <<'.$this->dumpHeader; + } + + /** + * {@inheritdoc} + */ + protected function enterHash(Cursor $cursor, $prefix, $hasChild) + { + if ($hasChild) { + $prefix .= ''; + } + + return parent::enterHash($cursor, $prefix, $hasChild); + } + + /** + * {@inheritdoc} + */ + protected function leaveHash(Cursor $cursor, $suffix, $hasChild, $cut) + { + if ($hasChild) { + $suffix = ''.$suffix; } - return preg_replace('/\s+/', ' ', $line).''.$this->dumpHeader; + return parent::leaveHash($cursor, $suffix, $hasChild, $cut); } /** @@ -138,6 +216,10 @@ protected function style($style, $val) $val = str_replace($c, "$r", $val); } } + } elseif ('note' === $style) { + if (false !== $c = strrpos($val, '\\')) { + $val = sprintf('%s', $val, $style, substr($val, $c+1)); + } } return "$val"; @@ -148,7 +230,6 @@ protected function style($style, $val) */ protected function dumpLine($depth) { - switch ($this->lastDepth - $depth) { case +1: $this->line = ''.$this->line; break; case -1: $this->line = "$this->line"; break; @@ -161,13 +242,11 @@ protected function dumpLine($depth) $this->line = $this->getDumpHeader().$this->line; } - if (false === $depth) { - $this->lastDepth = -1; + if (-1 === $depth) { $this->line .= $this->dumpSuffix; parent::dumpLine(0); - } else { - $this->lastDepth = $depth; } + $this->lastDepth = $depth; parent::dumpLine($depth); } diff --git a/src/Symfony/Component/VarDumper/README.md b/src/Symfony/Component/VarDumper/README.md index b38d82f9d0f6c..3eb3ef24052b8 100644 --- a/src/Symfony/Component/VarDumper/README.md +++ b/src/Symfony/Component/VarDumper/README.md @@ -1,10 +1,10 @@ -Symfony mechanim for exploring and dumping PHP variables -======================================================== +Symfony mechanism for exploring and dumping PHP variables +========================================================= This component provides a mechanism that allows exploring then dumping any PHP variable. -It handles scalar, objects and resources properly, taking hard and soft +It handles scalars, objects and resources properly, taking hard and soft references into account. More than being immune to inifinite recursion problems, it allows dumping where references link to each other. It explores recursive structures using a breadth-first algorithm. diff --git a/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php b/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php index 1bc32bda7c1ed..6a4e9c3f882fe 100644 --- a/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php +++ b/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php @@ -26,6 +26,13 @@ public function testGet() $dumper = new CliDumper('php://output'); $dumper->setColors(false); $cloner = new PhpCloner(); + $cloner->addCasters(array( + ':stream' => function ($res, $a) { + unset($a['uri']); + + return $a; + } + )); $data = $cloner->cloneVar($var); ob_start(); @@ -51,7 +58,7 @@ public function testGet() "[]" => [] "res" => resource:stream { wrapper_type: "plainfile" - stream_type: "dir" + stream_type: "STDIO" mode: "r" unread_bytes: 0 seekable: true diff --git a/src/Symfony/Component/VarDumper/Tests/Fixtures/dumb-var.php b/src/Symfony/Component/VarDumper/Tests/Fixtures/dumb-var.php new file mode 100644 index 0000000000000..6ffc8dd214388 --- /dev/null +++ b/src/Symfony/Component/VarDumper/Tests/Fixtures/dumb-var.php @@ -0,0 +1,43 @@ +bar = 'bar'; + +$g = fopen(__FILE__, 'r'); +$h = fopen(__FILE__, 'r'); +fclose($h); + +$var = array( + 'number' => 1, null, + 'const' => 1.1, true, false, NAN, INF, -INF, PHP_INT_MAX, + 'str' => "déjà", "\xE9", + '[]' => array(), + 'res' => $g, + $h, + '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, $h, $r); diff --git a/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php b/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php index d866aa83f9371..25b027d62e269 100644 --- a/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php +++ b/src/Symfony/Component/VarDumper/Tests/HtmlDumperTest.php @@ -25,7 +25,16 @@ public function testGet() $dumper = new HtmlDumper('php://output'); $dumper->setColors(false); + $dumper->setDumpHeader(''); + $dumper->setDumpBoundaries('', ''); $cloner = new PhpCloner(); + $cloner->addCasters(array( + ':stream' => function ($res, $a) { + unset($a['uri']); + + return $a; + } + )); $data = $cloner->cloneVar($var); ob_start(); @@ -37,7 +46,7 @@ public function testGet() $this->assertSame( <<
array:25 [
+array:25 [
   "number" => 1
   0 => null #1
   "const" => 1.1
@@ -50,9 +59,9 @@ public function testGet()
   "str" => "déjà"
   7 => b"é"
   "[]" => []
-  "res" => resource:stream {
+  "res" => resource:stream {
     wrapper_type: "plainfile"
-    stream_type: "dir"
+    stream_type: "STDIO"
     mode: "r"
     unread_bytes: 0
     seekable: true
@@ -60,13 +69,13 @@ public function testGet()
     blocked: true
     eof: false
     options: []
-  }
+  }
   8 => resource:Unknown {}
-  "obj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo { #2
+  "obj" => DumbFoo { #2
     foo: "foo"
     "bar": "bar"
-  }
-  "closure" => Closure {
+  }
+  "closure" => Closure {
     reflection: """
       Closure [ <user> {$closureLabel} Symfony\Component\VarDumper\Tests\Fixture\{closure} ] {
         @@ {$var['file']} {$var['line']} - {$var['line']}
@@ -77,22 +86,22 @@ public function testGet()
         }
       }
       """
-  }
+  }
   "line" => {$var['line']}
-  "nobj" => array:1 [
+  "nobj" => array:1 [
     0 => {} #3
-  ]
-  "recurs" => array:1 [ #4
+  ]
+  "recurs" => array:1 [ #4
     0 => &4 array:1 [@4]
-  ]
+  ]
   9 => &1 null
-  "sobj" => Symfony\Component\VarDumper\Tests\Fixture\DumbFoo {@2}
+  "sobj" => DumbFoo {@2}
   "snobj" => &3 {@3}
   "snobj2" => {@3}
   "file" => "{$var['file']}"
   b"bin-key-é" => ""
-]
-
+
] + EOTXT , From 80fd7361426707e2980a1575fde1997a200c00c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Gr=C3=A9goire=20Pineau?= Date: Tue, 23 Sep 2014 14:56:30 +0200 Subject: [PATCH 31/31] [DebugBundle] Enhance some comments --- src/Symfony/Bridge/Twig/Node/DumpNode.php | 24 +++++++------------ .../DataCollector/DumpDataCollector.php | 3 +-- .../Tests/EventListener/DumpListenerTest.php | 1 - 3 files changed, 9 insertions(+), 19 deletions(-) diff --git a/src/Symfony/Bridge/Twig/Node/DumpNode.php b/src/Symfony/Bridge/Twig/Node/DumpNode.php index 23ab1125d5c71..654b03aecf62e 100644 --- a/src/Symfony/Bridge/Twig/Node/DumpNode.php +++ b/src/Symfony/Bridge/Twig/Node/DumpNode.php @@ -31,8 +31,7 @@ public function compile(\Twig_Compiler $compiler) { $compiler ->write("if (\$this->env->isDebug()) {\n") - ->indent() - ; + ->indent(); $values = $this->getNode('values'); @@ -50,43 +49,36 @@ public function compile(\Twig_Compiler $compiler) ->outdent() ->write("}\n") ->addDebugInfo($this) - ->write(sprintf('\Symfony\Component\VarDumper\VarDumper::dump($%svars);'."\n", $this->varPrefix)) - ; + ->write(sprintf('\Symfony\Component\VarDumper\VarDumper::dump($%svars);'."\n", $this->varPrefix)); } elseif (1 === $values->count()) { $compiler ->addDebugInfo($this) ->write('\Symfony\Component\VarDumper\VarDumper::dump(') ->subcompile($values->getNode(0)) - ->raw(");\n") - ; + ->raw(");\n"); } else { $compiler ->addDebugInfo($this) ->write('\Symfony\Component\VarDumper\VarDumper::dump(array('."\n") - ->indent() - ; + ->indent(); foreach ($values as $node) { $compiler->addIndentation(); if ($node->hasAttribute('name')) { $compiler ->string($node->getAttribute('name')) - ->raw(' => ') - ; + ->raw(' => '); } $compiler ->subcompile($node) - ->raw(",\n") - ; + ->raw(",\n"); } $compiler ->outdent() - ->write("));\n") - ; + ->write("));\n"); } $compiler ->outdent() - ->raw("}\n") - ; + ->raw("}\n"); } } diff --git a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php index add239269c57c..5b65de4961bc9 100644 --- a/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php +++ b/src/Symfony/Component/HttpKernel/DataCollector/DumpDataCollector.php @@ -53,7 +53,7 @@ public function __clone() public function dump(Data $data) { if ($this->stopwatch) { - $this->stopwatch->start('dump'); + $this->stopwatch->start('dump'); } if ($this->isCollected) { $this->isCollected = false; @@ -160,7 +160,6 @@ public function getDumps($format, $maxDepthLimit = -1, $maxItemsPerDepth = -1) $dumper = new JsonDumper(); } else { throw new \InvalidArgumentException(sprintf('Invalid dump format: %s', $format)); - } $dumps = array(); diff --git a/src/Symfony/Component/HttpKernel/Tests/EventListener/DumpListenerTest.php b/src/Symfony/Component/HttpKernel/Tests/EventListener/DumpListenerTest.php index 11a84b91dac0e..c665df563de60 100644 --- a/src/Symfony/Component/HttpKernel/Tests/EventListener/DumpListenerTest.php +++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/DumpListenerTest.php @@ -13,7 +13,6 @@ use Symfony\Component\DependencyInjection\ContainerBuilder; use Symfony\Component\DependencyInjection\Definition; -use Symfony\Component\DependencyInjection\ParameterBag\ParameterBag; use Symfony\Component\HttpKernel\EventListener\DumpListener; use Symfony\Component\HttpKernel\KernelEvents; use Symfony\Component\VarDumper\VarDumper;