Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

Already on GitHub? Sign in to your account

VarDumper and DebugBundle #10640

Merged
merged 31 commits into from Sep 23, 2014

Conversation

Projects
None yet
Owner

nicolas-grekas commented Apr 5, 2014

Q A
Bug fix? no
New feature? yes
BC breaks? no
Deprecations? no
Tests pass? yes
Fixed tickets none
License MIT
Doc PR symfony/symfony-docs#4243

From a user land point of view, this PR creates a global dump() function that is to be used instead of var_dump(). The component can be used standalone in any dev workflow. Please see the provided README for details.

When used with the Framework bundle, variables passed to dump() are dumped in a new dedicated panel in the web toolbar. The function is also available in twig templates.

Regarding the implementation, I'm pretty sure you'll find a lot to comment. As I'm sure of nothing else, not even the names of things, please do.

I tried to organize this PR in several commits, from the most fundamental algorithm to pure Symfony glue.
I suggest you follow this order while progressing in the review and the discussion around this PR, so that we can together validate commits one after the other.

Don't hesitate to fork the PR and submit PR on it, I'll cherry-pick your patches.

TODO:

  • open a doc PR: symfony/symfony-docs#4243
  • open a PR on the Standard edition: symfony/symfony-standard#710
  • prefix the CSS classes
  • tests for the DebugBundle + other Symfony glue classes
  • inline css and js for compat with e.g. Silex
  • finish and merge nicolas-grekas/Patchwork-Dumper#5 for better UX
  • show a dump excerpt on hovering the icon in the toolbar
  • verify README and comments
  • validate interfaces/names (Caster / Cloner / Dumper)
  • validate new VarDumper component + DebugBundle
  • validate Resource/ext/ vs independent repos.
  • test and define behavior after KernelEvents::RESPONSE
  • update dependencies between components/bundles and composer.json files
  • no hard dep on iconv

Not for this PR but might be worth later:

  • show a light stack trace + timing + memory at debug() calls
  • create a "theme" concept for custom colors/UX
Contributor

Koc commented Apr 5, 2014

Awesome bulk of work was done! Very interesting component. I hope it will be part of Symfony.

@romainneutron romainneutron commented on an outdated diff Apr 5, 2014

