diff --git a/composer.json b/composer.json index fe40170481c7b..07aae60f9730d 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" }, diff --git a/src/Symfony/Bridge/Twig/Extension/DumpExtension.php b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php new file mode 100644 index 0000000000000..2fc02f3302e67 --- /dev/null +++ b/src/Symfony/Bridge/Twig/Extension/DumpExtension.php @@ -0,0 +1,80 @@ + + * + * 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; +use Symfony\Component\VarDumper\Cloner\ClonerInterface; +use Symfony\Component\VarDumper\Dumper\HtmlDumper; + +/** + * Provides integration of the dump() function with Twig. + * + * @author Nicolas Grekas
+ */
+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());
+ }
+
+ 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/Node/DumpNode.php b/src/Symfony/Bridge/Twig/Node/DumpNode.php
new file mode 100644
index 0000000000000..654b03aecf62e
--- /dev/null
+++ b/src/Symfony/Bridge/Twig/Node/DumpNode.php
@@ -0,0 +1,84 @@
+
+ *
+ * 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 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 @@
+
+
+
+ */
+class DumpDataCollector extends DataCollector implements DataDumperInterface
+{
+ private $stopwatch;
+ private $dataCount = 0;
+ private $isCollected = true;
+ private $clonesCount = 0;
+ private $clonesIndex = 0;
+ private $rootRefs;
+
+ public function __construct(Stopwatch $stopwatch = null)
+ {
+ $this->stopwatch = $stopwatch;
+
+ // All clones share these properties by reference:
+ $this->rootRefs = array(
+ &$this->data,
+ &$this->dataCount,
+ &$this->isCollected,
+ &$this->clonesCount,
+ );
+ }
+
+ public function __clone()
+ {
+ $this->clonesIndex = ++$this->clonesCount;
+ }
+
+ public function dump(Data $data)
+ {
+ if ($this->stopwatch) {
+ $this->stopwatch->start('dump');
+ }
+ if ($this->isCollected) {
+ $this->isCollected = false;
+ }
+
+ $trace = PHP_VERSION_ID >= 50306 ? DEBUG_BACKTRACE_PROVIDE_OBJECT | DEBUG_BACKTRACE_IGNORE_ARGS : true;
+ if (PHP_VERSION_ID >= 50400) {
+ $trace = debug_backtrace($trace, 7);
+ } else {
+ $trace = debug_backtrace($trace);
+ }
+
+ $file = $trace[0]['file'];
+ $line = $trace[0]['line'];
+ $name = false;
+ $fileExcerpt = false;
+
+ for ($i = 1; $i < 7; ++$i) {
+ if (isset($trace[$i]['class'], $trace[$i]['function'])
+ && 'dump' === $trace[$i]['function']
+ && 'Symfony\Component\VarDumper\VarDumper' === $trace[$i]['class']
+ ) {
+ $file = $trace[$i]['file'];
+ $line = $trace[$i]['line'];
+
+ 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'];
+ $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[] = '
+ */
+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;
+
+ 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));};
+ VarDumper::setHandler($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/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php b/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php
new file mode 100644
index 0000000000000..1218ef67e3ace
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/Tests/DataCollector/DumpDataCollectorTest.php
@@ -0,0 +1,68 @@
+
+ *
+ * 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' => "
+ */
+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/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
new file mode 100644
index 0000000000000..2a0dd2b9a61e8
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/Caster/DOMCaster.php
@@ -0,0 +1,302 @@
+
+ *
+ * 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 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',
+ );
+
+ 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"] = new CasterStub(self::$errorCodes[$a["\0*\0code"]], 'const');
+ }
+
+ return $a;
+ }
+
+ public static function castLength($dom, array $a, Stub $stub, $isNested)
+ {
+ $a += array(
+ 'length' => $dom->length,
+ );
+
+ return $a;
+ }
+
+ public static function castImplementation($dom, array $a, Stub $stub, $isNested)
+ {
+ $a += array(
+ "\0~\0Core" => '1.0',
+ "\0~\0XML" => '2.0',
+ );
+
+ return $a;
+ }
+
+ public static function castNode(\DOMNode $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += array(
+ 'nodeName' => $dom->nodeName,
+ 'nodeValue' => new CasterStub($dom->nodeValue),
+ 'nodeType' => new CasterStub(self::$nodeTypes[$dom->nodeType], 'const'),
+ 'parentNode' => new CasterStub($dom->parentNode),
+ 'childNodes' => $dom->childNodes,
+ 'firstChild' => new CasterStub($dom->firstChild),
+ 'lastChild' => new CasterStub($dom->lastChild),
+ 'previousSibling' => new CasterStub($dom->previousSibling),
+ 'nextSibling' => new CasterStub($dom->nextSibling),
+ 'attributes' => $dom->attributes,
+ 'ownerDocument' => new CasterStub($dom->ownerDocument),
+ 'namespaceURI' => $dom->namespaceURI,
+ 'prefix' => $dom->prefix,
+ 'localName' => $dom->localName,
+ 'baseURI' => $dom->baseURI,
+ 'textContent' => new CasterStub($dom->textContent),
+ );
+
+ return $a;
+ }
+
+ 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' => new CasterStub($dom->nodeValue),
+ 'nodeType' => new CasterStub(self::$nodeTypes[$dom->nodeType], 'const'),
+ 'prefix' => $dom->prefix,
+ 'localName' => $dom->localName,
+ 'namespaceURI' => $dom->namespaceURI,
+ 'ownerDocument' => new CasterStub($dom->ownerDocument),
+ 'parentNode' => new CasterStub($dom->parentNode),
+ );
+
+ return $a;
+ }
+
+ public static function castDocument(\DOMDocument $dom, array $a, Stub $stub, $isNested)
+ {
+ $formatOutput = $dom->formatOutput;
+ $dom->formatOutput = true;
+
+ $a += array(
+ 'doctype' => $dom->doctype,
+ 'implementation' => $dom->implementation,
+ 'documentElement' => new CasterStub($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, Stub $stub, $isNested)
+ {
+ $a += array(
+ 'data' => $dom->data,
+ 'length' => $dom->length,
+ );
+
+ return $a;
+ }
+
+ public static function castAttr(\DOMAttr $dom, array $a, Stub $stub, $isNested)
+ {
+ $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, Stub $stub, $isNested)
+ {
+ $a += array(
+ 'tagName' => $dom->tagName,
+ 'schemaTypeInfo' => $dom->schemaTypeInfo,
+ );
+
+ return $a;
+ }
+
+ public static function castText(\DOMText $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += array(
+ 'wholeText' => $dom->wholeText,
+ );
+
+ return $a;
+ }
+
+ public static function castTypeinfo(\DOMTypeinfo $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += array(
+ 'typeName' => $dom->typeName,
+ 'typeNamespace' => $dom->typeNamespace,
+ );
+
+ return $a;
+ }
+
+ public static function castDomError(\DOMDomError $dom, array $a, Stub $stub, $isNested)
+ {
+ $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, Stub $stub, $isNested)
+ {
+ $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, Stub $stub, $isNested)
+ {
+ $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, Stub $stub, $isNested)
+ {
+ $a += array(
+ 'publicId' => $dom->publicId,
+ 'systemId' => $dom->systemId,
+ );
+
+ return $a;
+ }
+
+ public static function castEntity(\DOMEntity $dom, array $a, Stub $stub, $isNested)
+ {
+ $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, Stub $stub, $isNested)
+ {
+ $a += array(
+ 'target' => $dom->target,
+ 'data' => $dom->data,
+ );
+
+ return $a;
+ }
+
+ public static function castXPath(\DOMXPath $dom, array $a, Stub $stub, $isNested)
+ {
+ $a += array(
+ 'document' => $dom->document,
+ );
+
+ return $a;
+ }
+}
diff --git a/src/Symfony/Component/VarDumper/Caster/DoctrineCaster.php b/src/Symfony/Component/VarDumper/Caster/DoctrineCaster.php
new file mode 100644
index 0000000000000..30d7ca6ef1a92
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/Caster/DoctrineCaster.php
@@ -0,0 +1,59 @@
+
+ *
+ * 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\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.
+ *
+ * @author Nicolas Grekas
+ */
+class DoctrineCaster
+{
+ public static function castCommonProxy(CommonProxy $proxy, array $a, Stub $stub, $isNested)
+ {
+ unset(
+ $a['__cloner__'],
+ $a['__initializer__']
+ );
+ $stub->cut += 2;
+
+ return $a;
+ }
+
+ public static function castOrmProxy(OrmProxy $proxy, array $a, Stub $stub, $isNested)
+ {
+ $prefix = "\0Doctrine\\ORM\\Proxy\\Proxy\0";
+ unset(
+ $a[$prefix.'_entityPersister'],
+ $a[$prefix.'_identifier']
+ );
+ $stub->cut += 2;
+
+ return $a;
+ }
+
+ public static function castPersistentCollection(PersistentCollection $coll, array $a, Stub $stub, $isNested)
+ {
+ $prefix = "\0Doctrine\\ORM\\PersistentCollection\0";
+
+ $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
new file mode 100644
index 0000000000000..120eae1d2a030
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/Caster/ExceptionCaster.php
@@ -0,0 +1,133 @@
+
+ *
+ * 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;
+use Symfony\Component\VarDumper\Cloner\Stub;
+
+/**
+ * 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, Stub $stub, $isNested)
+ {
+ $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, Stub $stub, $isNested)
+ {
+ if (isset($a[$s = "\0*\0severity"], self::$errorTypes[$a[$s]])) {
+ $a[$s] = new CasterStub(self::$errorTypes[$a[$s]], 'const');
+ }
+
+ return $a;
+ }
+
+ public static function castThrowingCasterException(ThrowingCasterException $e, array $a, Stub $stub, $isNested)
+ {
+ $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, $stub, $isNested);
+ 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/Caster/PdoCaster.php b/src/Symfony/Component/VarDumper/Caster/PdoCaster.php
new file mode 100644
index 0000000000000..90dac4708d5f3
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/Caster/PdoCaster.php
@@ -0,0 +1,114 @@
+
+ *
+ * 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 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, Stub $stub, $isNested)
+ {
+ $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] = new CasterStub($values[$a[$attr]], 'const');
+ }
+ } 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, Stub $stub, $isNested)
+ {
+ $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/Caster/ReflectionCaster.php b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php
new file mode 100644
index 0000000000000..94a25a89e484d
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/Caster/ReflectionCaster.php
@@ -0,0 +1,38 @@
+
+ *
+ * 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 Reflector related classes to array representation.
+ *
+ * @author Nicolas Grekas
+ */
+class ReflectionCaster
+{
+ 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, 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'], $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
new file mode 100644
index 0000000000000..903641f69c636
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/Caster/ResourceCaster.php
@@ -0,0 +1,67 @@
+
+ *
+ * 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 common resource types to array representation.
+ *
+ * @author Nicolas Grekas
+ */
+class ResourceCaster
+{
+ public static function castCurl($h, array $a, Stub $stub, $isNested)
+ {
+ return curl_getinfo($h);
+ }
+
+ public static function castDba($dba, array $a, Stub $stub, $isNested)
+ {
+ $list = dba_list();
+ $a['file'] = $list[(int) $dba];
+
+ return $a;
+ }
+
+ public static function castProcess($process, array $a, Stub $stub, $isNested)
+ {
+ return proc_get_status($process);
+ }
+
+ public static function castStream($stream, array $a, Stub $stub, $isNested)
+ {
+ return stream_get_meta_data($stream) + static::castStreamContext($stream, $a, $stub, $isNested);
+ }
+
+ public static function castStreamContext($stream, array $a, Stub $stub, $isNested)
+ {
+ return stream_context_get_params($stream);
+ }
+
+ public static function castGd($gd, array $a, Stub $stub, $isNested)
+ {
+ $a['size'] = imagesx($gd).'x'.imagesy($gd);
+ $a['trueColor'] = imageistruecolor($gd);
+
+ return $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);
+ $a['server'] = mysql_get_server_info($h);
+
+ return $a;
+ }
+}
diff --git a/src/Symfony/Component/VarDumper/Caster/SplCaster.php b/src/Symfony/Component/VarDumper/Caster/SplCaster.php
new file mode 100644
index 0000000000000..9b5377195ecbb
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/Caster/SplCaster.php
@@ -0,0 +1,110 @@
+
+ *
+ * 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 SPL related classes to array representation.
+ *
+ * @author Nicolas Grekas
+ */
+class SplCaster
+{
+ public static function castArrayObject(\ArrayObject $c, array $a, Stub $stub, $isNested)
+ {
+ $class = $stub->class;
+ $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, Stub $stub, $isNested)
+ {
+ $a += array(
+ "\0~\0heap" => iterator_to_array(clone $c),
+ );
+
+ return $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" => 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);
+
+ return $a;
+ }
+
+ public static function castFixedArray(\SplFixedArray $c, array $a, Stub $stub, $isNested)
+ {
+ $a += array(
+ "\0~\0storage" => $c->toArray(),
+ );
+
+ return $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
+
+ 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/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
new file mode 100644
index 0000000000000..ef94af979e94a
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/Cloner/AbstractCloner.php
@@ -0,0 +1,292 @@
+
+ *
+ * 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
+{
+ public static $defaultCasters = array(
+ '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',
+ 'Symfony\Component\VarDumper\Exception\ThrowingCasterException'
+ => 'Symfony\Component\VarDumper\Caster\ExceptionCaster::castThrowingCasterException',
+
+ '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;
+ 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.
+ * Resource types are to be prefixed with a `:`,
+ * 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.
+ *
+ * @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 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.
+ *
+ * @return array The object casted as array.
+ */
+ 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)),
+ );
+
+ $this->classInfo[$class] = $classInfo;
+ }
+
+ if ($classInfo[1]) {
+ $a = $this->callCaster(function ($obj) {return $obj->__debugInfo();}, $obj, array(), null, $isNested);
+ } else {
+ $a = (array) $obj;
+ }
+
+ foreach ($a as $k => $p) {
+ if (!isset($k[0]) || ("\0" !== $k[0] && !$classInfo[2]->hasProperty($k))) {
+ unset($a[$k]);
+ $a["\0+\0".$k] = $p;
+ }
+ }
+
+ foreach ($classInfo[3] as $p) {
+ if (!empty($this->casters[$p = strtolower($p)])) {
+ foreach ($this->casters[$p] as $p) {
+ $a = $this->callCaster($p, $obj, $a, $stub, $isNested);
+ }
+ }
+ }
+
+ return $a;
+ }
+
+ /**
+ * Casts a resource to an array representation.
+ *
+ * @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($res, Stub $stub, $isNested)
+ {
+ $a = array();
+ $type = $stub->class;
+
+ if (!empty($this->casters[':'.$type])) {
+ foreach ($this->casters[':'.$type] as $c) {
+ $a = $this->callCaster($c, $res, $a, $stub, $isNested);
+ }
+ }
+
+ return $a;
+ }
+
+ /**
+ * Calls a custom caster.
+ *
+ * @param callable $callback The caster.
+ * @param object|resource $obj The object/resource being casted.
+ * @param array $a The result of the previous cast for chained casters.
+ * @param Stub $stub The Stub for the casted object/resource.
+ * @param bool $isNested True if $obj is nested in the dumped structure.
+ *
+ * @return array The casted object/resource.
+ */
+ private function callCaster($callback, $obj, $a, $stub, $isNested)
+ {
+ try {
+ $cast = call_user_func($callback, $obj, $a, $stub, $isNested);
+
+ 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/Cursor.php b/src/Symfony/Component/VarDumper/Cloner/Cursor.php
new file mode 100644
index 0000000000000..50266ea52ab3e
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/Cloner/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\Cloner;
+
+/**
+ * Represents the current state of a dumper while dumping.
+ *
+ * @author Nicolas Grekas
+ */
+class Cursor
+{
+ 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 $softRefTo = false;
+ public $hardRefTo = false;
+ public $hashType;
+ public $hashKey;
+ public $hashIndex = 0;
+ public $hashLength = 0;
+ public $hashCut = 0;
+ public $stop = false;
+}
diff --git a/src/Symfony/Component/VarDumper/Cloner/Data.php b/src/Symfony/Component/VarDumper/Cloner/Data.php
new file mode 100644
index 0000000000000..3394cf796fd5c
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/Cloner/Data.php
@@ -0,0 +1,220 @@
+
+ *
+ * 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;
+ private $maxDepth = -1;
+ private $maxItemsPerDepth = -1;
+
+ /**
+ * @param array $data A array as returned by ClonerInterface::cloneVar().
+ */
+ 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 DumperInterface dumper.
+ */
+ public function dump(DumperInterface $dumper)
+ {
+ $refs = array(0);
+ $this->dumpItem($dumper, new Cursor, $refs, $this->data[0][0]);
+ }
+
+ /**
+ * Depth-first dumping of items.
+ *
+ * @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)
+ {
+ $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];
+ }
+ }
+ $type = $item->class ?: gettype($item->value);
+ $item = $item->value;
+ }
+ if ($item instanceof Stub) {
+ if ($item->ref) {
+ if (isset($refs[$r = $item->ref])) {
+ 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;
+ } else {
+ $cursor->refIndex = $refs[$r] = ++$refs[0];
+ }
+ }
+ $cut = $item->cut;
+
+ if ($item->position && false === $cursor->softRefTo && false === $cursor->hardRefTo) {
+ $children = $this->data[$item->position];
+
+ if ($cursor->stop) {
+ if ($cut >= 0) {
+ $cut += count($children);
+ }
+ $children = array();
+ }
+ } else {
+ $children = array();
+ }
+ 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);
+ $dumper->leaveObject($cursor, $item->class, (bool) $children, $cut);
+ break;
+
+ 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;
+
+ default:
+ throw new \RuntimeException(sprintf('Unexpected Stub type: %s', $item->type));
+ }
+ } elseif ('array' === $type) {
+ $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 DumperInterface $dumper
+ * @param Cursor $parentCursor The cursor of the parent hash.
+ * @param array &$refs A map of all references discovered while dumping.
+ * @param array $children The children to dump.
+ * @param int $hashCut The number of items removed from the original hash.
+ * @param string $hashType A Cursor::HASH_* const.
+ *
+ * @return int The final number of removed items.
+ */
+ private function dumpChildren($dumper, $parentCursor, &$refs, $children, $hashCut, $hashType)
+ {
+ if ($children) {
+ 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);
+ }
+ }
+
+ 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);
+ }
+
+ $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/Cloner/DumperInterface.php b/src/Symfony/Component/VarDumper/Cloner/DumperInterface.php
new file mode 100644
index 0000000000000..d910834ddf96b
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/Cloner/DumperInterface.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\Cloner;
+
+/**
+ * DumperInterface used by Data objects.
+ *
+ * @author Nicolas Grekas
+ */
+interface DumperInterface
+{
+ /**
+ * 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);
+}
diff --git a/src/Symfony/Component/VarDumper/Cloner/ExtCloner.php b/src/Symfony/Component/VarDumper/Cloner/ExtCloner.php
new file mode 100644
index 0000000000000..9aefcc9f4aa3b
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/Cloner/ExtCloner.php
@@ -0,0 +1,195 @@
+
+ *
+ * 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; // 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) {
+ $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)) {
+ $stub = new Stub();
+ $stub->type = Stub::TYPE_STRING;
+ $stub->class = Stub::STRING_BINARY;
+ if (0 <= $maxString && 0 < $cut = strlen($v) - $maxString) {
+ $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 = 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;
+
+ case 'integer':
+ break;
+
+ case 'array':
+ if ($v) {
+ $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 = 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;
+ }
+ break;
+
+ case 'resource':
+ 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;
+ }
+ break;
+ }
+
+ if (isset($stub)) {
+ if ($zval['zval_isref']) {
+ if (Stub::TYPE_ARRAY === $stub->type) {
+ $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = $stub;
+ } else {
+ $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = $v = new Stub();
+ $v->value = $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 ($stub->cut >= 0) {
+ $stub->cut += $pos - $maxItems;
+ }
+ }
+ } else {
+ if ($stub->cut >= 0) {
+ $stub->cut += $k;
+ }
+ $stub = $a = null;
+ unset($arrayRefs[$len]);
+ continue;
+ }
+ }
+ $queue[$len] = $a;
+ $stub->position = $len++;
+ }
+ $stub = $a = null;
+ } elseif ($zval['zval_isref']) {
+ $queue[$i][$k] = $hardRefs[$zval['zval_hash']] = new Stub();
+ $queue[$i][$k]->value = $v;
+ }
+ }
+
+ if (isset($arrayRefs[$i])) {
+ if ($indexed) {
+ $arrayRefs[$i]->class = Stub::ARRAY_INDEXED;
+ }
+ unset($arrayRefs[$i]);
+ }
+ }
+
+ return $queue;
+ }
+}
diff --git a/src/Symfony/Component/VarDumper/Cloner/PhpCloner.php b/src/Symfony/Component/VarDumper/Cloner/PhpCloner.php
new file mode 100644
index 0000000000000..46fa6b0f0cbc3
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/Cloner/PhpCloner.php
@@ -0,0 +1,214 @@
+
+ *
+ * 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; // 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) {
+ $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 Stub && 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)) {
+ $stub = new Stub();
+ $stub->type = Stub::TYPE_STRING;
+ $stub->class = Stub::STRING_BINARY;
+ if (0 <= $maxString && 0 < $cut = strlen($v) - $maxString) {
+ $stub->cut = $cut;
+ $cut = substr_replace($v, '', -$cut);
+ } else {
+ $cut = $v;
+ }
+ $stub->value = Data::utf8Encode($cut);
+ } elseif (0 <= $maxString && isset($v[1+($maxString>>2)]) && 0 < $cut = iconv_strlen($v, 'UTF-8') - $maxString) {
+ $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;
+
+ case 'integer':
+ break;
+
+ case 'array':
+ if ($v) {
+ $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 = 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;
+ }
+ break;
+
+ case 'resource':
+ case 'unknown type':
+ 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;
+ }
+ break;
+ }
+
+ if (isset($stub)) {
+ if ($isRef) {
+ if (Stub::TYPE_ARRAY === $stub->type) {
+ $step[$k] = $stub;
+ } else {
+ $step[$k] = new Stub();
+ $step[$k]->value = $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 ($stub->cut >= 0) {
+ $stub->cut += $pos - $maxItems;
+ }
+ }
+ } else {
+ if ($stub->cut >= 0) {
+ $stub->cut += $k;
+ }
+ $stub = $a = null;
+ unset($arrayRefs[$len]);
+ continue;
+ }
+ }
+ $queue[$len] = $a;
+ $stub->position = $len++;
+ }
+ $stub = $a = null;
+ } elseif ($isRef) {
+ $step[$k] = $queue[$i][$k] = new Stub();
+ $step[$k]->value = $v;
+ $h = spl_object_hash($step[$k]);
+ $hardRefs[$h] =& $step[$k];
+ $values[$h] = $v;
+ $isRef = false;
+ }
+ }
+
+ if (isset($arrayRefs[$i])) {
+ if ($indexed) {
+ $arrayRefs[$i]->class = Stub::ARRAY_INDEXED;
+ }
+ unset($arrayRefs[$i]);
+ }
+ }
+
+ foreach ($values as $h => $v) {
+ $hardRefs[$h] = $v;
+ }
+
+ return $queue;
+ }
+}
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/AbstractDumper.php b/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.php
new file mode 100644
index 0000000000000..4b99c1f71ba8c
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/Dumper/AbstractDumper.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\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, DumperInterface
+{
+ 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)
+ {
+ $exception = null;
+ if ($lineDumper) {
+ $prevLineDumper = $this->setLineDumper($lineDumper);
+ }
+ try {
+ $data->dump($this);
+ $this->dumpLine(-1);
+ } catch (\Exception $exception) {
+ // Re-thrown below
+ }
+ if ($lineDumper) {
+ $this->setLineDumper($prevLineDumper);
+ }
+ if (null !== $exception) {
+ throw $exception;
+ }
+ }
+
+ /**
+ * 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 (-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
new file mode 100644
index 0000000000000..87680de5984c2
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/Dumper/CliDumper.php
@@ -0,0 +1,430 @@
+
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Dumper;
+
+use Symfony\Component\VarDumper\Cloner\Data;
+use Symfony\Component\VarDumper\Cloner\Cursor;
+
+/**
+ * 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 'integer':
+ $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);
+
+ $this->endLine($cursor);
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function dumpString(Cursor $cursor, $str, $bin, $cut)
+ {
+ $this->dumpKey($cursor);
+
+ if ('' === $str) {
+ $this->line .= '""';
+ $this->endLine($cursor);
+ } else {
+ $str = explode("\n", $str);
+ $m = count($str) - 1;
+ $i = $lineCut = 0;
+
+ if ($bin) {
+ $this->line .= 'b';
+ }
+
+ if ($m) {
+ $this->line .= '"""';
+ $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, 'UTF-8');
+ $lineCut = $len - $this->maxStringWidth;
+ }
+
+ if ($m) {
+ $this->line .= $this->indentPad;
+ }
+ $this->line .= $this->style('str', $str);
+
+ if ($i++ == $m) {
+ $this->line .= '"';
+ if ($m) {
+ $this->line .= '""';
+ }
+ if ($cut < 0) {
+ $this->line .= '…';
+ $lineCut = 0;
+ } elseif ($cut) {
+ $lineCut += $cut;
+ }
+ }
+ if ($lineCut) {
+ $this->line .= '…'.$lineCut;
+ $lineCut = 0;
+ }
+
+ $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->softRefTo) {
+ $this->line .= $this->style('ref', '@'.$cursor->softRefTo);
+ } elseif (false !== $cursor->hardRefTo) {
+ $this->line .= $this->style('ref', '@'.$cursor->hardRefTo);
+ } 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->softRefTo && false === $cursor->hardRefTo) {
+ $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;
+ }
+
+ if (false !== $cursor->hardRefTo) {
+ $this->line .= $this->style('ref', '&'.$cursor->hardRefTo).' ';
+ }
+ }
+ }
+
+ /**
+ * 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;
+ }
+}
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/HtmlDumper.php b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php
new file mode 100644
index 0000000000000..6e740c9dcd15b
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/Dumper/HtmlDumper.php
@@ -0,0 +1,253 @@
+
+ *
+ * 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\Cursor;
+
+/**
+ * HtmlDumper dumps variables as HTML.
+ *
+ * @author Nicolas Grekas
+ */
+class HtmlDumper extends CliDumper
+{
+ public static $defaultOutputStream = 'php://output';
+
+ protected $dumpHeader;
+ protected $dumpPrefix = '
+ */
+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);
+ }
+}
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..3eb3ef24052b8
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/README.md
@@ -0,0 +1,14 @@
+Symfony mechanism for exploring and dumping PHP variables
+=========================================================
+
+This component provides a mechanism that allows exploring then dumping
+any PHP variable.
+
+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.
+
+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/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/Tests/CliDumperTest.php b/src/Symfony/Component/VarDumper/Tests/CliDumperTest.php
new file mode 100644
index 0000000000000..6a4e9c3f882fe
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/Tests/CliDumperTest.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\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();
+ $cloner->addCasters(array(
+ ':stream' => function ($res, $a) {
+ unset($a['uri']);
+
+ return $a;
+ }
+ ));
+ $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(
+ <<
+ */
+class HtmlDumperTest extends \PHPUnit_Framework_TestCase
+{
+ public function testGet()
+ {
+ require __DIR__.'/Fixtures/dumb-var.php';
+
+ $dumper = new HtmlDumper('php://output');
+ $dumper->setColors(false);
+ $dumper->setDumpHeader('
+ */
+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, $var);
+ }
+
+ public static function setHandler($callable)
+ {
+ if (null !== $callable && !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
new file mode 100644
index 0000000000000..4a22fc2098aa1
--- /dev/null
+++ b/src/Symfony/Component/VarDumper/composer.json
@@ -0,0 +1,31 @@
+{
+ "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": {
+ "files": [ "Resources/functions/dump.php" ],
+ "psr-0": { "Symfony\\Component\\VarDumper\\": "" }
+ },
+ "target-dir": "Symfony/Component/VarDumper",
+ "minimum-stability": "dev",
+ "extra": {
+ "branch-alias": {
+ "dev-master": "2.6-dev"
+ }
+ }
+}
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 @@
+
+
+123\n\n"
+ ."456\n\n",
+ ),
+ array(
+ array('foo' => 'bar'),
+ array(),
+ "array:1 [\n"
+ ." \"\" => \"bar\"\n"
+ ."]\n"
+ ."\n",
+ ),
+ );
+ }
+}
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/Bridge/Twig/TokenParser/DumpTokenParser.php b/src/Symfony/Bridge/Twig/TokenParser/DumpTokenParser.php
new file mode 100644
index 0000000000000..269ead64f5d40
--- /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
+
+ {% endset %}
+
+ {% set text %}
+
+ {{- "" -}}
+
+ dump()
+
+ {{ collector.dumpsCount }}
+
+
+{% endblock %}
+
+{% block panel %}
+
dump()
+
+
+
+
+ {% for dump in collector.getDumps('html') %}
+
+{% 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 @@
+
+
+'.htmlspecialchars($src[$i - 1]).''.implode("\n", $fileExcerpt).'
';
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ if (false === $name) {
+ $name = strtr($file, '\\', '/');
+ $name = substr($file, strrpos($file, '/') + 1);
+ }
+
+ $this->data[] = compact('data', 'name', 'file', 'line', 'fileExcerpt');
+ ++$this->dataCount;
+
+ if ($this->stopwatch) {
+ $this->stopwatch->stop('dump');
+ }
+ }
+
+ public function collect(Request $request, Response $response, \Exception $exception = null)
+ {
+ }
+
+ public function serialize()
+ {
+ if ($this->clonesCount !== $this->clonesIndex) {
+ return 'a:0:{}';
+ }
+
+ $ser = serialize($this->data);
+ $this->data = array();
+ $this->dataCount = 0;
+ $this->isCollected = true;
+
+ return $ser;
+ }
+
+ public function unserialize($data)
+ {
+ parent::unserialize($data);
+ $this->dataCount = count($this->data);
+ self::__construct($this->stopwatch);
+ }
+
+ public function getDumpsCount()
+ {
+ return $this->dataCount;
+ }
+
+ public function getDumps($format, $maxDepthLimit = -1, $maxItemsPerDepth = -1)
+ {
+ 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) {
+ $data = '';
+ $dumper->dump(
+ $dump['data']->getLimitedClone($maxDepthLimit, $maxItemsPerDepth),
+ function ($line, $depth) use (&$data) {
+ if (-1 !== $depth) {
+ $data .= str_repeat(' ', $depth).$line."\n";
+ }
+ }
+ );
+ $dump['data'] = $data;
+ $dumps[] = $dump;
+ }
+
+ return $dumps;
+ }
+
+ public function getName()
+ {
+ return 'dump';
+ }
+
+ public function __destruct()
+ {
+ if (0 === $this->clonesCount-- && !$this->isCollected && $this->data) {
+ $this->clonesCount = 0;
+ $this->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 ('cli' !== PHP_SAPI && stripos($h[$i], 'html')) {
+ echo '';
+ $dumper = new HtmlDumper('php://output');
+ } else {
+ $dumper = new CliDumper('php://output');
+ $dumper->setColors(false);
+ }
+
+ foreach ($this->data as $i => $dump) {
+ $this->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";
+ } else {
+ echo "{$dump['name']} on line {$dump['line']}:\n";
+ }
+ $dumper->dump($dump['data']);
+ }
+
+ $this->data = array();
+ $this->dataCount = 0;
+ }
+ }
+}
diff --git a/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php b/src/Symfony/Component/HttpKernel/EventListener/DumpListener.php
new file mode 100644
index 0000000000000..46833959b20dc
--- /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\DependencyInjection\ContainerInterface;
+use Symfony\Component\EventDispatcher\EventSubscriberInterface;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\VarDumper\VarDumper;
+
+/**
+ * Configures dump() handler.
+ *
+ * @author Nicolas Grekas 123\n\n",
+ 'name' => 'DumpDataCollectorTest.php',
+ 'file' => __FILE__,
+ 'line' => $line,
+ 'fileExcerpt' => false,
+ ),
+ );
+ $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:',
+ 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());
+ }
+}
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..c665df563de60
--- /dev/null
+++ b/src/Symfony/Component/HttpKernel/Tests/EventListener/DumpListenerTest.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\HttpKernel\Tests\EventListener;
+
+use Symfony\Component\DependencyInjection\ContainerBuilder;
+use Symfony\Component\DependencyInjection\Definition;
+use Symfony\Component\HttpKernel\EventListener\DumpListener;
+use Symfony\Component\HttpKernel\KernelEvents;
+use Symfony\Component\VarDumper\VarDumper;
+
+/**
+ * DumpListenerTest
+ *
+ * @author Nicolas Grekas ';
+ 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 that 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 setDumpBoundaries($prefix, $suffix)
+ {
+ $this->dumpPrefix = $prefix;
+ $this->dumpSuffix = $suffix;
+ }
+
+ /**
+ * Dumps the HTML header.
+ */
+ protected function getDumpHeader()
+ {
+ $this->headerIsDumped = true;
+
+ if (null !== $this->dumpHeader) {
+ return $this->dumpHeader;
+ }
+
+ $line = <<<'EOHTML'
+
+'.$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 parent::leaveHash($cursor, $suffix, $hasChild, $cut);
+ }
+
+ /**
+ * {@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);
+ }
+ }
+ } elseif ('note' === $style) {
+ if (false !== $c = strrpos($val, '\\')) {
+ $val = sprintf('%s', $val, $style, substr($val, $c+1));
+ }
+ }
+
+ return "$val";
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ protected function dumpLine($depth)
+ {
+ 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 (!$this->headerIsDumped) {
+ $this->line = $this->getDumpHeader().$this->line;
+ }
+
+ if (-1 === $depth) {
+ $this->line .= $this->dumpSuffix;
+ parent::dumpLine(0);
+ }
+ $this->lastDepth = $depth;
+
+ parent::dumpLine($depth);
+ }
+}
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