[HttpKernel] Use VarDumper in the profiler

This commit is contained in:
WouterJ 2016-08-14 15:24:11 +02:00
parent c21bed7fed
commit 41a76494ec
29 changed files with 465 additions and 279 deletions

View File

@ -38,8 +38,14 @@ Form
FrameworkBundle FrameworkBundle
--------------- ---------------
* The service `serializer.mapping.cache.doctrine.apc` is deprecated. APCu should now * The service `serializer.mapping.cache.doctrine.apc` is deprecated. APCu should now
be automatically used when available. be automatically used when available.
HttpKernel
----------
* `DataCollector::varToString()` is deprecated and will be removed in Symfony
4.0. Use the `cloneVar()` method instead.
HttpFoundation HttpFoundation
--------------- ---------------

View File

@ -155,6 +155,8 @@ HttpKernel
have your own `ControllerResolverInterface` implementation, you should have your own `ControllerResolverInterface` implementation, you should
inject an `ArgumentResolverInterface` instance. inject an `ArgumentResolverInterface` instance.
* The `DataCollector::varToString()` method has been removed in favor of `cloneVar()`.
Serializer Serializer
---------- ----------

View File

@ -24,6 +24,7 @@
<service id="data_collector.form" class="Symfony\Component\Form\Extension\DataCollector\FormDataCollector"> <service id="data_collector.form" class="Symfony\Component\Form\Extension\DataCollector\FormDataCollector">
<tag name="data_collector" template="@WebProfiler/Collector/form.html.twig" id="form" priority="310" /> <tag name="data_collector" template="@WebProfiler/Collector/form.html.twig" id="form" priority="310" />
<argument type="service" id="data_collector.form.extractor" /> <argument type="service" id="data_collector.form.extractor" />
<argument>false</argument>
</service> </service>
</services> </services>
</container> </container>

View File

@ -20,6 +20,7 @@ use Symfony\Component\Security\Core\Role\RoleInterface;
use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator; use Symfony\Component\Security\Http\Logout\LogoutUrlGenerator;
use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface; use Symfony\Component\Security\Core\Authorization\AccessDecisionManagerInterface;
use Symfony\Component\Security\Core\Authorization\DebugAccessDecisionManager; use Symfony\Component\Security\Core\Authorization\DebugAccessDecisionManager;
use Symfony\Component\VarDumper\Cloner\Data;
/** /**
* SecurityDataCollector. * SecurityDataCollector.
@ -58,6 +59,7 @@ class SecurityDataCollector extends DataCollector
$this->data = array( $this->data = array(
'enabled' => false, 'enabled' => false,
'authenticated' => false, 'authenticated' => false,
'token' => null,
'token_class' => null, 'token_class' => null,
'logout_url' => null, 'logout_url' => null,
'user' => '', 'user' => '',
@ -69,6 +71,7 @@ class SecurityDataCollector extends DataCollector
$this->data = array( $this->data = array(
'enabled' => true, 'enabled' => true,
'authenticated' => false, 'authenticated' => false,
'token' => null,
'token_class' => null, 'token_class' => null,
'logout_url' => null, 'logout_url' => null,
'user' => '', 'user' => '',
@ -101,18 +104,24 @@ class SecurityDataCollector extends DataCollector
$this->data = array( $this->data = array(
'enabled' => true, 'enabled' => true,
'authenticated' => $token->isAuthenticated(), 'authenticated' => $token->isAuthenticated(),
'token' => $this->cloneVar($token),
'token_class' => get_class($token), 'token_class' => get_class($token),
'logout_url' => $logoutUrl, 'logout_url' => $logoutUrl,
'user' => $token->getUsername(), 'user' => $token->getUsername(),
'roles' => array_map(function (RoleInterface $role) { return $role->getRole();}, $assignedRoles), 'roles' => $this->cloneVar(array_map(function (RoleInterface $role) { return $role->getRole();}, $assignedRoles)),
'inherited_roles' => array_map(function (RoleInterface $role) { return $role->getRole(); }, $inheritedRoles), 'inherited_roles' => $this->cloneVar(array_map(function (RoleInterface $role) { return $role->getRole(); }, $inheritedRoles)),
'supports_role_hierarchy' => null !== $this->roleHierarchy, 'supports_role_hierarchy' => null !== $this->roleHierarchy,
); );
} }
// collect voters and access decision manager information // collect voters and access decision manager information
if ($this->accessDecisionManager instanceof DebugAccessDecisionManager) { if ($this->accessDecisionManager instanceof DebugAccessDecisionManager) {
$this->data['access_decision_log'] = $this->accessDecisionManager->getDecisionLog(); $this->data['access_decision_log'] = array_map(function ($decision) {
$decision['object'] = $this->cloneVar($decision['object']);
return $decision;
}, $this->accessDecisionManager->getDecisionLog());
$this->data['voter_strategy'] = $this->accessDecisionManager->getStrategy(); $this->data['voter_strategy'] = $this->accessDecisionManager->getStrategy();
foreach ($this->accessDecisionManager->getVoters() as $voter) { foreach ($this->accessDecisionManager->getVoters() as $voter) {
@ -196,6 +205,16 @@ class SecurityDataCollector extends DataCollector
return $this->data['token_class']; return $this->data['token_class'];
} }
/**
* Get the full security token class as Data object.
*
* @return Data
*/
public function getToken()
{
return $this->data['token'];
}
/** /**
* Get the provider key (i.e. the name of the active firewall). * Get the provider key (i.e. the name of the active firewall).
* *

View File

@ -3,7 +3,7 @@
{% block page_title 'Security' %} {% block page_title 'Security' %}
{% block toolbar %} {% block toolbar %}
{% if collector.tokenClass %} {% if collector.token %}
{% set is_authenticated = collector.enabled and collector.authenticated %} {% set is_authenticated = collector.enabled and collector.authenticated %}
{% set color_code = is_authenticated ? '' : 'yellow' %} {% set color_code = is_authenticated ? '' : 'yellow' %}
{% else %} {% else %}
@ -16,7 +16,7 @@
{% endset %} {% endset %}
{% set text %} {% set text %}
{% if collector.tokenClass %} {% if collector.token %}
<div class="sf-toolbar-info-piece"> <div class="sf-toolbar-info-piece">
<b>Logged in as</b> <b>Logged in as</b>
<span>{{ collector.user }}</span> <span>{{ collector.user }}</span>
@ -27,7 +27,7 @@
<span class="sf-toolbar-status sf-toolbar-status-{{ is_authenticated ? 'green' : 'red' }}">{{ is_authenticated ? 'Yes' : 'No' }}</span> <span class="sf-toolbar-status sf-toolbar-status-{{ is_authenticated ? 'green' : 'red' }}">{{ is_authenticated ? 'Yes' : 'No' }}</span>
</div> </div>
{% if collector.tokenClass != null %} {% if collector.token != null %}
<div class="sf-toolbar-info-piece"> <div class="sf-toolbar-info-piece">
<b>Token class</b> <b>Token class</b>
<span>{{ collector.tokenClass|abbr_class }}</span> <span>{{ collector.tokenClass|abbr_class }}</span>
@ -54,7 +54,7 @@
{% endblock %} {% endblock %}
{% block menu %} {% block menu %}
<span class="label {{ not collector.enabled or not collector.tokenClass ? 'disabled' }}"> <span class="label {{ not collector.enabled or not collector.token ? 'disabled' }}">
<span class="icon">{{ include('@Security/Collector/icon.svg') }}</span> <span class="icon">{{ include('@Security/Collector/icon.svg') }}</span>
<strong>Security</strong> <strong>Security</strong>
</span> </span>
@ -63,7 +63,7 @@
{% block panel %} {% block panel %}
<h2>Security Token</h2> <h2>Security Token</h2>
{% if collector.tokenClass %} {% if collector.token %}
<div class="metrics"> <div class="metrics">
<div class="metric"> <div class="metric">
<span class="value">{{ collector.user == 'anon.' ? 'Anonymous' : collector.user }}</span> <span class="value">{{ collector.user == 'anon.' ? 'Anonymous' : collector.user }}</span>
@ -87,7 +87,7 @@
<tr> <tr>
<th>Roles</th> <th>Roles</th>
<td> <td>
{{ collector.roles is empty ? 'none' : collector.roles|yaml_encode }} {{ collector.roles is empty ? 'none' : profiler_dump(collector.roles, maxDepth=1) }}
{% if not collector.authenticated and collector.roles is empty %} {% if not collector.authenticated and collector.roles is empty %}
<p class="help">User is not authenticated probably because they have no roles.</p> <p class="help">User is not authenticated probably because they have no roles.</p>
@ -98,14 +98,14 @@
{% if collector.supportsRoleHierarchy %} {% if collector.supportsRoleHierarchy %}
<tr> <tr>
<th>Inherited Roles</th> <th>Inherited Roles</th>
<td>{{ collector.inheritedRoles is empty ? 'none' : collector.inheritedRoles|yaml_encode }}</td> <td>{{ collector.inheritedRoles is empty ? 'none' : profiler_dump(collector.inheritedRoles, maxDepth=1) }}</td>
</tr> </tr>
{% endif %} {% endif %}
{% if collector.tokenClass %} {% if collector.token %}
<tr> <tr>
<th>Token class</th> <th>Token</th>
<td>{{ collector.tokenClass }}</td> <td>{{ profiler_dump(collector.token) }}</td>
</tr> </tr>
{% endif %} {% endif %}
</tbody> </tbody>

View File

@ -62,8 +62,12 @@ class SecurityDataCollectorTest extends \PHPUnit_Framework_TestCase
$this->assertTrue($collector->isAuthenticated()); $this->assertTrue($collector->isAuthenticated());
$this->assertSame('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', $collector->getTokenClass()); $this->assertSame('Symfony\Component\Security\Core\Authentication\Token\UsernamePasswordToken', $collector->getTokenClass());
$this->assertTrue($collector->supportsRoleHierarchy()); $this->assertTrue($collector->supportsRoleHierarchy());
$this->assertSame($normalizedRoles, $collector->getRoles()); $this->assertSame($normalizedRoles, $collector->getRoles()->getRawData()[1]);
$this->assertSame($inheritedRoles, $collector->getInheritedRoles()); if ($inheritedRoles) {
$this->assertSame($inheritedRoles, $collector->getInheritedRoles()->getRawData()[1]);
} else {
$this->assertSame($inheritedRoles, $collector->getInheritedRoles()->getRawData()[0][0]);
}
$this->assertSame('hhamon', $collector->getUser()); $this->assertSame('hhamon', $collector->getUser());
} }

View File

@ -18,7 +18,7 @@
"require": { "require": {
"php": ">=5.5.9", "php": ">=5.5.9",
"symfony/security": "~3.2", "symfony/security": "~3.2",
"symfony/http-kernel": "~3.1", "symfony/http-kernel": "~3.2",
"symfony/polyfill-php70": "~1.0" "symfony/polyfill-php70": "~1.0"
}, },
"require-dev": { "require-dev": {
@ -34,6 +34,7 @@
"symfony/twig-bridge": "~2.8|~3.0", "symfony/twig-bridge": "~2.8|~3.0",
"symfony/process": "~2.8|~3.0", "symfony/process": "~2.8|~3.0",
"symfony/validator": "~2.8|~3.0", "symfony/validator": "~2.8|~3.0",
"symfony/var-dumper": "~3.2",
"symfony/yaml": "~2.8|~3.0", "symfony/yaml": "~2.8|~3.0",
"symfony/expression-language": "~2.8|~3.0", "symfony/expression-language": "~2.8|~3.0",
"doctrine/doctrine-bundle": "~1.4", "doctrine/doctrine-bundle": "~1.4",

View File

@ -75,38 +75,7 @@
<tr> <tr>
<td class="text-right">{{ listener.priority|default('-') }}</td> <td class="text-right">{{ listener.priority|default('-') }}</td>
<td class="font-normal"> <td class="font-normal">{{ profiler_dump(listener.data) }}</td>
{% if listener.type == 'Closure' %}
Closure
<span class="text-muted text-small">(there is no class or file information)</span>
{% elseif listener.type == 'Function' %}
{% set link = listener.file|file_link(listener.line) %}
{% if link %}
<a href="{{ link }}">{{ listener.function }}()</a>
<span class="text-muted text-small">({{ listener.file }})</span>
{% else %}
{{ listener.function }}()
<span class="text-muted newline text-small">{{ listener.file }} (line {{ listener.line }})</span>
{% endif %}
{% elseif listener.type == "Method" %}
{% set link = listener.file|file_link(listener.line) %}
{% set class_namespace = listener.class|split('\\', -1)|join('\\') %}
{% if link %}
<a href="{{ link }}"><strong>{{ listener.class|abbr_class|striptags }}</strong>::{{ listener.method }}()</a>
<span class="text-muted text-small">({{ listener.class }})</span>
{% else %}
<span>{{ class_namespace }}\</span><strong>{{ listener.class|abbr_class|striptags }}</strong>::{{ listener.method }}()
<span class="text-muted newline text-small">{{ listener.file }} (line {{ listener.line }})</span>
{% endif %}
{% endif %}
</td>
</tr> </tr>
{% if loop.last %} {% if loop.last %}

View File

@ -152,9 +152,6 @@
margin-top: -9px; margin-top: -9px;
margin-left: 6px; margin-left: 6px;
} }
.form-type {
color: #999;
}
.badge-error { .badge-error {
float: right; float: right;
background: #B0413E; background: #B0413E;
@ -198,7 +195,7 @@
<div id="tree-details-container"> <div id="tree-details-container">
{% for formName, formData in collector.data.forms %} {% for formName, formData in collector.data.forms %}
{{ form_tree_details(formName, formData, collector.data.forms_by_hash) }} {{ form_tree_details(formName, formData, collector.data.forms_by_hash, loop.first) }}
{% endfor %} {% endfor %}
</div> </div>
{% else %} {% else %}
@ -442,7 +439,7 @@
{% endif %} {% endif %}
<span {% if has_error or data.has_children_error|default(false) %}class="has-error"{% endif %}> <span {% if has_error or data.has_children_error|default(false) %}class="has-error"{% endif %}>
{{ name|default('(no name)') }} {% if data.type_class is defined %}[<abbr title="{{ data.type_class }}">{{ data.type_class|split('\\')|last }}</abbr>]{% endif %} {{ name|default('(no name)') }}
</span> </span>
</div> </div>
@ -456,14 +453,11 @@
</li> </li>
{% endmacro %} {% endmacro %}
{% macro form_tree_details(name, data, forms_by_hash) %} {% macro form_tree_details(name, data, forms_by_hash, show) %}
{% import _self as tree %} {% import _self as tree %}
<div class="tree-details" {% if data.id is defined %}id="{{ data.id }}-details"{% endif %}> <div class="tree-details{% if not show|default(false) %} hidden{% endif %}" {% if data.id is defined %}id="{{ data.id }}-details"{% endif %}>
<h2> <h2 class="dump-inline">
{{ name|default('(no name)') }} {{ name|default('(no name)') }} ({{ profiler_dump(data.type_class) }})
{% if data.type_class is defined and data.type is defined %}
<span class="form-type">[<abbr title="{{ data.type_class }}">{{ data.type }}</abbr>]</span>
{% endif %}
</h2> </h2>
{% if data.errors is defined and data.errors|length > 0 %} {% if data.errors is defined and data.errors|length > 0 %}
@ -496,29 +490,12 @@
{% endif %} {% endif %}
</td> </td>
<td> <td>
{% for trace in error.trace %} {% if error.trace %}
{% if not loop.first %} <span class="newline">Caused by:</span>
<span class="newline">Caused by:</span> {{ profiler_dump(error.trace, maxDepth=2) }}
{% endif %}
{% if trace.root is defined %}
<strong class="newline">{{ trace.class }}</strong>
<pre>
{{- trace.root -}}
{%- if trace.path is not empty -%}
{%- if trace.path|first != '[' %}.{% endif -%}
{{- trace.path -}}
{%- endif %} = {{ trace.value -}}
</pre>
{% elseif trace.message is defined %}
<strong class="newline">{{ trace.class }}</strong>
<pre>{{ trace.message }}</pre>
{% else %}
<pre>{{ trace }}</pre>
{% endif %}
{% else %} {% else %}
<em>Unknown.</em> <em>Unknown.</em>
{% endfor %} {% endif %}
</td> </td>
</tr> </tr>
{% endfor %} {% endfor %}
@ -547,7 +524,7 @@
<th class="font-normal" scope="row">Model Format</th> <th class="font-normal" scope="row">Model Format</th>
<td> <td>
{% if data.default_data.model is defined %} {% if data.default_data.model is defined %}
{{ data.default_data.model }} {{ profiler_dump(data.default_data.model) }}
{% else %} {% else %}
<em class="font-normal text-muted">same as normalized format</em> <em class="font-normal text-muted">same as normalized format</em>
{% endif %} {% endif %}
@ -555,13 +532,13 @@
</tr> </tr>
<tr> <tr>
<th class="font-normal" scope="row">Normalized Format</th> <th class="font-normal" scope="row">Normalized Format</th>
<td>{{ data.default_data.norm }}</td> <td>{{ profiler_dump(data.default_data.norm) }}</td>
</tr> </tr>
<tr> <tr>
<th class="font-normal" scope="row">View Format</th> <th class="font-normal" scope="row">View Format</th>
<td> <td>
{% if data.default_data.view is defined %} {% if data.default_data.view is defined %}
{{ data.default_data.view }} {{ profiler_dump(data.default_data.view) }}
{% else %} {% else %}
<em class="font-normal text-muted">same as normalized format</em> <em class="font-normal text-muted">same as normalized format</em>
{% endif %} {% endif %}
@ -593,7 +570,7 @@
<th class="font-normal" scope="row">View Format</th> <th class="font-normal" scope="row">View Format</th>
<td> <td>
{% if data.submitted_data.view is defined %} {% if data.submitted_data.view is defined %}
{{ data.submitted_data.view }} {{ profiler_dump(data.submitted_data.view) }}
{% else %} {% else %}
<em class="font-normal text-muted">same as normalized format</em> <em class="font-normal text-muted">same as normalized format</em>
{% endif %} {% endif %}
@ -601,13 +578,13 @@
</tr> </tr>
<tr> <tr>
<th class="font-normal" scope="row">Normalized Format</th> <th class="font-normal" scope="row">Normalized Format</th>
<td>{{ data.submitted_data.norm }}</td> <td>{{ profiler_dump(data.submitted_data.norm) }}</td>
</tr> </tr>
<tr> <tr>
<th class="font-normal" scope="row">Model Format</th> <th class="font-normal" scope="row">Model Format</th>
<td> <td>
{% if data.submitted_data.model is defined %} {% if data.submitted_data.model is defined %}
{{ data.submitted_data.model }} {{ profiler_dump(data.submitted_data.model) }}
{% else %} {% else %}
<em class="font-normal text-muted">same as normalized format</em> <em class="font-normal text-muted">same as normalized format</em>
{% endif %} {% endif %}
@ -644,12 +621,12 @@
{% for option, value in data.passed_options %} {% for option, value in data.passed_options %}
<tr> <tr>
<th>{{ option }}</th> <th>{{ option }}</th>
<td>{{ value }}</td> <td>{{ profiler_dump(value) }}</td>
<td> <td>
{% if data.resolved_options[option] is same as(value) %} {% if data.resolved_options[option] == value %}
<em class="font-normal text-muted">same as passed value</em> <em class="font-normal text-muted">same as passed value</em>
{% else %} {% else %}
{{ data.resolved_options[option] }} {{ profiler_dump(data.resolved_options[option]) }}
{% endif %} {% endif %}
</td> </td>
</tr> </tr>
@ -683,7 +660,7 @@
{% for option, value in data.resolved_options %} {% for option, value in data.resolved_options %}
<tr> <tr>
<th scope="row">{{ option }}</th> <th scope="row">{{ option }}</th>
<td>{{ value }}</td> <td>{{ profiler_dump(value) }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>
@ -710,7 +687,7 @@
{% for variable, value in data.view_vars %} {% for variable, value in data.view_vars %}
<tr> <tr>
<th scope="row">{{ variable }}</th> <th scope="row">{{ variable }}</th>
<td>{{ value }}</td> <td>{{ profiler_dump(value) }}</td>
</tr> </tr>
{% endfor %} {% endfor %}
</tbody> </tbody>

View File

@ -120,7 +120,7 @@
<p>No GET parameters</p> <p>No GET parameters</p>
</div> </div>
{% else %} {% else %}
{{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestquery }, with_context = false) }} {{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestquery, maxDepth: 1 }, with_context = false) }}
{% endif %} {% endif %}
<h3>POST Parameters</h3> <h3>POST Parameters</h3>
@ -130,7 +130,7 @@
<p>No POST parameters</p> <p>No POST parameters</p>
</div> </div>
{% else %} {% else %}
{{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestrequest }, with_context = false) }} {{ include('@WebProfiler/Profiler/bag.html.twig', { bag: collector.requestrequest, maxDepth: 1 }, with_context = false) }}
{% endif %} {% endif %}
<h3>Request Attributes</h3> <h3>Request Attributes</h3>
@ -252,7 +252,7 @@
{% for child in profile.children %} {% for child in profile.children %}
<h3> <h3>
<a href="{{ path('_profiler', { token: child.token }) }}"> <a href="{{ path('_profiler', { token: child.token }) }}">
{{- child.getcollector('request').requestattributes.get('_controller') -}} {{- child.getcollector('request').identifier -}}
</a> </a>
<small>(token = {{ child.token }})</small> <small>(token = {{ child.token }})</small>
</h3> </h3>

View File

@ -128,7 +128,7 @@
{% for child in profile.children %} {% for child in profile.children %}
{% set events = child.getcollector('time').events %} {% set events = child.getcollector('time').events %}
<h4> <h4>
<a href="{{ path('_profiler', { token: child.token, panel: 'time' }) }}">{{ child.getcollector('request').requestattributes.get('_controller') }}</a> <a href="{{ path('_profiler', { token: child.token, panel: 'time' }) }}">{{ child.getcollector('request').identifier }}</a>
<small>{{ events.__section__.duration }} ms</small> <small>{{ events.__section__.duration }} ms</small>
</h4> </h4>

View File

@ -9,7 +9,7 @@
{% for key in bag.keys|sort %} {% for key in bag.keys|sort %}
<tr> <tr>
<th>{{ key }}</th> <th>{{ key }}</th>
<td>{{ profiler_dump(bag.get(key)) }}</td> <td>{{ profiler_dump(bag.get(key), maxDepth=maxDepth|default(0)) }}</td>
</tr> </tr>
{% else %} {% else %}
<tr> <tr>

View File

@ -889,12 +889,6 @@ table.logs .sf-call-stack abbr {
#collector-content .sf-dump samp { #collector-content .sf-dump samp {
{{ mixins.monospace_font|raw }} {{ mixins.monospace_font|raw }}
} }
#collector-content pre.sf-dump {
background: #222;
line-height: 1.4;
margin-top: .5em;
padding: 1em;
}
#collector-content .sf-dump h3 { #collector-content .sf-dump h3 {
font-size: 18px; font-size: 18px;
margin: .5em 0 0; margin: .5em 0 0;
@ -907,9 +901,28 @@ table.logs .sf-call-stack abbr {
#collector-content .sf-dump-str { color: #629755; } #collector-content .sf-dump-str { color: #629755; }
#collector-content .sf-dump-private, #collector-content .sf-dump-private,
#collector-content .sf-dump-protected, #collector-content .sf-dump-protected,
#collector-content .sf-dump-public { color: #E0E0E0; } #collector-content .sf-dump-public { color: #262626; }
#collector-content .sf-dump-note { color: #6897BB; } #collector-content .sf-dump-note { color: #6897BB; }
#collector-content .sf-dump-key { color: #A5C261; } #collector-content .sf-dump-key { color: #789339; }
#collector-content .sf-dump-ref { color: #6E6E6E; }
#collector-content .sf-dump {
margin: 0;
padding: 0;
background: none;
line-height: 1.4;
}
#collector-content .dump-inline .sf-dump {
display: inline;
white-space: normal;
font-size: inherit;
line-height: inherit;
}
#collector-content .dump-inline pre.sf-dump .sf-dump-ellipsis {
width: 4em;
}
#collector-content .sf-dump .trace { #collector-content .sf-dump .trace {
border: 1px solid #DDD; border: 1px solid #DDD;

View File

@ -12,31 +12,89 @@
namespace Symfony\Bundle\WebProfilerBundle\Twig; namespace Symfony\Bundle\WebProfilerBundle\Twig;
use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Dumper\HtmlDumper;
/** /**
* Twig extension for the profiler. * Twig extension for the profiler.
* *
* @author Fabien Potencier <fabien@symfony.com> * @author Fabien Potencier <fabien@symfony.com>
*/ */
class WebProfilerExtension extends \Twig_Extension class WebProfilerExtension extends \Twig_Extension_Profiler
{ {
/** /**
* @var ValueExporter * @var ValueExporter
*/ */
private $valueExporter; private $valueExporter;
/**
* @var HtmlDumper
*/
private $dumper;
/**
* @var resource
*/
private $output;
/**
* @var int
*/
private $stackLevel = 0;
public function __construct(HtmlDumper $dumper = null)
{
$this->dumper = $dumper ?: new HtmlDumper();
$this->dumper->setOutput($this->output = fopen('php://memory', 'r+b'));
}
public function enter(\Twig_Profiler_Profile $profile)
{
++$this->stackLevel;
}
public function leave(\Twig_Profiler_Profile $profile)
{
if (0 === --$this->stackLevel) {
$this->dumper->setOutput($this->output = fopen('php://memory', 'r+b'));
}
}
/** /**
* {@inheritdoc} * {@inheritdoc}
*/ */
public function getFunctions() public function getFunctions()
{ {
$profilerDump = function (\Twig_Environment $env, $value, $maxDepth = 0) {
return $value instanceof Data ? $this->dumpData($env, $value, $maxDepth) : twig_escape_filter($env, $this->dumpValue($value));
};
return array( return array(
new \Twig_SimpleFunction('profiler_dump', array($this, 'dumpValue')), new \Twig_SimpleFunction('profiler_dump', $profilerDump, array('is_safe' => array('html'), 'needs_environment' => true)),
); );
} }
public function dumpData(\Twig_Environment $env, Data $data, $maxDepth = 0)
{
$this->dumper->setCharset($env->getCharset());
$this->dumper->dump($data, null, array(
'maxDepth' => $maxDepth,
));
$dump = stream_get_contents($this->output, -1, 0);
rewind($this->output);
ftruncate($this->output, 0);
return str_replace("\n</pre", '</pre', rtrim($dump));
}
/**
* @deprecated since 3.2, to be removed in 4.0. Use the dumpData() method instead.
*/
public function dumpValue($value) public function dumpValue($value)
{ {
@trigger_error(sprintf('The %s() method is deprecated since version 3.2 and will be removed in 4.0. Use the dumpData() method instead.', __METHOD__), E_USER_DEPRECATED);
if (null === $this->valueExporter) { if (null === $this->valueExporter) {
$this->valueExporter = new ValueExporter(); $this->valueExporter = new ValueExporter();
} }

View File

@ -17,7 +17,7 @@
], ],
"require": { "require": {
"php": ">=5.5.9", "php": ">=5.5.9",
"symfony/http-kernel": "~3.1", "symfony/http-kernel": "~3.2",
"symfony/polyfill-php70": "~1.0", "symfony/polyfill-php70": "~1.0",
"symfony/routing": "~2.8|~3.0", "symfony/routing": "~2.8|~3.0",
"symfony/twig-bridge": "~2.8|~3.0", "symfony/twig-bridge": "~2.8|~3.0",

View File

@ -15,6 +15,12 @@ use Symfony\Component\Form\FormInterface;
use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormView;
use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter;
use Symfony\Component\Validator\ConstraintViolationInterface; use Symfony\Component\Validator\ConstraintViolationInterface;
use Symfony\Component\VarDumper\Caster\Caster;
use Symfony\Component\VarDumper\Caster\ClassStub;
use Symfony\Component\VarDumper\Caster\StubCaster;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\Stub;
use Symfony\Component\VarDumper\Cloner\VarCloner;
/** /**
* Default implementation of {@link FormDataExtractorInterface}. * Default implementation of {@link FormDataExtractorInterface}.
@ -23,11 +29,19 @@ use Symfony\Component\Validator\ConstraintViolationInterface;
*/ */
class FormDataExtractor implements FormDataExtractorInterface class FormDataExtractor implements FormDataExtractorInterface
{ {
private $valueExporter; /**
* @var VarCloner
*/
private $cloner;
public function __construct(ValueExporter $valueExporter = null) /**
* Constructs a new data extractor.
*/
public function __construct(ValueExporter $valueExporter = null, $triggerDeprecationNotice = true)
{ {
$this->valueExporter = $valueExporter ?: new ValueExporter(); if (null !== $valueExporter && $triggerDeprecationNotice) {
@trigger_error('Passing a ValueExporter instance to '.__METHOD__.'() is deprecated in version 3.2 and will be removed in 4.0.', E_USER_DEPRECATED);
}
} }
/** /**
@ -38,18 +52,18 @@ class FormDataExtractor implements FormDataExtractorInterface
$data = array( $data = array(
'id' => $this->buildId($form), 'id' => $this->buildId($form),
'name' => $form->getName(), 'name' => $form->getName(),
'type_class' => get_class($form->getConfig()->getType()->getInnerType()), 'type_class' => $this->cloneVar(new ClassStub(get_class($form->getConfig()->getType()->getInnerType()))),
'synchronized' => $this->valueExporter->exportValue($form->isSynchronized()), 'synchronized' => $this->cloneVar($form->isSynchronized()),
'passed_options' => array(), 'passed_options' => array(),
'resolved_options' => array(), 'resolved_options' => array(),
); );
foreach ($form->getConfig()->getAttribute('data_collector/passed_options', array()) as $option => $value) { foreach ($form->getConfig()->getAttribute('data_collector/passed_options', array()) as $option => $value) {
$data['passed_options'][$option] = $this->valueExporter->exportValue($value); $data['passed_options'][$option] = $this->cloneVar($value);
} }
foreach ($form->getConfig()->getOptions() as $option => $value) { foreach ($form->getConfig()->getOptions() as $option => $value) {
$data['resolved_options'][$option] = $this->valueExporter->exportValue($value); $data['resolved_options'][$option] = $this->cloneVar($value);
} }
ksort($data['passed_options']); ksort($data['passed_options']);
@ -65,17 +79,17 @@ class FormDataExtractor implements FormDataExtractorInterface
{ {
$data = array( $data = array(
'default_data' => array( 'default_data' => array(
'norm' => $this->valueExporter->exportValue($form->getNormData()), 'norm' => $this->cloneVar($form->getNormData()),
), ),
'submitted_data' => array(), 'submitted_data' => array(),
); );
if ($form->getData() !== $form->getNormData()) { if ($form->getData() !== $form->getNormData()) {
$data['default_data']['model'] = $this->valueExporter->exportValue($form->getData()); $data['default_data']['model'] = $this->cloneVar($form->getData());
} }
if ($form->getViewData() !== $form->getNormData()) { if ($form->getViewData() !== $form->getNormData()) {
$data['default_data']['view'] = $this->valueExporter->exportValue($form->getViewData()); $data['default_data']['view'] = $this->cloneVar($form->getViewData());
} }
return $data; return $data;
@ -88,17 +102,17 @@ class FormDataExtractor implements FormDataExtractorInterface
{ {
$data = array( $data = array(
'submitted_data' => array( 'submitted_data' => array(
'norm' => $this->valueExporter->exportValue($form->getNormData()), 'norm' => $this->cloneVar($form->getNormData()),
), ),
'errors' => array(), 'errors' => array(),
); );
if ($form->getViewData() !== $form->getNormData()) { if ($form->getViewData() !== $form->getNormData()) {
$data['submitted_data']['view'] = $this->valueExporter->exportValue($form->getViewData()); $data['submitted_data']['view'] = $this->cloneVar($form->getViewData());
} }
if ($form->getData() !== $form->getNormData()) { if ($form->getData() !== $form->getNormData()) {
$data['submitted_data']['model'] = $this->valueExporter->exportValue($form->getData()); $data['submitted_data']['model'] = $this->cloneVar($form->getData());
} }
foreach ($form->getErrors() as $error) { foreach ($form->getErrors() as $error) {
@ -114,24 +128,14 @@ class FormDataExtractor implements FormDataExtractorInterface
while (null !== $cause) { while (null !== $cause) {
if ($cause instanceof ConstraintViolationInterface) { if ($cause instanceof ConstraintViolationInterface) {
$errorData['trace'][] = array( $errorData['trace'][] = $cause;
'class' => $this->valueExporter->exportValue(get_class($cause)),
'root' => $this->valueExporter->exportValue($cause->getRoot()),
'path' => $this->valueExporter->exportValue($cause->getPropertyPath()),
'value' => $this->valueExporter->exportValue($cause->getInvalidValue()),
);
$cause = method_exists($cause, 'getCause') ? $cause->getCause() : null; $cause = method_exists($cause, 'getCause') ? $cause->getCause() : null;
continue; continue;
} }
if ($cause instanceof \Exception) { if ($cause instanceof \Exception) {
$errorData['trace'][] = array( $errorData['trace'][] = $cause;
'class' => $this->valueExporter->exportValue(get_class($cause)),
'message' => $this->valueExporter->exportValue($cause->getMessage()),
);
$cause = $cause->getPrevious(); $cause = $cause->getPrevious();
continue; continue;
@ -142,10 +146,13 @@ class FormDataExtractor implements FormDataExtractorInterface
break; break;
} }
if ($errorData['trace']) {
$errorData['trace'] = $this->cloneVar($errorData['trace']);
}
$data['errors'][] = $errorData; $data['errors'][] = $errorData;
} }
$data['synchronized'] = $this->valueExporter->exportValue($form->isSynchronized()); $data['synchronized'] = $this->cloneVar($form->isSynchronized());
return $data; return $data;
} }
@ -168,7 +175,7 @@ class FormDataExtractor implements FormDataExtractorInterface
} }
foreach ($view->vars as $varName => $value) { foreach ($view->vars as $varName => $value) {
$data['view_vars'][$varName] = $this->valueExporter->exportValue($value); $data['view_vars'][$varName] = $this->cloneVar($value);
} }
ksort($data['view_vars']); ksort($data['view_vars']);
@ -193,4 +200,46 @@ class FormDataExtractor implements FormDataExtractorInterface
return $id; return $id;
} }
/**
* Converts the variable into a serializable Data instance.
*
* @param mixed $var
*
* @return Data
*/
private function cloneVar($var)
{
if (null === $this->cloner) {
$this->cloner = new VarCloner();
$this->cloner->addCasters(array(
Stub::class => function (Stub $v, array $a, Stub $s, $isNested) {
return $isNested ? $a : StubCaster::castStub($v, $a, $s, true);
},
\Exception::class => function (\Exception $e, array $a, Stub $s) {
if (isset($a[$k = "\0Exception\0previous"])) {
unset($a[$k]);
++$s->cut;
}
return $a;
},
FormInterface::class => function (FormInterface $f, array $a) {
return array(
Caster::PREFIX_VIRTUAL.'name' => $f->getName(),
Caster::PREFIX_VIRTUAL.'type_class' => new ClassStub(get_class($f->getConfig()->getType()->getInnerType())),
);
},
ConstraintViolationInterface::class => function (ConstraintViolationInterface $v, array $a) {
return array(
Caster::PREFIX_VIRTUAL.'root' => $v->getRoot(),
Caster::PREFIX_VIRTUAL.'path' => $v->getPropertyPath(),
Caster::PREFIX_VIRTUAL.'value' => $v->getInvalidValue(),
);
},
));
}
return $this->cloner->cloneVar($var);
}
} }

View File

@ -18,28 +18,17 @@ use Symfony\Component\Form\FormBuilder;
use Symfony\Component\Form\FormError; use Symfony\Component\Form\FormError;
use Symfony\Component\Form\FormView; use Symfony\Component\Form\FormView;
use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer; use Symfony\Component\Form\Tests\Fixtures\FixedDataTransformer;
use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; use Symfony\Component\Validator\ConstraintViolation;
use Symfony\Component\VarDumper\Cloner\Data;
class FormDataExtractorTest_SimpleValueExporter extends ValueExporter use Symfony\Component\VarDumper\Dumper\CliDumper;
{ use Symfony\Component\VarDumper\Test\VarDumperTestTrait;
/**
* {@inheritdoc}
*/
public function exportValue($value, $depth = 1, $deep = false)
{
return is_object($value) ? sprintf('object(%s)', get_class($value)) : var_export($value, true);
}
}
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
*/ */
class FormDataExtractorTest extends \PHPUnit_Framework_TestCase class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
{ {
/** use VarDumperTestTrait;
* @var FormDataExtractorTest_SimpleValueExporter
*/
private $valueExporter;
/** /**
* @var FormDataExtractor * @var FormDataExtractor
@ -58,8 +47,7 @@ class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
protected function setUp() protected function setUp()
{ {
$this->valueExporter = new FormDataExtractorTest_SimpleValueExporter(); $this->dataExtractor = new FormDataExtractor();
$this->dataExtractor = new FormDataExtractor($this->valueExporter);
$this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); $this->dispatcher = $this->getMock('Symfony\Component\EventDispatcher\EventDispatcherInterface');
$this->factory = $this->getMock('Symfony\Component\Form\FormFactoryInterface'); $this->factory = $this->getMock('Symfony\Component\Form\FormFactoryInterface');
} }
@ -78,11 +66,11 @@ class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array( $this->assertSame(array(
'id' => 'name', 'id' => 'name',
'name' => 'name', 'name' => 'name',
'type_class' => 'stdClass', 'type_class' => '"stdClass"',
'synchronized' => 'true', 'synchronized' => 'true',
'passed_options' => array(), 'passed_options' => array(),
'resolved_options' => array(), 'resolved_options' => array(),
), $this->dataExtractor->extractConfiguration($form)); ), $this->inlineData($this->dataExtractor->extractConfiguration($form)));
} }
public function testExtractConfigurationSortsPassedOptions() public function testExtractConfigurationSortsPassedOptions()
@ -108,15 +96,15 @@ class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array( $this->assertSame(array(
'id' => 'name', 'id' => 'name',
'name' => 'name', 'name' => 'name',
'type_class' => 'stdClass', 'type_class' => '"stdClass"',
'synchronized' => 'true', 'synchronized' => 'true',
'passed_options' => array( 'passed_options' => array(
'a' => "'bar'", 'a' => '"bar"',
'b' => "'foo'", 'b' => '"foo"',
'c' => "'baz'", 'c' => '"baz"',
), ),
'resolved_options' => array(), 'resolved_options' => array(),
), $this->dataExtractor->extractConfiguration($form)); ), $this->inlineData($this->dataExtractor->extractConfiguration($form)));
} }
public function testExtractConfigurationSortsResolvedOptions() public function testExtractConfigurationSortsResolvedOptions()
@ -139,15 +127,15 @@ class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array( $this->assertSame(array(
'id' => 'name', 'id' => 'name',
'name' => 'name', 'name' => 'name',
'type_class' => 'stdClass', 'type_class' => '"stdClass"',
'synchronized' => 'true', 'synchronized' => 'true',
'passed_options' => array(), 'passed_options' => array(),
'resolved_options' => array( 'resolved_options' => array(
'a' => "'bar'", 'a' => '"bar"',
'b' => "'foo'", 'b' => '"foo"',
'c' => "'baz'", 'c' => '"baz"',
), ),
), $this->dataExtractor->extractConfiguration($form)); ), $this->inlineData($this->dataExtractor->extractConfiguration($form)));
} }
public function testExtractConfigurationBuildsIdRecursively() public function testExtractConfigurationBuildsIdRecursively()
@ -175,11 +163,11 @@ class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array( $this->assertSame(array(
'id' => 'grandParent_parent_name', 'id' => 'grandParent_parent_name',
'name' => 'name', 'name' => 'name',
'type_class' => 'stdClass', 'type_class' => '"stdClass"',
'synchronized' => 'true', 'synchronized' => 'true',
'passed_options' => array(), 'passed_options' => array(),
'resolved_options' => array(), 'resolved_options' => array(),
), $this->dataExtractor->extractConfiguration($form)); ), $this->inlineData($this->dataExtractor->extractConfiguration($form)));
} }
public function testExtractDefaultData() public function testExtractDefaultData()
@ -190,10 +178,10 @@ class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array( $this->assertSame(array(
'default_data' => array( 'default_data' => array(
'norm' => "'Foobar'", 'norm' => '"Foobar"',
), ),
'submitted_data' => array(), 'submitted_data' => array(),
), $this->dataExtractor->extractDefaultData($form)); ), $this->inlineData($this->dataExtractor->extractDefaultData($form)));
} }
public function testExtractDefaultDataStoresModelDataIfDifferent() public function testExtractDefaultDataStoresModelDataIfDifferent()
@ -208,11 +196,11 @@ class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array( $this->assertSame(array(
'default_data' => array( 'default_data' => array(
'norm' => "'Bar'", 'norm' => '"Bar"',
'model' => "'Foo'", 'model' => '"Foo"',
), ),
'submitted_data' => array(), 'submitted_data' => array(),
), $this->dataExtractor->extractDefaultData($form)); ), $this->inlineData($this->dataExtractor->extractDefaultData($form)));
} }
public function testExtractDefaultDataStoresViewDataIfDifferent() public function testExtractDefaultDataStoresViewDataIfDifferent()
@ -227,11 +215,11 @@ class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array( $this->assertSame(array(
'default_data' => array( 'default_data' => array(
'norm' => "'Foo'", 'norm' => '"Foo"',
'view' => "'Bar'", 'view' => '"Bar"',
), ),
'submitted_data' => array(), 'submitted_data' => array(),
), $this->dataExtractor->extractDefaultData($form)); ), $this->inlineData($this->dataExtractor->extractDefaultData($form)));
} }
public function testExtractSubmittedData() public function testExtractSubmittedData()
@ -242,11 +230,11 @@ class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array( $this->assertSame(array(
'submitted_data' => array( 'submitted_data' => array(
'norm' => "'Foobar'", 'norm' => '"Foobar"',
), ),
'errors' => array(), 'errors' => array(),
'synchronized' => 'true', 'synchronized' => 'true',
), $this->dataExtractor->extractSubmittedData($form)); ), $this->inlineData($this->dataExtractor->extractSubmittedData($form)));
} }
public function testExtractSubmittedDataStoresModelDataIfDifferent() public function testExtractSubmittedDataStoresModelDataIfDifferent()
@ -262,12 +250,12 @@ class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array( $this->assertSame(array(
'submitted_data' => array( 'submitted_data' => array(
'norm' => "'Bar'", 'norm' => '"Bar"',
'model' => "'Foo'", 'model' => '"Foo"',
), ),
'errors' => array(), 'errors' => array(),
'synchronized' => 'true', 'synchronized' => 'true',
), $this->dataExtractor->extractSubmittedData($form)); ), $this->inlineData($this->dataExtractor->extractSubmittedData($form)));
} }
public function testExtractSubmittedDataStoresViewDataIfDifferent() public function testExtractSubmittedDataStoresViewDataIfDifferent()
@ -283,12 +271,12 @@ class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array( $this->assertSame(array(
'submitted_data' => array( 'submitted_data' => array(
'norm' => "'Foo'", 'norm' => '"Foo"',
'view' => "'Bar'", 'view' => '"Bar"',
), ),
'errors' => array(), 'errors' => array(),
'synchronized' => 'true', 'synchronized' => 'true',
), $this->dataExtractor->extractSubmittedData($form)); ), $this->inlineData($this->dataExtractor->extractSubmittedData($form)));
} }
public function testExtractSubmittedDataStoresErrors() public function testExtractSubmittedDataStoresErrors()
@ -300,13 +288,13 @@ class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array( $this->assertSame(array(
'submitted_data' => array( 'submitted_data' => array(
'norm' => "'Foobar'", 'norm' => '"Foobar"',
), ),
'errors' => array( 'errors' => array(
array('message' => 'Invalid!', 'origin' => spl_object_hash($form), 'trace' => array()), array('message' => 'Invalid!', 'origin' => spl_object_hash($form), 'trace' => array()),
), ),
'synchronized' => 'true', 'synchronized' => 'true',
), $this->dataExtractor->extractSubmittedData($form)); ), $this->inlineData($this->dataExtractor->extractSubmittedData($form)));
} }
public function testExtractSubmittedDataStoresErrorOrigin() public function testExtractSubmittedDataStoresErrorOrigin()
@ -321,13 +309,13 @@ class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array( $this->assertSame(array(
'submitted_data' => array( 'submitted_data' => array(
'norm' => "'Foobar'", 'norm' => '"Foobar"',
), ),
'errors' => array( 'errors' => array(
array('message' => 'Invalid!', 'origin' => spl_object_hash($form), 'trace' => array()), array('message' => 'Invalid!', 'origin' => spl_object_hash($form), 'trace' => array()),
), ),
'synchronized' => 'true', 'synchronized' => 'true',
), $this->dataExtractor->extractSubmittedData($form)); ), $this->inlineData($this->dataExtractor->extractSubmittedData($form)));
} }
public function testExtractSubmittedDataStoresErrorCause() public function testExtractSubmittedDataStoresErrorCause()
@ -335,24 +323,39 @@ class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
$form = $this->createBuilder('name')->getForm(); $form = $this->createBuilder('name')->getForm();
$exception = new \Exception(); $exception = new \Exception();
$violation = new ConstraintViolation('Foo', 'Foo', array(), 'Root', 'property.path', 'Invalid!', null, null, null, $exception);
$form->submit('Foobar'); $form->submit('Foobar');
$form->addError(new FormError('Invalid!', null, array(), null, $exception)); $form->addError(new FormError('Invalid!', null, array(), null, $violation));
$origin = spl_object_hash($form);
$this->assertSame(array( $this->assertDumpMatchesFormat(<<<EODUMP
'submitted_data' => array( array:3 [
'norm' => "'Foobar'", "submitted_data" => array:1 [
), "norm" => ""Foobar""
'errors' => array( ]
array('message' => 'Invalid!', 'origin' => spl_object_hash($form), 'trace' => array( "errors" => array:1 [
array( 0 => array:3 [
'class' => "'Exception'", "message" => "Invalid!"
'message' => "''", "origin" => "$origin"
), "trace" => """
)), array:2 [\\n
), 0 => Symfony\Component\Validator\ConstraintViolation {\\n
'synchronized' => 'true', root: "Root"\\n
), $this->dataExtractor->extractSubmittedData($form)); path: "property.path"\\n
value: "Invalid!"\\n
}\\n
1 => Exception {%A}\\n
]
"""
]
]
"synchronized" => "true"
]
EODUMP
,
$this->inlineData($this->dataExtractor->extractSubmittedData($form))
);
} }
public function testExtractSubmittedDataRemembersIfNonSynchronized() public function testExtractSubmittedDataRemembersIfNonSynchronized()
@ -370,12 +373,12 @@ class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
$this->assertSame(array( $this->assertSame(array(
'submitted_data' => array( 'submitted_data' => array(
'norm' => "'Foobar'", 'norm' => '"Foobar"',
'model' => 'NULL', 'model' => 'null',
), ),
'errors' => array(), 'errors' => array(),
'synchronized' => 'false', 'synchronized' => 'false',
), $this->dataExtractor->extractSubmittedData($form)); ), $this->inlineData($this->dataExtractor->extractSubmittedData($form)));
} }
public function testExtractViewVariables() public function testExtractViewVariables()
@ -394,13 +397,31 @@ class FormDataExtractorTest extends \PHPUnit_Framework_TestCase
'id' => 'foo_bar', 'id' => 'foo_bar',
'name' => 'bar', 'name' => 'bar',
'view_vars' => array( 'view_vars' => array(
'a' => "'bar'", 'a' => '"bar"',
'b' => "'foo'", 'b' => '"foo"',
'c' => "'baz'", 'c' => '"baz"',
'id' => "'foo_bar'", 'id' => '"foo_bar"',
'name' => "'bar'", 'name' => '"bar"',
), ),
), $this->dataExtractor->extractViewVariables($view)); ), $this->inlineData($this->dataExtractor->extractViewVariables($view)));
}
private function inlineData(array $extraction)
{
$dumper = new CliDumper();
$inlined = array();
foreach ($extraction as $k => $v) {
if (is_array($v)) {
$inlined[$k] = $this->inlineData($v);
} elseif ($v instanceof Data) {
$inlined[$k] = rtrim($dumper->dump($v->withRefHandles(false), true));
} else {
$inlined[$k] = $v;
}
}
return $inlined;
} }
/** /**

View File

@ -30,7 +30,8 @@
"symfony/http-foundation": "~2.8|~3.0", "symfony/http-foundation": "~2.8|~3.0",
"symfony/http-kernel": "~2.8|~3.0", "symfony/http-kernel": "~2.8|~3.0",
"symfony/security-csrf": "~2.8|~3.0", "symfony/security-csrf": "~2.8|~3.0",
"symfony/translation": "~2.8|~3.0" "symfony/translation": "~2.8|~3.0",
"symfony/var-dumper": "~3.2"
}, },
"conflict": { "conflict": {
"symfony/doctrine-bridge": "<2.7", "symfony/doctrine-bridge": "<2.7",

View File

@ -1,6 +1,11 @@
CHANGELOG CHANGELOG
========= =========
3.2.0
-----
* deprecated `DataCollector::varToString()`, use `cloneVar()` instead
3.1.0 3.1.0
----- -----
* deprecated passing objects as URI attributes to the ESI and SSI renderers * deprecated passing objects as URI attributes to the ESI and SSI renderers

View File

@ -12,6 +12,13 @@
namespace Symfony\Component\HttpKernel\DataCollector; namespace Symfony\Component\HttpKernel\DataCollector;
use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter;
use Symfony\Component\VarDumper\Caster\ClassStub;
use Symfony\Component\VarDumper\Caster\LinkStub;
use Symfony\Component\VarDumper\Caster\StubCaster;
use Symfony\Component\VarDumper\Cloner\ClonerInterface;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\Stub;
use Symfony\Component\VarDumper\Cloner\VarCloner;
/** /**
* DataCollector. * DataCollector.
@ -30,6 +37,11 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable
*/ */
private $valueExporter; private $valueExporter;
/**
* @var ClonerInterface
*/
private $cloner;
public function serialize() public function serialize()
{ {
return serialize($this->data); return serialize($this->data);
@ -40,19 +52,88 @@ abstract class DataCollector implements DataCollectorInterface, \Serializable
$this->data = unserialize($data); $this->data = unserialize($data);
} }
/**
* Converts the variable into a serializable Data instance.
*
* This array can be displayed in the template using
* the VarDumper component.
*
* @param mixed $var
*
* @return Data
*/
protected function cloneVar($var)
{
if (null === $this->cloner) {
if (class_exists(ClassStub::class)) {
$this->cloner = new VarCloner();
$this->cloner->addCasters(array(
Stub::class => function (Stub $v, array $a, Stub $s, $isNested) {
return $isNested ? $a : StubCaster::castStub($v, $a, $s, true);
},
));
} else {
@trigger_error(sprintf('Using the %s() method without the VarDumper component is deprecated since version 3.2 and won\'t be supported in 4.0. Install symfony/var-dumper version 3.2 or above.', __METHOD__), E_USER_DEPRECATED);
$this->cloner = false;
}
}
if (false === $this->cloner) {
if (null === $this->valueExporter) {
$this->valueExporter = new ValueExporter();
}
return $this->valueExporter->exportValue($var);
}
return $this->cloner->cloneVar($this->decorateVar($var));
}
/** /**
* Converts a PHP variable to a string. * Converts a PHP variable to a string.
* *
* @param mixed $var A PHP variable * @param mixed $var A PHP variable
* *
* @return string The string representation of the variable * @return string The string representation of the variable
*
* @deprecated Deprecated since version 3.2, to be removed in 4.0. Use cloneVar() instead.
*/ */
protected function varToString($var) protected function varToString($var)
{ {
@trigger_error(sprintf('The %() method is deprecated since version 3.2 and will be removed in 4.0. Use cloneVar() instead.', __METHOD__), E_USER_DEPRECATED);
if (null === $this->valueExporter) { if (null === $this->valueExporter) {
$this->valueExporter = new ValueExporter(); $this->valueExporter = new ValueExporter();
} }
return $this->valueExporter->exportValue($var); return $this->valueExporter->exportValue($var);
} }
private function decorateVar($var)
{
if (is_array($var)) {
if (isset($var[0], $var[1]) && is_callable($var)) {
return ClassStub::wrapCallable($var);
}
foreach ($var as $k => $v) {
if ($v !== $d = $this->decorateVar($v)) {
$var[$k] = $d;
}
}
return $var;
}
if (is_string($var)) {
if (false !== strpos($var, '\\')) {
$c = (false !== $i = strpos($var, '::')) ? substr($var, 0, $i) : $var;
if (class_exists($c, false) || interface_exists($c, false) || trait_exists($c, false)) {
return new ClassStub($var);
}
}
if (false !== strpos($var, DIRECTORY_SEPARATOR) && file_exists($var)) {
return new LinkStub($var);
}
}
return $var;
}
} }

View File

@ -52,17 +52,16 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
// attributes are serialized and as they can be anything, they need to be converted to strings. // attributes are serialized and as they can be anything, they need to be converted to strings.
$attributes = array(); $attributes = array();
$route = '';
foreach ($request->attributes->all() as $key => $value) { foreach ($request->attributes->all() as $key => $value) {
if ('_route' === $key && is_object($value)) { if ('_route' === $key && is_object($value)) {
$attributes[$key] = $this->varToString($value->getPath()); $attributes[$key] = $this->cloneVar($value->getPath());
} elseif ('_route_params' === $key) {
// we need to keep route params as an array (see getRouteParams())
foreach ($value as $k => $v) {
$value[$k] = $this->varToString($v);
}
$attributes[$key] = $value;
} else { } else {
$attributes[$key] = $this->varToString($value); $attributes[$key] = $this->cloneVar($value);
}
if ('_route' === $key) {
$route = is_object($value) ? $value->getPath() : $value;
} }
} }
@ -98,12 +97,13 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
'content_type' => $response->headers->get('Content-Type', 'text/html'), 'content_type' => $response->headers->get('Content-Type', 'text/html'),
'status_text' => isset(Response::$statusTexts[$statusCode]) ? Response::$statusTexts[$statusCode] : '', 'status_text' => isset(Response::$statusTexts[$statusCode]) ? Response::$statusTexts[$statusCode] : '',
'status_code' => $statusCode, 'status_code' => $statusCode,
'request_query' => $request->query->all(), 'request_query' => array_map(array($this, 'cloneVar'), $request->query->all()),
'request_request' => $request->request->all(), 'request_request' => array_map(array($this, 'cloneVar'), $request->request->all()),
'request_headers' => $request->headers->all(), 'request_headers' => $request->headers->all(),
'request_server' => $request->server->all(), 'request_server' => $request->server->all(),
'request_cookies' => $request->cookies->all(), 'request_cookies' => $request->cookies->all(),
'request_attributes' => $attributes, 'request_attributes' => $attributes,
'route' => $route,
'response_headers' => $responseHeaders, 'response_headers' => $responseHeaders,
'session_metadata' => $sessionMetadata, 'session_metadata' => $sessionMetadata,
'session_attributes' => $sessionAttributes, 'session_attributes' => $sessionAttributes,
@ -247,7 +247,12 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
*/ */
public function getRoute() public function getRoute()
{ {
return isset($this->data['request_attributes']['_route']) ? $this->data['request_attributes']['_route'] : ''; return $this->data['route'];
}
public function getIdentifier()
{
return $this->data['route'] ?: (is_array($this->data['controller']) ? $this->data['controller']['class'].'::'.$this->data['controller']['method'].'()' : $this->data['controller']);
} }
/** /**
@ -259,7 +264,7 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
*/ */
public function getRouteParams() public function getRouteParams()
{ {
return isset($this->data['request_attributes']['_route_params']) ? $this->data['request_attributes']['_route_params'] : array(); return isset($this->data['request_attributes']['_route_params']) ? $this->data['request_attributes']['_route_params'] : $this->cloneVar(array());
} }
/** /**

View File

@ -11,8 +11,12 @@
namespace Symfony\Component\HttpKernel\DataCollector\Util; namespace Symfony\Component\HttpKernel\DataCollector\Util;
@trigger_error('The '.__NAMESPACE__.'\ValueExporter class is deprecated since version 3.2 and will be removed in 4.0. Use the VarDumper component instead.', E_USER_DEPRECATED);
/** /**
* @author Bernhard Schussek <bschussek@gmail.com> * @author Bernhard Schussek <bschussek@gmail.com>
*
* @deprecated since version 3.2, to be removed in 4.0. Use the VarDumper component instead.
*/ */
class ValueExporter class ValueExporter
{ {

View File

@ -82,7 +82,7 @@ class DumpDataCollectorTest extends \PHPUnit_Framework_TestCase
$line = __LINE__ - 1; $line = __LINE__ - 1;
$file = __FILE__; $file = __FILE__;
$xOutput = <<<EOTXT $xOutput = <<<EOTXT
<pre class=sf-dump id=sf-dump data-indent-pad=" "><a href="test://{$file}:{$line}" title="{$file}"><span class=sf-dump-meta>DumpDataCollectorTest.php</span></a> on line <span class=sf-dump-meta>{$line}</span>: <pre class=sf-dump id=sf-dump data-indent-pad=" "><a href="test://{$file}:{$line}" title="{$file}"><span class=sf-dump-meta>DumpDataCollectorTest.php</span></a> on line <span class=sf-dump-meta>{$line}</span>:
<span class=sf-dump-num>123</span> <span class=sf-dump-num>123</span>
</pre> </pre>

View File

@ -23,6 +23,8 @@ use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\Cookie; use Symfony\Component\HttpFoundation\Cookie;
use Symfony\Component\EventDispatcher\EventDispatcher; use Symfony\Component\EventDispatcher\EventDispatcher;
use Symfony\Component\VarDumper\Cloner\Data;
use Symfony\Component\VarDumper\Cloner\VarCloner;
class RequestDataCollectorTest extends \PHPUnit_Framework_TestCase class RequestDataCollectorTest extends \PHPUnit_Framework_TestCase
{ {
@ -30,8 +32,9 @@ class RequestDataCollectorTest extends \PHPUnit_Framework_TestCase
{ {
$c = new RequestDataCollector(); $c = new RequestDataCollector();
$c->collect($this->createRequest(), $this->createResponse()); $c->collect($request = $this->createRequest(), $this->createResponse());
$cloner = new VarCloner();
$attributes = $c->getRequestAttributes(); $attributes = $c->getRequestAttributes();
$this->assertSame('request', $c->getName()); $this->assertSame('request', $c->getName());
@ -42,12 +45,12 @@ class RequestDataCollectorTest extends \PHPUnit_Framework_TestCase
$this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestRequest()); $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestRequest());
$this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestQuery()); $this->assertInstanceOf('Symfony\Component\HttpFoundation\ParameterBag', $c->getRequestQuery());
$this->assertSame('html', $c->getFormat()); $this->assertSame('html', $c->getFormat());
$this->assertSame('foobar', $c->getRoute()); $this->assertEquals('foobar', $c->getRoute());
$this->assertSame(array('name' => 'foo'), $c->getRouteParams()); $this->assertEquals($cloner->cloneVar(array('name' => 'foo')), $c->getRouteParams());
$this->assertSame(array(), $c->getSessionAttributes()); $this->assertSame(array(), $c->getSessionAttributes());
$this->assertSame('en', $c->getLocale()); $this->assertSame('en', $c->getLocale());
$this->assertRegExp('/Resource\(stream#\d+\)/', $attributes->get('resource')); $this->assertEquals($cloner->cloneVar($request->attributes->get('resource')), $attributes->get('resource'));
$this->assertSame('Object(stdClass)', $attributes->get('object')); $this->assertEquals($cloner->cloneVar($request->attributes->get('object')), $attributes->get('object'));
$this->assertInstanceOf('Symfony\Component\HttpFoundation\HeaderBag', $c->getResponseHeaders()); $this->assertInstanceOf('Symfony\Component\HttpFoundation\HeaderBag', $c->getResponseHeaders());
$this->assertSame('OK', $c->getStatusText()); $this->assertSame('OK', $c->getStatusText());

View File

@ -13,6 +13,9 @@ namespace Symfony\Component\HttpKernel\Tests\DataCollector\Util;
use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter; use Symfony\Component\HttpKernel\DataCollector\Util\ValueExporter;
/**
* @group legacy
*/
class ValueExporterTest extends \PHPUnit_Framework_TestCase class ValueExporterTest extends \PHPUnit_Framework_TestCase
{ {
/** /**

View File

@ -16,6 +16,7 @@ use Symfony\Component\HttpKernel\Profiler\FileProfilerStorage;
use Symfony\Component\HttpKernel\Profiler\Profiler; use Symfony\Component\HttpKernel\Profiler\Profiler;
use Symfony\Component\HttpFoundation\Request; use Symfony\Component\HttpFoundation\Request;
use Symfony\Component\HttpFoundation\Response; use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\VarDumper\Cloner\Data;
class ProfilerTest extends \PHPUnit_Framework_TestCase class ProfilerTest extends \PHPUnit_Framework_TestCase
{ {
@ -35,7 +36,7 @@ class ProfilerTest extends \PHPUnit_Framework_TestCase
$this->assertSame(204, $profile->getStatusCode()); $this->assertSame(204, $profile->getStatusCode());
$this->assertSame('GET', $profile->getMethod()); $this->assertSame('GET', $profile->getMethod());
$this->assertEquals(array('foo' => 'bar'), $profiler->get('request')->getRequestQuery()->all()); $this->assertInstanceOf(Data::class, $profiler->get('request')->getRequestQuery()->all()['foo']);
} }
public function testFindWorksWithDates() public function testFindWorksWithDates()

View File

@ -11,7 +11,6 @@
namespace Symfony\Component\Security\Core\Authorization; namespace Symfony\Component\Security\Core\Authorization;
use Doctrine\Common\Util\ClassUtils;
use Symfony\Component\Security\Core\Authentication\Token\TokenInterface; use Symfony\Component\Security\Core\Authentication\Token\TokenInterface;
/** /**
@ -50,7 +49,7 @@ class DebugAccessDecisionManager implements AccessDecisionManagerInterface
$this->decisionLog[] = array( $this->decisionLog[] = array(
'attributes' => $attributes, 'attributes' => $attributes,
'object' => $this->getStringRepresentation($object), 'object' => $object,
'result' => $result, 'result' => $result,
); );
@ -96,39 +95,4 @@ class DebugAccessDecisionManager implements AccessDecisionManagerInterface
{ {
return $this->decisionLog; return $this->decisionLog;
} }
/**
* @param mixed $object
*
* @return string
*/
private function getStringRepresentation($object)
{
if (null === $object) {
return 'NULL';
}
if (!is_object($object)) {
if (is_bool($object)) {
return sprintf('%s (%s)', gettype($object), $object ? 'true' : 'false');
}
if (is_scalar($object)) {
return sprintf('%s (%s)', gettype($object), $object);
}
return gettype($object);
}
$objectClass = class_exists('Doctrine\Common\Util\ClassUtils') ? ClassUtils::getClass($object) : get_class($object);
if (method_exists($object, 'getId')) {
$objectAsString = sprintf('ID: %s', $object->getId());
} elseif (method_exists($object, '__toString')) {
$objectAsString = (string) $object;
} else {
$objectAsString = sprintf('object hash: %s', spl_object_hash($object));
}
return sprintf('%s (%s)', $objectClass, $objectAsString);
}
} }

View File

@ -32,12 +32,12 @@ class DebugAccessDecisionManagerTest extends \PHPUnit_Framework_TestCase
{ {
$object = new \stdClass(); $object = new \stdClass();
yield array(array(array('attributes' => array('ATTRIBUTE_1'), 'object' => 'NULL', 'result' => false)), null); yield array(array(array('attributes' => array('ATTRIBUTE_1'), 'object' => null, 'result' => false)), null);
yield array(array(array('attributes' => array('ATTRIBUTE_1'), 'object' => 'boolean (true)', 'result' => false)), true); yield array(array(array('attributes' => array('ATTRIBUTE_1'), 'object' => true, 'result' => false)), true);
yield array(array(array('attributes' => array('ATTRIBUTE_1'), 'object' => 'string (jolie string)', 'result' => false)), 'jolie string'); yield array(array(array('attributes' => array('ATTRIBUTE_1'), 'object' => 'jolie string', 'result' => false)), 'jolie string');
yield array(array(array('attributes' => array('ATTRIBUTE_1'), 'object' => 'integer (12345)', 'result' => false)), 12345); yield array(array(array('attributes' => array('ATTRIBUTE_1'), 'object' => 12345, 'result' => false)), 12345);
yield array(array(array('attributes' => array('ATTRIBUTE_1'), 'object' => 'resource', 'result' => false)), fopen(__FILE__, 'r')); yield array(array(array('attributes' => array('ATTRIBUTE_1'), 'object' => $x = fopen(__FILE__, 'r'), 'result' => false)), $x);
yield array(array(array('attributes' => array('ATTRIBUTE_1'), 'object' => 'array', 'result' => false)), array()); yield array(array(array('attributes' => array('ATTRIBUTE_1'), 'object' => $x = array(), 'result' => false)), $x);
yield array(array(array('attributes' => array('ATTRIBUTE_1'), 'object' => sprintf('stdClass (object hash: %s)', spl_object_hash($object)), 'result' => false)), $object); yield array(array(array('attributes' => array('ATTRIBUTE_1'), 'object' => $object, 'result' => false)), $object);
} }
} }

View File

@ -363,8 +363,7 @@ return function (root, x) {
}; };
})(document); })(document);
</script> </script><style>
<style>
pre.sf-dump { pre.sf-dump {
display: block; display: block;
white-space: pre; white-space: pre;