src/Symfony/Component/VarDebug/Caster/SplCaster.php
+
+ return $a;
+ }
+
+ public static function castSplFixedArray(\SplFixedArray $c, array $a)
+ {
+ $a = array_merge($a, $c->toArray());
+
+ return $a;
+ }
+
+ public static function castSplObjectStorage(\SplObjectStorage $c, array $a)
+ {
+ foreach ($c as $k => $obj) {
+ $a[$k] = $obj;
+ if (null !== $i = $c->getInfo()) $a["\0~\0$k"] = $i;
@romainneutron

romainneutron Apr 5, 2014

Member

code style : it should be on two lines, using brackets

@pborreli pborreli commented on an outdated diff Apr 5, 2014

src/Symfony/Component/VarDebug/Resources/ext/README.rst
+=======================
+
+This extension adds a ``symfony_zval_info($key, $array, $options = 0)`` function that:
+
+- exposes zval_hash/refcounts, allowing e.g. efficient exploration of arbitrary structures in PHP,
+- does work with references, preventing memory copying.
+
+Its behavior is about the same as:
+
+.. code-block:: php
+
+ <?php
+
+ function symfony_zval_info($key, $array, $options = 0)
+ {
+ // $options is currenlty not used, but could be in future version.
@pborreli

pborreli Apr 5, 2014

Contributor

currenlty -> currently

@pborreli pborreli commented on an outdated diff Apr 5, 2014

...le/WebProfilerBundle/Resources/public/js/var_debug.js
+
+ function htmlizeData(data, tags, title)
+ {
+ var i, e, t, b;
+
+ ++counter;
+ title = title || [];
+ tags = tags || '';
+
+ if (refs[counter]) push('#' + counter, 'ref target');
+ else if (iRefs[counter]) push('r' + iRefs[counter], 'ref handle');
+ else if (iRefs[-counter]) push('R' + iRefs[-counter], 'ref alias');
+
+ switch (true)
+ {
+ case null === data: data = 'null';
@pborreli

pborreli Apr 5, 2014

Contributor

[cs] case(s) should be indented

@stof stof and 2 others commented on an outdated diff Apr 5, 2014

src/Symfony/Bridge/Twig/Extension/VarDebugExtension.php
+ if (2 === $count) {
+ $vars = array();
+ foreach ($context as $key => $value) {
+ if (! $value instanceof \Twig_Template) {
+ $vars[$key] = $value;
+ }
+ }
+ } elseif (3 === $count) {
+ $vars = func_get_arg(2);
+ } else{
+ $vars = array_slice(func_get_args(), 2);
+ }
+
+ debug($vars);
+
+ return '';
@stof

stof Apr 5, 2014

Member

This looks weird to me. The meaning of a Twig function is that it returns some content. If debug() is outputing the debugging data, there is 2 solutions to respect the Twig semantic:

  • make it a Twig tag rather than a Twig function
  • use output buffering to return the output
@nicolas-grekas

nicolas-grekas Apr 6, 2014

Owner

This is the indented behavior: rather than outputting anything, the debug() function fills in a data collector. The data is then displayed in the toolbar. The same occurs when debug() is used in PHP code.

@Koc

Koc Apr 6, 2014

Contributor

So construction like debug($variable); exit; will not output anything?

@nicolas-grekas

nicolas-grekas Apr 6, 2014

Owner

It depends on how you configure the debug handler. In CLI mode, vars are displayed on stderr. In web mode, data is collected for displaying in the toolbar. So I think that yes: debug+die is not expected workflow currently.

@stof

stof Apr 6, 2014

Member

@nicolas-grekas then it should not be a Twig function but a tag, to respect the Twig semantic.

It is perfectly fine to collect the output separately, but it should be done in the Twig way

@Koc

Koc Apr 6, 2014

Contributor

What about two sollutions: override standard Twig's dump function http://twig.sensiolabs.org/doc/functions/dump.html for immediately output as now and tag for collect?

@Koc

Koc Apr 6, 2014

Contributor

Maybe same for native functions: debug and debug_collect.

@stof

stof Apr 6, 2014

Member

@Koc The VarDebug component is not about immediate output. So overwriting the standard dump function would be weird IMO.

and a debug_collect function would still suffer from the same issue. Collecting the debug of the variable is not a function is the Twig semantic, it is a tag. This ends here.

@Koc

Koc Apr 6, 2014

Contributor

You misunderstood me: I'm proposing debug_collect function for the native php (for using in controllers).

@stof stof commented on an outdated diff Apr 5, 2014

...FrameworkBundle/DependencyInjection/Configuration.php
@@ -497,4 +498,26 @@ private function addSerializerSection(ArrayNodeDefinition $rootNode)
->end()
;
}
+
+ private function addVarDebugSection(ArrayNodeDefinition $rootNode)
+ {
+ $rootNode
+ ->children()
+ ->arrayNode('var_debug')
+ ->info('va_debug configuration')
@stof

stof Apr 5, 2014

Member

typo: var_debug

@stof stof commented on an outdated diff Apr 5, 2014

...FrameworkBundle/DependencyInjection/Configuration.php
@@ -497,4 +498,26 @@ private function addSerializerSection(ArrayNodeDefinition $rootNode)
->end()
;
}
+
+ private function addVarDebugSection(ArrayNodeDefinition $rootNode)
+ {
+ $rootNode
+ ->children()
+ ->arrayNode('var_debug')
+ ->info('va_debug configuration')
+ ->children()
+ ->integerNode('max_items')
+ ->info('Max number of displayed items, all levels included, 0 means no limit, -1 only first level')
+ ->min(0)
@stof

stof Apr 5, 2014

Member

the info explains that -1 means only first level, but this is preventing to pass -1. There is a mistake somewhere

@stof stof commented on an outdated diff Apr 5, 2014

...FrameworkBundle/DependencyInjection/Configuration.php
@@ -497,4 +498,26 @@ private function addSerializerSection(ArrayNodeDefinition $rootNode)
->end()
;
}
+
+ private function addVarDebugSection(ArrayNodeDefinition $rootNode)
+ {
+ $rootNode
+ ->children()
+ ->arrayNode('var_debug')
+ ->info('va_debug configuration')
+ ->children()
+ ->integerNode('max_items')
+ ->info('Max number of displayed items, all levels included, 0 means no limit, -1 only first level')
+ ->min(0)
+ ->defaultValue(500)
+ ->end()
+ ->integerNode('max_string')
@stof

stof Apr 5, 2014

Member

shouldn't it be max_string_length instead ?

@stof stof commented on an outdated diff Apr 5, 2014

...workBundle/DependencyInjection/FrameworkExtension.php
@@ -130,6 +130,21 @@ public function load(array $configs, ContainerBuilder $container)
$loader->load('serializer.xml');
}
+ $loader->load('var_debug.xml');
+
+ if (!$container->getParameter('var_debug.collector.class')) {
@stof

stof Apr 5, 2014

Member

this check should be removed, as the parameter is not set yet in the temp container passed to the extension and so the condition will always be true (and if you set the parameter yourself in the main config, it will win over the parameter set in the DI extension anyway)

@stof stof commented on an outdated diff Apr 5, 2014

...workBundle/DependencyInjection/FrameworkExtension.php
@@ -130,6 +130,21 @@ public function load(array $configs, ContainerBuilder $container)
$loader->load('serializer.xml');
}
+ $loader->load('var_debug.xml');
+
+ if (!$container->getParameter('var_debug.collector.class')) {
+ $container->setParameter(
+ 'var_debug.collector.class',
+ 'Symfony\Component\VarDebug\Collector\\'.(extension_loaded('symfony_debug') ? 'Ext' : 'Php').'Collector'
+ );
+ }
+
+ if (isset($config['var_debug'])) {
@stof

stof Apr 5, 2014

Member

you should add a call to addDefaultsIfNotSet() in the configuration class, so that the default values of the configuration tree are used even when you don't put var_debug: ~ in your config

@stof stof commented on an outdated diff Apr 5, 2014

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
@@ -54,6 +54,13 @@ public function boot()
if ($trustedHosts = $this->container->getParameter('kernel.trusted_hosts')) {
Request::setTrustedHosts($trustedHosts);
}
+
+ $container = $this->container;
+
+ set_debug_handler(function ($var) use ($container) {
+ $data = $container->get('var_debug.collector')->collect($var);
+ $container->get('var_debug.dumper.cli')->dump($data);
@stof

stof Apr 5, 2014

Member

shouldn't the CLI dumper be used only in a CLI context ?

@stof stof commented on an outdated diff Apr 5, 2014

...Bundle/FrameworkBundle/Resources/config/var_debug.xml
+<?xml version="1.0" ?>
+
+<container xmlns="http://symfony.com/schema/dic/services"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
+
+ <parameters>
+ <parameter key="var_debug.collector.class"><!-- empty for auto-selection --></parameter>
+ <parameter key="var_debug.dumper.json.class">Symfony\Component\VarDebug\Dumper\JsonDumper</parameter>
+ <parameter key="var_debug.dumper.html.class">Symfony\Component\VarDebug\Dumper\HtmlDumper</parameter>
+ <parameter key="var_debug.dumper.cli.class">Symfony\Component\VarDebug\Dumper\CliDumper</parameter>
+ </parameters>
+
+ <services>
+ <service id="var_debug.collector" class="%var_debug.collector.class%">
+ </service>
@stof

stof Apr 5, 2014

Member

you should use a self-closing tag for consistency with the Symfony code base

@stof stof commented on an outdated diff Apr 5, 2014

...Bundle/FrameworkBundle/Resources/config/var_debug.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0" ?>
+
+<container xmlns="http://symfony.com/schema/dic/services"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
+
+ <parameters>
+ <parameter key="var_debug.collector.class"><!-- empty for auto-selection --></parameter>
@stof

stof Apr 5, 2014

Member

just don't set it there, and set it always in the DI extension

@stof stof commented on an outdated diff Apr 5, 2014

...le/WebProfilerBundle/Resources/public/js/var_debug.js
@@ -0,0 +1,243 @@
+"use strict";
@stof

stof Apr 5, 2014

Member

Please don't put a use strict outside functions, as this will make all JS code running in strict mode (it puts the global scope in strict mode), which can break other JS on the page. It is not sage

@stof stof commented on an outdated diff Apr 5, 2014

...le/WebProfilerBundle/Resources/public/js/var_debug.js
+ var i, e, t, b;
+
+ ++counter;
+ title = title || [];
+ tags = tags || '';
+
+ if (refs[counter]) push('#' + counter, 'ref target');
+ else if (iRefs[counter]) push('r' + iRefs[counter], 'ref handle');
+ else if (iRefs[-counter]) push('R' + iRefs[-counter], 'ref alias');
+
+ switch (true)
+ {
+ case null === data: data = 'null';
+ case true === data:
+ case false === data:
+ default:
@stof

stof Apr 5, 2014

Member

should't default be the last case ?

@stof stof commented on an outdated diff Apr 5, 2014

...rBundle/Resources/views/Collector/var_debug.html.twig
@@ -0,0 +1,65 @@
+{% extends 'WebProfilerBundle:Profiler:layout.html.twig' %}
+
+{% block toolbar %}
+
+ {% set dumps_count = collector.dumps|length %}
+
+ {% set icon %}
+ <img alt="debug()" src="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABwAAAAcCAMAAABF0y+mAAAAt1BMVEUAAAANDQ0AAAAhISESEhIAAABEREQAAAAAAAAQEBAAAAAAAAAFBQUMDAwVFRU2NjYJCQkGBgYNDQ0AAAA+Pj5FRUUuLi4QEBAuLi41NTUICAgFBQUAAAAAAAA6Ojo+Pj4jIyMJCQkEBAQAAAATExMqKipAQEAbGxssLCwAAAA6OjoKCgodHR0fHx89PT0ZGRlKSkpOTk5PT09dXV1SUlJaWlpYWFhGRkZVVVVERERiYmJUVFRISEj7yUYlAAAAOHRSTlMAahiweAX4AgFvCAo4Y4TjUyxmKO773HLO3EQ2JBHv/bRYOw59xPWW0B/5T56o5o7//////////vCDrPIAAAGKSURBVCjPbVPneoMwDBQrQPYgjCxSMpu2qQcz8fs/V22RfDSl+oGFzrKl0xl+mQP7zAUb/jPb2X3eTjunjWJCz7h9dQH+wo4DoE8GEY8GEx1/X9ImrlFxfue8Y7gTjDTYW5Txe3jNrmHJs2jcoHLVCk6NqT7ke326ZbwaPFFH5hWZN90BmNwFWGteNh+rOCbORrn3AdBFUJY79vJoBXadqOU0kJtsBOUCJs0HElFN6dv80JVBC0zJkCXRXpgb6/rWlaAaKEMQzaXFu2KlvxiWXmJbliXvzAPoSs9OvHK4WMKIEpqyeUcZOZgHgp5gKSXiAVKiLA3NMEWPIojHpsfpst/vJ7NT+T1LpLfUjqk8FgsqxLOg8lFQQCpVELbCVCsgW0ld+VWtMGwFSWAikLseoCJBMCShpo9tavrSAOnbMB/pq4mP6UZTxDMkfkNjJP45sphWamRMjayiMY6sGbZPqDiPyOgsKPFx2K8yuRAiCLnUMmkLzCd+I7CWNEkjzbaoRVvUzXOoXp7DD5aJLSEAGSFlAAAAAElFTkSuQmCC" />
+ <span class="sf-toolbar-status{% if dumps_count > 0 %} sf-toolbar-status-yellow{% endif %}">{{ dumps_count }}</span>
@stof

stof Apr 5, 2014

Member

I suggest hiding the toolbar element entirely if there is no dumped data (just like the Swiftmailer and form items are hidden), to limit the amount of useless elements in the toolbar (the toolbar becomes broken when the content cannot fit in a single row anymore)

@stof stof commented on an outdated diff Apr 5, 2014

...ymfony/Bundle/WebProfilerBundle/WebProfilerBundle.php
@@ -20,4 +20,13 @@
*/
class WebProfilerBundle extends Bundle
{
+ public function boot()
+ {
+ $container = $this->container;
+
+ set_debug_handler(function ($var) use ($container) {
+ $data = $container->get('var_debug.collector')->collect($var);
+ $container->get('data_collector.var_debug')->dump($data);
@stof

stof Apr 5, 2014

Member

shoudln't this be handled by the FrameworkBundle ? WebProfilerBundle is not responsible for configuring the profiler and its collectors. This is done in FrameworkBundle. WebProfilerBundle is only the web interface to display the profiler data

@stof stof commented on an outdated diff Apr 5, 2014

src/Symfony/Component/VarDebug/Caster/DoctrineCaster.php
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDebug\Caster;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class DoctrineCaster
+{
+ public static function castCommonProxy(\Doctrine\Common\Proxy\Proxy $p, array $a)
@stof

stof Apr 5, 2014

Member

please add a use statement

@stof stof commented on an outdated diff Apr 5, 2014

src/Symfony/Component/VarDebug/Caster/PdoCaster.php
@@ -0,0 +1,114 @@
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDebug\Caster;
+
+use PDO;
+use PDOStatement;
@stof

stof Apr 5, 2014

Member

The Symfony codebase does not put use statements for the global namespace

@stof stof commented on an outdated diff Apr 5, 2014

...ny/Component/VarDebug/Collector/AbstractCollector.php
+ {
+ $this->prevErrorHandler = set_error_handler(array($this, 'handleError'));
+ try {
+ $data = $this->doCollect($var);
+ } catch (\Exception $e) {
+ restore_error_handler();
+
+ throw $e;
+ }
+ restore_error_handler();
+ $this->prevErrorHandler = null;
+
+ return new Data($data);
+ }
+
+ abstract protected function doCollect($var);
@stof

stof Apr 5, 2014

Member

Please add the phpdoc on the different methods (use {@inheritDoc} for methods documented in the interface)

@stof stof commented on an outdated diff Apr 5, 2014

...ny/Component/VarDebug/Collector/AbstractCollector.php
+ $a = array();
+
+ if (!empty($this->casters['r:'.$type])) {
+ foreach ($this->casters['r:'.$type] as $c) {
+ $a = $this->callCaster($c, $res, $a);
+ }
+ }
+
+ return $a;
+ }
+
+ private function callCaster($callback, $obj, $a)
+ {
+ try {
+ // Ignore invalid $callback
+ $callback = @call_user_func($callback, $obj, $a);
@stof

stof Apr 5, 2014

Member

the naming seems weird. $callback is not the callback anymore after this call. It is the casted data

@stof stof commented on an outdated diff Apr 5, 2014

src/Symfony/Component/VarDebug/Collector/Data.php
+ }
+
+ public function getRawData()
+ {
+ return $this->data;
+ }
+
+ public function dump(DumperInterface $dumper)
+ {
+ $refs = array(0);
+ $dumper->dumpStart();
+ $this->dumpItem($dumper, new Cursor, $refs, $this->data[0][0]);
+ $dumper->dumpEnd();
+ }
+
+ protected function dumpItem($dumper, $cursor, &$refs, $item)
@stof

stof Apr 5, 2014

Member

should be private (we make everything private in Symfony unless we want to provide an extension point, which does not seem to be the case here)

@stof stof and 1 other commented on an outdated diff Apr 5, 2014

src/Symfony/Component/VarDebug/README.md
@@ -0,0 +1,22 @@
+Symfony debug tools for PHP variables
+=====================================
+
+This package provides a better `debug()` function, that you can use instead of
+`var_dump()`, *better* being for:
+
+- 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).
@stof

stof Apr 5, 2014

Member

having a doc folder in the component looks weird to me. This should go in the symfony-docs repo itself

@nicolas-grekas

nicolas-grekas Apr 11, 2014

Owner

This is not an end user doc but a specification that explains why the JsonDumper behaves the way it is

@stof stof commented on an outdated diff Apr 5, 2014

src/Symfony/Component/VarDebug/Resources/ext/config.m4
@@ -0,0 +1,63 @@
+dnl $Id$
+dnl config.m4 for extension symfony_debug
+
+dnl Comments in this file start with the string 'dnl'.
+dnl Remove where necessary. This file will not work
+dnl without editing.
@stof

stof Apr 5, 2014

Member

Shoudln't this file be cleaned from comments coming from the default template to adapt the file to the extension itself ?

@stof stof commented on an outdated diff Apr 5, 2014

src/Symfony/Component/VarDebug/Resources/ext/config.w32
@@ -0,0 +1,13 @@
+// $Id$
+// vim:ft=javascript
@stof

stof Apr 5, 2014

Member

vim setting should be removed IMO (and $Id$ as well)

@stof stof commented on an outdated diff Apr 5, 2014

...fony/Component/VarDebug/Resources/functions/debug.php
+ if (class_exists('Symfony\Component\VarDebug\Dumper\CliDumper')) {
+ $collector = extension_loaded('symfony_debug') ? new ExtCollector : new PhpCollector;
+ $dumper = new CliDumper;
+ $h['handler'] = function ($var) use ($collector, $dumper) {
+ $dumper->dump($collector->collect($var));
+ };
+ } else {
+ $h['handler'] = function ($var) {var_dump($var);};
+ }
+ set_debug_handler($h['handler']);
+ }
+
+ return $h['handler']($var);
+ }
+
+ function set_debug_handler(\Closure $closure)
@stof

stof Apr 5, 2014

Member

couldn't it accept any callable rather than just closures ?

@stof stof commented on an outdated diff Apr 5, 2014

...fony/Component/VarDebug/Resources/functions/debug.php
+
+ if (!isset($reflector)) {
+ $reflector = new ReflectionFunction('set_debug_handler');
+ }
+
+ $h = $reflector->getStaticVariables();
+
+ if (!isset($h['handler'])) {
+ if (class_exists('Symfony\Component\VarDebug\Dumper\CliDumper')) {
+ $collector = extension_loaded('symfony_debug') ? new ExtCollector : new PhpCollector;
+ $dumper = new CliDumper;
+ $h['handler'] = function ($var) use ($collector, $dumper) {
+ $dumper->dump($collector->collect($var));
+ };
+ } else {
+ $h['handler'] = function ($var) {var_dump($var);};
@stof

stof Apr 5, 2014

Member

once any callable is supported, this could become $h['handler'] = 'var_dump'

@stof stof and 1 other commented on an outdated diff Apr 5, 2014

src/Symfony/Component/VarDebug/composer.json
+ "homepage": "http://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.3"
+ },
+ "autoload": {
+ "files": [ "Resources/functions/debug.php" ],
+ "psr-0": { "Symfony\\Component\\VarDebug\\": "" }
+ },
+ "target-dir": "Symfony/Component/ClassLoader"
@stof

stof Apr 5, 2014

Member

I suggest using PSR-4 instead of PSR-0 + target-dir (target-dir is deprecated in composer as it was only a workaround because of the missing PSR-4 spec)

@nicolas-grekas

nicolas-grekas Apr 11, 2014

Owner

PSR-4 is not used currently in the code base. I'd prefer not being the first to make the move, but move together with all the other components.

@stof stof and 1 other commented on an outdated diff Apr 5, 2014

src/Symfony/Component/VarDebug/composer.json
+{
+ "name": "symfony/var-debug",
+ "type": "library",
+ "description": "Symfony debug tools for PHP variables",
+ "keywords": ["dump", "debug"],
+ "homepage": "http://symfony.com",
+ "license": "MIT",
+ "authors": [
+ {
+ "name": "Nicolas Grekas",
+ "email": "p@tchwork.com"
+ }
+ ],
+ "require": {
+ "php": ">=5.3.3"
+ },
@stof

stof Apr 5, 2014

Member

I would add a suggestion on ext-symfony_debug to make people aware of the available extension

Member

stof commented Apr 5, 2014

The root composer.json file needs to be updated:

  • adding the replace clause for the new component
  • handling the loading of the debug() function when using the fullstack repo.

The FrameworkBundle composer.json also needs to be updated to requrie the var-debug component (it is a mandatory dependency).

and the Travis config needs to be updated to run the tests of the C extension as well. It would be easier to run them in the CI if it was a separate repo for the C extension than the PHP code, which would also benefit the installation btw (installing the extension is not done by Composer, and even if it was done in the future, it would not take it from the middle of another package but from PECL). Thus, having the extension in a separate repo will make it easier to handle its versionning (we will be able to have a 1.0 tag for its 1.0 version, which is not the case currently) and will allow to submit it to PECL with its own versionning (having it on PECL mean that Windows users will be able to download the compiled build directly thanks to the PECL infrastructure).
What do you think about split the C extension outside the VarDebug component ? Keeping it in the symfony organization but as a separate repo also mean that the extension can be officially maintained by you (contributors to a C extension are not the same than PHP developers)

Contributor

Koc commented Apr 6, 2014

Should this component respect accepted recently __debugInfo RFC https://wiki.php.net/rfc/debug-info ?

Contributor

damienalexandre commented Apr 6, 2014

Cool stuffs :neckbeard:

Maybe the DoctrineCaster could be improved to be more like the native Doctrine dump function?

They have a great export function doing some transformations on entities Proxy.

Contributor

realityking commented Apr 6, 2014

Great concept but two concerns:

  1. debug() might be a rather common name for a non-namespaced function which might cause conflicts with other libraries.
  2. The PHP implementation would be significantly easier with a class with static methods, simply because there would be no need to use Reflection.

@realityking realityking and 2 others commented on an outdated diff Apr 6, 2014

...fony/Component/VarDebug/Resources/ext/symfony_debug.c
+ case IS_RESOURCE:
+ return "resource";
+
+ default:
+ return "unknown type";
+ }
+}
+
+zend_module_entry symfony_debug_module_entry = {
+ STANDARD_MODULE_HEADER,
+ "symfony_debug",
+ symfony_debug_functions,
+ PHP_MINIT(symfony_debug),
+ PHP_MSHUTDOWN(symfony_debug),
+ PHP_RINIT(symfony_debug),
+ PHP_RSHUTDOWN(symfony_debug),
@realityking

realityking Apr 6, 2014

Contributor

Not a big deal as this will probably rarely be enabled on production systems but these four entries (PHP_MINIT, PHP_MSHUTDOWN, PHP_RINIT, PHP_RSHUTDOWN) should be replaced with null as those functions contain no code. This nets a small performance improvement and is considered a best practice.

@jpauli

jpauli Apr 8, 2014

Contributor

This will be a matter of micro or nano seconds, considering compiler optimizations http://lxr.php.net/xref/PHP_5_5/Zend/zend_API.c#1757

And this extension will never happen to be on production servers :-p

I left those functions so that when I'll need them (in the future, probably), I won't have to rewrite them. They come from the skeleton generator as well.

Member

stof commented Apr 6, 2014

the fatal error in the testsuite is because the autoloading of the var_debug functions is missing in the root composer.json

Owner

nicolas-grekas commented Apr 8, 2014

Some commits added, see above.

If you want to try now, please use: https://github.com/nicolas-grekas/Patchwork-Dumper

I'll keep it in sync with this PR so you can enjoy debug() right now.

Owner

nicolas-grekas commented Apr 8, 2014

Pour les lecteurs français, consultez également la présentation faite hier au Symfony Live Paris :
https://speakerdeck.com/nicolasgrekas/debug-plus-symfony

Member

romainneutron commented Apr 8, 2014

After thinking a bit more about the debug function, rather than a debug function, the behaviour looks like a snapshoter, a screenshoter, a polaroid camera.
We discussed with Nicolas about taking lots of "snapshots" of an object during the lifecycle of a program would naturally lead to data visualisation and diffs.
One of the powerful features of this component is to understand and visualize very quickly what's going on in the code.

Contributor

Taluu commented Apr 8, 2014

Even if this looks promising, I'm not sure that this should be in a new component rather than plain old Debug component (as it is a tool to debug things)...

@stof stof added the Feature label Apr 9, 2014

Owner

nicolas-grekas commented Apr 10, 2014

Update:

  • @Koc commit nicolas-grekas/symfony@92b1502 is for you,
  • I also did a first round of fixes for composer/components/bundles dependencies,
  • Many thanks to @ruian who implemented a Twig tag, so you can use {% debug var %} instead of {{ debug(var) }}. For the moment, I've kept both syntax enabled, to gather more feedback before final decision,
  • https://github.com/nicolas-grekas/Patchwork-Dumper is up to date with these updates so you can test right now.

Positive and/or negative feedback welcomed!

@stloyd stloyd and 1 other commented on an outdated diff Apr 10, 2014

...nt/HttpKernel/DataCollector/VarDebugDataCollector.php
+ $excerpt = array();
+
+ for ($i = max($line - 3, 1), $max = min($line + 3, count($src)); $i <= $max; ++$i) {
+ $excerpt[] = '<li'.($i === $line ? ' class="selected"' : '').'><code>'.htmlspecialchars($src[$i - 1]).'</code></li>';
+ }
+
+ $excerpt = '<ol start="'.max($line - 3, 1).'">'.implode("\n", $excerpt).'</ol>';
+ }
+ break;
+ }
+ }
+ break;
+ }
+ }
+
+ $name or $name = 0 === strpos($file, $this->rootDir) ? substr($file, strlen($this->rootDir)) : $file;
@stloyd

stloyd Apr 10, 2014

Contributor

or => ||.

@stloyd

stloyd Apr 10, 2014

Contributor

In overall this line looks strange and unreadeable.

@stof

stof Apr 10, 2014

Member

@stloyd the change or to || would be wrong. This code here is using the control flow operator, not the boolean operator. Doing the change will even break the logic because of the different precedence of or and ||.
However, I agree that using a if would be more readable than using the control flow operator here.

@stloyd stloyd and 2 others commented on an outdated diff Apr 10, 2014

...nt/HttpKernel/DataCollector/VarDebugDataCollector.php
+ public function serialize()
+ {
+ $this->isCollected = true;
+
+ return parent::serialize();
+ }
+
+ public function __destruct()
+ {
+ if (!$this->isCollected) {
+ $this->isCollected = true;
+
+ $h = headers_list();
+ array_unshift($h, 'Content-Type: ' . ini_get('default_mimetype'));
+ $i = count($h);
+ while (0 !== stripos($h[--$i], 'Content-Type:')) {
@stloyd

stloyd Apr 10, 2014

Contributor

This loop does nothing...

@romainneutron

romainneutron Apr 10, 2014

Member

it decreases $i value

@stof

stof Apr 10, 2014

Member

I suggest adding a comment to make it clearer

@Koc Koc commented on an outdated diff Apr 10, 2014

...fony/Component/VarDebug/Resources/functions/debug.php
+ /**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+ function debug($var)
+ {
+ static $reflector;
+
+ if (!isset($reflector)) {
+ $reflector = new ReflectionFunction('set_debug_handler');
+ }
+
+ $h = $reflector->getStaticVariables();
+
+ if (!isset($h['handler'])) {
+ if (class_exists('Symfony\Component\VarDebug\Dumper\CliDumper')) {
+ $collector = extension_loaded('symfony_debug') ? new ExtCollector : new PhpCollector;
@Koc

Koc Apr 10, 2014

Contributor

Symfony usually uses brackets for classes instanciation without args.

Contributor

Koc commented Apr 10, 2014

@nicolas-grekas nice, thanx. But it looks too complicated comparing to add just another function for inline dumping.

@pborreli pborreli commented on an outdated diff Apr 10, 2014

...nt/HttpKernel/DataCollector/VarDebugDataCollector.php
+class VarDebugDataCollector extends DataCollector
+{
+ private $rootDir;
+ private $stopwatch;
+ private $isCollected = true;
+
+ public function __construct($rootDir, Stopwatch $stopwatch = null)
+ {
+ $this->rootDir = dirname($rootDir).DIRECTORY_SEPARATOR;
+ $this->stopwatch = $stopwatch;
+ $this->data['dumps'] = array();
+ }
+
+ public function dump(Data $data)
+ {
+ $this->stopwatch and $this->stopwatch->start('debug');
@pborreli

pborreli Apr 10, 2014

Contributor

and is never used in Symfony codebase, for readability, I would recommend a real test instead of this one line shortcut

@tgabi333 tgabi333 commented on an outdated diff Apr 10, 2014

...ny/Component/VarDebug/Collector/AbstractCollector.php
+ 'r:gd' => 'Symfony\Component\VarDebug\Caster\BaseCaster::castGd',
+ 'r:mysql link' => 'Symfony\Component\VarDebug\Caster\BaseCaster::castMysqlLink',
+ 'r:process' => 'Symfony\Component\VarDebug\Caster\BaseCaster::castProcess',
+ 'r:stream' => 'Symfony\Component\VarDebug\Caster\BaseCaster::castStream',
+ );
+
+ protected $maxItems = 1000;
+ protected $maxString = 10000;
+
+ private $casters = array();
+ private $data = array(array(null));
+ private $prevErrorHandler = null;
+
+ public function __construct(array $defaultCasters = null)
+ {
+ isset($defaultCasters) or $defaultCasters = static::$defaultCasters;
@tgabi333

tgabi333 Apr 10, 2014

Contributor

For better readability and CS, please use an if statement here.

@stof stof and 2 others commented on an outdated diff Apr 10, 2014

src/Symfony/Bridge/Twig/Node/DebugNode.php
+ }
+
+ /**
+ * {@inheritdoc}
+ */
+ public function compile(\Twig_Compiler $compiler)
+ {
+ if (!$this->env->isDebug()) {
+ return;
+ }
+
+ $compiler->addDebugInfo($this);
+ $compiler->write('debug(');
+ if (!$values = $this->getNode('values')) {
+ $compiler->raw('$context');
+ } elseif ($values->count() === 1) {
@stof

stof Apr 10, 2014

Member

$values is undefined here (which makes me think that this code is currently not tested)

@nicolas-grekas

nicolas-grekas Apr 10, 2014

Owner

$values is defined line 38

@pborreli

pborreli Apr 10, 2014

Contributor

line 38 is an assignation in condition

@stof

stof Apr 10, 2014

Member

hmm sorry, missed it. I suggest moving the assignment outside the condition for readability

@stof stof commented on an outdated diff Apr 10, 2014

src/Symfony/Bridge/Twig/Node/DebugNode.php
+ {
+ if (!$this->env->isDebug()) {
+ return;
+ }
+
+ $compiler->addDebugInfo($this);
+ $compiler->write('debug(');
+ if (!$values = $this->getNode('values')) {
+ $compiler->raw('$context');
+ } elseif ($values->count() === 1) {
+ $compiler->subcompile($values->getNode(0));
+ } else {
+ $compiler->raw('array(');
+ foreach ($values as $node) {
+ if ($node->hasAttribute('name')) {
+ $compiler->raw("'".addslashes($node->getAttribute('name'))."' => ");
@stof

stof Apr 10, 2014

Member

you should use $compiler->string() instead of duplicating its logic

@stof stof commented on an outdated diff Apr 10, 2014

src/Symfony/Bundle/FrameworkBundle/FrameworkBundle.php
@@ -54,6 +54,16 @@ public function boot()
if ($trustedHosts = $this->container->getParameter('kernel.trusted_hosts')) {
Request::setTrustedHosts($trustedHosts);
}
+
+ if (function_exists('set_debug_handler')) {
+ $container = $this->container;
+ $dumperService = 'cli' === PHP_SAPI ? 'var_debug.dumper.cli' : 'data_collector.var_debug';
+
+ set_debug_handler(function ($var) use ($container, $dumperService) {
+ $data = $container->get('var_debug.collector')->collect($var);
+ $container->get($dumperService)->dump($data);
+ });
+ }
@stof

stof Apr 10, 2014

Member

you also need to reset the debug handler on shutdown so that it does not keep a reference to the container preventing the garbage collection

@stof stof commented on an outdated diff Apr 10, 2014

src/Symfony/Component/VarDebug/Caster/BaseCaster.php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDebug\Caster;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class BaseCaster
@stof

stof Apr 10, 2014

Member

BaseCaster seems to imply that other casters will extend from this class, which is not the case. A better name should be found. ResourceCaster is not good as it does not only deal with resources but also with closures and reflectors.
If someone has a good idea for a name, please comment with your suggestions

@stof stof and 2 others commented on an outdated diff Apr 10, 2014

src/Symfony/Component/VarDebug/Caster/DoctrineCaster.php
+namespace Symfony\Component\VarDebug\Caster;
+
+use Doctrine\Common\Proxy\Proxy as CommonProxy;
+use Doctrine\ORM\Proxy\Proxy as OrmProxy;
+
+/**
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class DoctrineCaster
+{
+ public static function castCommonProxy(CommonProxy $p, array $a)
+ {
+ unset(
+ $a['__cloner__'],
+ $a['__initializer__'],
+ $a['__isInitialized__']
@stof

stof Apr 10, 2014

Member

I'm not sure we should remove this __isInitialized__ property entirely. It can make sense to know whether a proxy is initialized or no when debugging (for instance, some bugs may appear only before the initialization of the proxy, we already faced it)

@Koc

Koc Apr 10, 2014

Contributor

+1. Looks like it doesn't handle collections properly like in original dumper https://github.com/doctrine/common/blob/master/lib/Doctrine/Common/Util/Debug.php#L98

@nicolas-grekas

nicolas-grekas Apr 10, 2014

Owner

It does handle collections properly: the internal array will just be dumped as a private property of the ArrayCollection, which exactly represents the internal state of the collection. More over, if the collection is lazy, it won't trigger loading of the data, which is exactly what is required for debugging (no state change).

@stof

stof Apr 10, 2014

Member

for collections, the elements will appear (as the collection has the elements inside it), but it will also show you the other internal elements (useful to debug lazy collections for instance).
Thus, compared to the Doctrine dumper, it will not initialize lazy collection as a side effect of debugging them.

Note that the code available on Moday was including a caster for Doctrine Collection, but @nicolas-grekas and I decided to remove it when discussing about it during the hackday yesterday because of the reason above.

@stof stof and 1 other commented on an outdated diff Apr 10, 2014

...Symfony/Component/VarDebug/Caster/ExceptionCaster.php
+ E_USER_NOTICE => 'E_USER_NOTICE',
+ E_STRICT => 'E_STRICT',
+ );
+
+ public static function castException(\Exception $e, array $a)
+ {
+ $trace = $a["\0Exception\0trace"];
+ unset($a["\0Exception\0trace"]); // Ensures the trace is always last
+
+ static::filterTrace($trace, static::$traceArgs);
+
+ if (isset($trace)) {
+ $a["\0Exception\0trace"] = $trace;
+ }
+ if (empty($a["\0Exception\0previous"])) {
+ unset($a["\0Exception\0previous"]);
@stof

stof Apr 10, 2014

Member

why removing the previous exception ?

@nicolas-grekas

nicolas-grekas Apr 10, 2014

Owner

Useless noise if it's empty

@stof

stof Apr 10, 2014

Member

hmm, I read it too fast. this is indeed fine

@stof stof commented on an outdated diff Apr 10, 2014

...Symfony/Component/VarDebug/Caster/ExceptionCaster.php
+
+ empty($a["\0*\0message"]) and $a["\0*\0message"] = "Unexpected exception thrown from a caster: ".get_class($a["\0Exception\0previous"]);
+
+ isset($b["\0*\0message"]) and $a["\0~\0message"] = $b["\0*\0message"];
+ isset($b["\0*\0file"]) and $a["\0~\0file"] = $b["\0*\0file"];
+ isset($b["\0*\0line"]) and $a["\0~\0line"] = $b["\0*\0line"];
+ isset($b["\0Exception\0trace"]) and $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;
@stof

stof Apr 10, 2014

Member

please always use curly braces for control structures

@stof stof commented on an outdated diff Apr 10, 2014

src/Symfony/Component/VarDebug/Dumper/CliDumper.php
+ case '--color=never':
+ return false;
+ }
+ }
+ }
+ }
+
+ if (null !== static::$defaultColors) {
+ return static::$defaultColors;
+ }
+
+ if (empty($this->outputStream)) {
+ return false;
+ }
+
+ $colors = DIRECTORY_SEPARATOR === '\\'
@stof

stof Apr 10, 2014

Member

we generally use defined('PHP_WINDOWS_VERSION_MAJOR') (or one of the other windows-specific version constants) to detect windows

@stof stof commented on an outdated diff Apr 10, 2014

src/Symfony/Component/VarDebug/Dumper/HtmlDumper.php
+ $this->dumpPrefix = "<style>$s</style><pre class=$p style=white-space:pre>";
+ $this->dumpSuffix = '</pre>';
+ }
+
+ protected function style($style, $val)
+ {
+ if ('ref' === $style) {
+ $ref = substr($val, 1);
+ if ('#' === $val[0]) {
+ return "<a class=sf-var-debug-ref name=\"sf-var-debug-ref$ref\">$val</a>";
+ } else {
+ return "<a class=sf-var-debug-ref href=\"#sf-var-debug-ref$ref\">$val</a>";
+ }
+ }
+
+ $val = htmlspecialchars($val, ENT_NOQUOTES, 'UTF-8');
@stof

stof Apr 10, 2014

Member

why ENT_NOQUOTES ? We generally use ENT_QUOTES when escaping in Symfony

@stof stof and 1 other commented on an outdated diff Apr 10, 2014

...fony/Component/VarDebug/Resources/functions/debug.php
+<?php
+
+/*
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+use Symfony\Component\VarDebug\Collector\ExtCollector;
+use Symfony\Component\VarDebug\Collector\PhpCollector;
+use Symfony\Component\VarDebug\Dumper\CliDumper;
+
+if (!function_exists('debug')) {
@stof

stof Apr 10, 2014

Member

when discussing during the hackday yesterday, we discovered a conflict with Drupal which also has a debug function. Yesterday evening, I had an idea to workaround this issue (yeah, my brain keeps computing things at time I go to bed):
I suggest moving this logic to a namepsaced class with 2 static methods (it will also avoid using reflection to access the debug handler). then, the debug function defined conditionally would just be a wrapper around Symfony\Component\VarDebug\VarDebug::debug() (or whatever the class name is). This way, if you have a conflicting function in your codebase (i.e. Drupal for instance), you can still use the VarDebug component without copy-pasting the logic. You simply need to use the static method directly (or to define your own wrapper shortcut with a different name).
If we go this way, the Twig integration should call the static method directly to be compatible with cases where the shortcut wrapper is not available.
For set_debug_handler, we can then either drop it entirely or build it as a wrapper around the static method.

@romainneutron

romainneutron Apr 10, 2014

Member

👍 for Symfony\Component\VarDebug\VarDebug::debug()

@stof stof commented on an outdated diff Apr 10, 2014

...fony/Component/VarDebug/Resources/functions/debug.php
+ if (class_exists('Symfony\Component\VarDebug\Dumper\CliDumper')) {
+ $collector = extension_loaded('symfony_debug') ? new ExtCollector : new PhpCollector;
+ $dumper = new CliDumper;
+ $h['handler'] = function ($var) use ($collector, $dumper) {
+ $dumper->dump($collector->collect($var));
+ };
+ } else {
+ $h['handler'] = 'var_dump';
+ }
+ set_debug_handler($h['handler']);
+ }
+
+ return $h['handler']($var);
+ }
+
+ function set_debug_handler($callable)
@stof

stof Apr 10, 2014

Member

the code is currently handling the case of debug existing already, but it does not check set_debug_handler, which can create some issues (it will be solved by my suggestion above though)

@stof stof and 3 others commented on an outdated diff Apr 10, 2014

...fony/Component/VarDebug/Resources/functions/debug.php
+ $h = $reflector->getStaticVariables();
+
+ if (!isset($h['handler'])) {
+ if (class_exists('Symfony\Component\VarDebug\Dumper\CliDumper')) {
+ $collector = extension_loaded('symfony_debug') ? new ExtCollector : new PhpCollector;
+ $dumper = new CliDumper;
+ $h['handler'] = function ($var) use ($collector, $dumper) {
+ $dumper->dump($collector->collect($var));
+ };
+ } else {
+ $h['handler'] = 'var_dump';
+ }
+ set_debug_handler($h['handler']);
+ }
+
+ return $h['handler']($var);
@stof

stof Apr 10, 2014

Member

this syntax is not compatible with PHP 5.3 when using array($object, $method) for the callable. call_user_func need to be used (PHP 5.4+ handles it fine)

@nicolas-grekas

nicolas-grekas Apr 10, 2014

Owner

It works since PHP4.3.0 :)
http://3v4l.org/qiBkL

@stof

stof Apr 10, 2014

Member

nope. your handler is a function name, which is not the case I described in my comment. See http://3v4l.org/cZHfs for the actual testcase

@romainneutron

romainneutron Apr 10, 2014

Member

stof means that the following code does not work:

class a {
    function b() {
        $handler = array($this, 'hello');
        $handler('nicolas');
    }

    function hello($var) {
        echo 'hello '.$var;
    }
}

$a = new a();
$a->b();
@Taluu

Taluu Apr 10, 2014

Contributor

@romainneutron & @stof sure your points are valid... But here, it is either an anonymous function, either a string, which works in 5.3.

But as you can't control the form of $h['handler'], as it may be an array, described by @stof (and @romainneutron), you should use the call_user_func, as ugly as that is.

@nicolas-grekas

nicolas-grekas Apr 10, 2014

Owner

Ok understood! That's one of the reason why I previously accepted only closures...

@stof

stof Apr 10, 2014

Member

@nicolas-grekas in Symfony, we prefer giving more flexibility to the caller and taking care of this unfortunate limitation of PHP 5.3 internally.

@webmozart webmozart commented on the diff Sep 18, 2014

src/Symfony/Component/VarDumper/Cloner/Stub.php
+ * This file is part of the Symfony package.
+ *
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Component\VarDumper\Cloner;
+
+/**
+ * Represents the main properties of a PHP variable.
+ *
+ * @author Nicolas Grekas <p@tchwork.com>
+ */
+class Stub
@webmozart

webmozart Sep 18, 2014

Contributor

Why is this called "Stub"? Could you find a more descriptive name there?

Contributor

webmozart commented Sep 18, 2014

Hi @nicolas-grekas, this is a really awesome piece of code! I did a quick review, the code looks good. I'll try to do a more thorough review when I have more time.

I tried the dumper in a test project and have some feedback regarding the output styling in the web debugger. All of the below is obviously my humble opinion.

  • I think you should replace FQCNs by <abbr title="FQCN">ShortName</abbr>. The output is much easier to read this way.
  • I don't like the mixture of monospace/sans-serif. Could you use monospace everywhere?
  • The colors are quite hard to read with a white background. Could you increase the contrast?
  • Could you use the +/- icons we use in the rest of the profiler? See the Form debbuger for example.
  • Reference targets (#1) should be displayed behind the class name. This is what var_debug() does, and I can't think of a good reason to change that.
  • Also references (@1) should be prefixed by the class name as well, for clarity.
  • I think the contents of collapsed objects should be hidden. For example, right now the output is:
FooClass#1{
   [+] ref => BarClass#2{ prop => value, ref2 => BazClass#3{...
}

When collapsed, this should be reduced to:

FooClass#1{
   [+] ref => BarClass#2{ ... }
}

When people want to see the contents, they can click "+".

  • The same goes for arrays. There we have to options: Either we hide the contents of the collapsed array itself:
FooClass#1{
   [+] array => [ ... ]
}

Or we hide the contents of the values of the collapsed array:

FooClass#1{
   [+] array => [ key1 => val1, key2 => [ ... ], key3 => BarClass#2{ ... } ]
}

In the second case, it would probably be wise to display ... after a fixed number of key-value pairs (3?).

Nice to have:

  • When hovering over a class name/reference, all other class names/references referring to the same object could be highlighted. E.g., I hover over FooClass#1, both FooClass#1 and all FooClass@1 are highlighted with a yellow (or whatever) background.

I hacked together a quick example in Chrome.

Before

before

After

after

(imagine the cursor hovering over one of the two highlighted pieces. Also, I left the + and - unchanged; see my comment above. The line break before the first { was not intentional, just ignore it.)

Thanks for your great work! :)

Contributor

kbond commented Sep 18, 2014

I moved the auto-required file to an inline in the standard-edition: https://github.com/symfony/symfony-standard/pull/710/files

What about using the component standalone or if someone isn't using the standard edition?

Owner

nicolas-grekas commented Sep 18, 2014

The component has the required autoload file specification, so no pb.
For people not using the std edition and still relying on the full symfony/symfony package, they will need to inline the function in their code. We should document that somewhere.

Member

stof commented Sep 18, 2014

I wonder if this same extension would work with hhvm

It won't, as the extension deals with low-level details about the PHP data structures, and HHVM stores values differently

Contributor

kbond commented Sep 18, 2014

Ah I missed that it was still in the component's composer.json.

Is there a reason for not including the file autoload in the main composer.json?

Owner

fabpot commented Sep 18, 2014

@kbond Mainly performance

Contributor

kbond commented Sep 18, 2014

OK fair enough.

Owner

nicolas-grekas commented Sep 18, 2014

For HHVM, the tests show that the PhpCloner works well, so we won't miss anything feature wise.

Owner

fabpot commented Sep 22, 2014

👍

Owner

nicolas-grekas commented Sep 22, 2014

@webmozart thanks for your suggestions!
In fact the "dump visualization" subject has many room for improvement, and I'd prefer enhancing this part in later minor versions. It's "only" JS and/or CSS :)

Currently, there are already 3 "dumper engines" which you can choose by changing the debug.profiler_template config param:

  • '@Debug/Profiler/Patchwork/dump.html.twig' is the current default you experienced
  • '@Debug/Profiler/Base/dump.html.twig' is a pure HTML visu, with no expand/collapse button
  • '@Debug/Profiler/OwlyCode/dump.html.twig' is a new JS engine developed by @OwlyCode that paves the way for a better rendering engine (e.g. highlight on hover), but that need more work to become the default one.
Owner

fabpot commented Sep 22, 2014

@nicolas-grekas I'm not sure we need more than one dumper engines.

Contributor

rvanlaak commented Sep 22, 2014

Why not use https://github.com/raulfraile/LadybugBundle for dumping? It also is nicely integrated in Twig and the Symfony Profiler.

Contributor

cordoval commented Sep 22, 2014

@rvanlaak besides the fact that ladybug is based on types https://github.com/raulfraile/ladybug/tree/master/src/Ladybug/Type and this PR uses a more native PHP function to introspect references etc, this has also a C extension which makes it faster.

@slaci slaci commented on the diff Sep 23, 2014

src/Symfony/Bridge/Twig/Node/DumpNode.php
+ * (c) Fabien Potencier <fabien@symfony.com>
+ *
+ * For the full copyright and license information, please view the LICENSE
+ * file that was distributed with this source code.
+ */
+
+namespace Symfony\Bridge\Twig\Node;
+
+/**
+ * @author Julien Galenski <julien.galenski@gmail.com>
+ */
+class DumpNode extends \Twig_Node
+{
+ private $varPrefix;
+
+ public function __construct($varPrefix, \Twig_NodeInterface $values = null, $lineno, $tag = null)
@slaci

slaci Sep 23, 2014

Hey

$lineno without default value after an optional argument?

Example #5 Incorrect usage of default function arguments php.net

@xabbuh

xabbuh Sep 23, 2014

Member

The default value for $values is required. Otherwise, you wouldn't be able to pass null as an argument because of the type hint.

@slaci

slaci Sep 23, 2014

That's ok, but mandatory parameter should not follow an optional (not php error but...). Twig_Node has $lineno = 0 as default, so this should have too :) Nothing big, just noticed.

Owner

nicolas-grekas commented Sep 23, 2014

Here is the new default rendering:
capture du 2014-09-23 14 59 13
capture du 2014-09-23 14 58 57

I removed the option for several rendering engines and choose to keep the "base" one. This removes the Patchwork and @OwlyCode renderer. As the JsonDumper is not used anymore, I also removed it and its related tests+doc from the git history of this PR. I kept them in patchwork/dumper so they can come back again later.

I also enhanced the base rendering engine with some JS. This should match some of your suggestions @webmozart .
The main benefit of this approach is that enhancing the HtmlDumper not only enhances the profiler panel, but also inline dumps (the twig function and dump()+exit in your code).

(the remaining fabbot error is intentional)

Owner

fabpot commented Sep 23, 2014

It took time, but here we go, this is in now. Thank you very much @nicolas-grekas.

@fabpot fabpot merged commit 80fd736 into symfony:master Sep 23, 2014

0 of 2 checks passed

continuous-integration/travis-ci The Travis CI build failed
Details
default Failure: Travis, fabbot
Details

fabpot added a commit that referenced this pull request Sep 23, 2014

feature #10640 VarDumper and DebugBundle (jpauli, nicolas-grekas, rui…
…an, moux2003, tony-co, romainneutron, oscherler, lyrixx)

This PR was merged into the 2.6-dev branch.

Discussion
----------

VarDumper and DebugBundle

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

From a user land point of view, this PR creates a global `dump()` function that is to be used instead of `var_dump()`. The component can be used standalone in any dev workflow. Please see the [provided README](https://github.com/symfony/symfony/pull/10640/files?short_path=52d526f#diff-52d526f19bc9e3825c80e7694755409c) for details.

When used with the Framework bundle, variables passed to `dump()` are dumped in a new dedicated panel in the web toolbar. The function is also available in twig templates.

Regarding the implementation, I'm pretty sure you'll find a lot to comment. As I'm sure of nothing else, not even the names of things, please do.

I tried to organize this PR in several commits, from the most fundamental algorithm to pure Symfony glue.
I suggest you follow this order while progressing in the review and the discussion around this PR, so that we can together validate commits one after the other.

Don't hesitate to fork the PR and submit PR on it, I'll cherry-pick your patches.

TODO:
- [x] open a doc PR: symfony/symfony-docs#4243
- [x] open a PR on the Standard edition: symfony/symfony-standard#710
- [x] prefix the CSS classes
- [x] tests for the DebugBundle + other Symfony glue classes
- [x] inline css and js for compat with e.g. Silex
- [x] finish and merge nicolas-grekas/Patchwork-Dumper#5 for better UX
- [x] show a dump excerpt on hovering the icon in the toolbar
- [x] verify README and comments
- [x] validate interfaces/names (Caster / Cloner / Dumper)
- [x] validate new VarDumper component + DebugBundle
- [x] validate Resource/ext/ vs independent repos.
- [x] test and define behavior after KernelEvents::RESPONSE
- [x] update dependencies between components/bundles and composer.json files
- [x] no hard dep on iconv

Not for this PR but might be worth later:
- show a light stack trace + timing + memory at debug() calls
- create a "theme" concept for custom colors/UX

Commits
-------

80fd736 [DebugBundle] Enhance some comments
2e167ba [TwigBridge] add Twig dump() function + tests and fixes
0f8d30f [VarDumper] Replace \e with \x1B in CliDumper to support colour in PHP < 5.4
d43ae82 [VarDumper] Add workaround to https://bugs.php.net/65967
a8d81e4 [DebugBundle] Inlined assets to avoid installation issues
5f59811 [DebugBundle] Add doc example for Twig usage
e4e00ef [TwigBridge] DumpNode and Token parser
de05cd9 [DebugBundle] enhance dump excerpts
49f13c6 [HttpKernel] add tests for DumpDataCollector
081363c [HttpKernel] tests for DumpListener
0d8a942 [VarDumper] add Stub objects for cutting cleanly and dumping consts
c8746a4 [DebugBundle] add tests for twig and for the bundle
8d5d970 [DebugBundle] adjust after review
eb98c81 [DebugBundle] dump() + better Symfony glue
9dea601 [DebugBundle] global dump() function for daily use
297d373 [VarDumper] README, LICENSE and composer.json
a69e962 [VarDumper] tests for HtmlDumper
5eaa187 [VarDumper] tests for CliDumper
e6dde33 [VarDumper] HTML variant of the CLI dumper
fa81544 [VarDumper] CLI dedicated dumper and related abstract
1d5e3f4 [VarDumper] interface for dumping collected variables
0266072 [VarDumper] casters for DOM objects
c426d8b [VarDumper] casters for Doctrine objects
0a92c08 [VarDumper] casters for PDO related objects
da3e50a [VarDumper] casters for SPL data structures
c91bc83 [VarDumper] casters for exceptions representation
3ddbf4b [VarDumper] add casters for per class/resource custom state extraction
5b7ae28 [VarDumper] symfony_debug ext. fast and memory efficient cloning algo
07135a0 [VarDumper] algo to clone any PHP variable to a breadth-first queue
4bf9300 [Debug] a README for the debug extension
eec5c92 [Debug] Symfony debug extension

@nicolas-grekas nicolas-grekas deleted the nicolas-grekas:var-debug branch Sep 23, 2014

Contributor

mykiwi commented Sep 23, 2014

👏

Contributor

mickaelandrieu commented Sep 23, 2014

This is an amazing enhancement, thanks to all contributors !

awesome! 👊

Was waiting impatiently for this feature, thanks guys !

EDIT : Though, bad news for LadybugBundle, won't be very useful anymore...

fabpot added a commit to symfony/symfony-standard that referenced this pull request Sep 25, 2014

feature #710 Enable DebugBundle (nicolas-grekas)
This PR was merged into the 2.6-dev branch.

Discussion
----------

Enable DebugBundle

This enables the DebugBundle as proposed in symfony/symfony#10640

Commits
-------

dadea61 Enable DebugBundle
Owner

nicolas-grekas commented Dec 2, 2014

For reference, if you'd like to use VarDumper / dump() in your Symfony 2.3/4/5 app, I backported the Debug-Bundle, here it is:
https://github.com/tchwork/debug-bundle

Member

xabbuh commented Dec 2, 2014

@nicolas-grekas Could it be worth to add this information to the documentation?

I found an issue here. If I add the following to a form…

$builder->addEventListener(FormEvents::SUBMIT, 'dump');

…I get a PHP notice…

Undefined index: file

Thank you for your great work on this!

Member

stof replied Mar 12, 2015

Please open issues to report bugs. Comments on old commits are not tracked so it gets lost

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment