[WebProfiler] [HttpKernel] profile redirections

closes #17501.

The profiler stores the current request attributes in a
current session when a `RedirectionResponse` is returned.
So the next request profile will inherit the previous request
attributes.

The main profiler layout displays a shortcut to a previous
redirection profile, along with some useful informations.

The web debug toolbar shows a notifying icon, meaning a shortcut
to a redirection profile is available in the request toolbar panel.
This commit is contained in:
Jules Pietri 2016-01-28 11:45:34 +01:00
parent 06eb52cb75
commit 0a1b2841f7
7 changed files with 228 additions and 90 deletions

View File

@ -2,59 +2,72 @@
{% block toolbar %}
{% set request_handler %}
{% if collector.controller.class is defined %}
{% set link = collector.controller.file|file_link(collector.controller.line) %}
{% if link %}<a href="{{ link }}" title="{{ collector.controller.file }}">{% else %}<span>{% endif %}
{{ collector.controller.class|abbr_class|striptags }}
{%- if collector.controller.method -%}
&nbsp;::&nbsp;{{ collector.controller.method }}
{%- endif -%}
{% if link %}</a>{% else %}</span>{% endif %}
{% else %}
<span>{{ collector.controller }}</span>
{% endif %}
{% import _self as helper %}
{{ helper.set_handler(collector.controller) }}
{% endset %}
{% if collector.redirect %}
{% set redirect_handler %}
{% import _self as helper %}
{{ helper.set_handler(collector.redirect.controller, collector.redirect.route, 'GET' != collector.redirect.method ? collector.redirect.method) }}
{% endset %}
{% endif %}
{% set request_status_code_color = (collector.statuscode >= 400) ? 'red' : (collector.statuscode >= 300) ? 'yellow' : 'green' %}
{% set icon %}
<span class="sf-toolbar-status sf-toolbar-status-{{ request_status_code_color }}">{{ collector.statuscode }}</span>
{% if collector.route %}
{% if collector.redirect %}{{ include('@WebProfiler/Icon/redirect.svg') }}{% endif %}
<span class="sf-toolbar-label">@</span>
<span class="sf-toolbar-value sf-toolbar-info-piece-additional">{{ collector.route }}</span>
{% endif %}
{% endset %}
{% set text %}
<div class="sf-toolbar-info-piece">
<b>HTTP status</b>
<span>{{ collector.statuscode }} {{ collector.statustext }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Controller</b>
<span>{{ request_handler }}</span>
</div>
{% if collector.controller.class is defined %}
<div class="sf-toolbar-info-group">
<div class="sf-toolbar-info-piece">
<b>Controller class</b>
<span>{{ collector.controller.class }}</span>
<b>HTTP status</b>
<span>{{ collector.statuscode }} {{ collector.statustext }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Controller</b>
<span>{{ request_handler }}</span>
</div>
{% if collector.controller.class is defined -%}
<div class="sf-toolbar-info-piece">
<b>Controller class</b>
<span>{{ collector.controller.class }}</span>
</div>
{%- endif %}
<div class="sf-toolbar-info-piece">
<b>Route name</b>
<span>{{ collector.route|default('NONE') }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Has session</b>
<span>{% if collector.sessionmetadata|length %}yes{% else %}no{% endif %}</span>
</div>
</div>
{% if redirect_handler is defined -%}
<div class="sf-toolbar-info-group">
<div class="sf-toolbar-info-piece">
<b>
<span class="sf-toolbar-redirection-status sf-toolbar-status-yellow">{{ collector.redirect.status_code }}</span>
Redirect from
</b>
<span>
{{ redirect_handler }}
(<a href="{{ path('_profiler', { token: collector.redirect.token }) }}">{{ collector.redirect.token }}</a>)
</span>
</div>
</div>
{% endif %}
<div class="sf-toolbar-info-piece">
<b>Route name</b>
<span>{{ collector.route|default('NONE') }}</span>
</div>
<div class="sf-toolbar-info-piece">
<b>Has session</b>
<span>{% if collector.sessionmetadata|length %}yes{% else %}no{% endif %}</span>
</div>
{% endset %}
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url }) }}
@ -224,3 +237,22 @@
{% endif %}
</div>
{% endblock %}
{% macro set_handler(controller, route, method) %}
{% if controller.class is defined -%}
{%- if method|default(false) %}<span class="sf-toolbar-status sf-toolbar-redirection-method">{{ method }}</span>{% endif -%}
{%- set link = controller.file|file_link(controller.line) %}
{%- if link %}<a href="{{ link }}" title="{{ controller.file }}">{% else %}<span>{% endif %}
{%- if route|default(false) -%}
@{{ route }}
{%- else -%}
{{- controller.class|abbr_class|striptags -}}
{{- controller.method ? ' :: ' ~ controller.method -}}
{%- endif -%}
{%- if link %}</a>{% else %}</span>{% endif %}
{%- else -%}
<span>{{ route|default(controller) }}</span>
{%- endif %}
{% endmacro %}

View File

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24">
<path style="fill:#aaa" d="M23.06,7.83L14,0.38a1.25,1.25,0,0,0-2,.89V4.09a13.61,13.61,0,0,1-2.2.61l-1.3.47C8,
5.35,7.59,5.6,7.12,5.81l-0.69.35-0.72.45a10.62,10.62,0,0,0-1.41,1A13.22,13.22,0,0,0,3,8.82a15.31,15.31,
0,0,0-1.13,1.46A17.63,17.63,0,0,0,1,11.93c-0.18.58-.34,1.16-0.48,1.71S0.45,14.76.43,15.29a10.2,10.2,0,0,0,.16,
1.5,5.72,5.72,0,0,0,.33,1.34c0.14,0.41.26,0.82,0.42,1.19,0.37,0.71.67,1.38,1,1.94l1,1.46c0.32,0.41.63,0.75,0.87,
1s0.51,0.09.43-.22-0.23-.75-0.35-1.23L4,20.69c-0.1-.58-0.09-1.22-0.14-1.86,0-.32.05-0.65,0.08-1a3.44,3.44,0,0,1,
.16-1A6.44,6.44,0,0,1,4.41,16l0.41-.8c0.2-.22.38-0.44,0.55-0.65L6,14c0.23-.14.5-0.24,0.72-0.37a7.52,7.52,0,0,1,
.79-0.25,4.48,4.48,0,0,1,.84-0.15l0.41-.06H9.22c0.3,0,.56,0,0.85,0l0.72,0.07a3.77,3.77,0,0,1,1.2.21v3.17a1.25,
1.25,0,0,0,2,.89l9-7.45A1.46,1.46,0,0,0,23.06,7.83Z"/>
</svg>

After

Width:  |  Height:  |  Size: 980 B

View File

@ -19,6 +19,30 @@
{% endif %}
</h2>
{% if profile.collectors.request is defined and profile.collectors.request.redirect -%}
{%- set redirect = profile.collectors.request.redirect -%}
{%- set controller = redirect.controller -%}
{%- set redirect_route = '@' ~ redirect.route %}
<dl class="metadata">
<dt>
<span class="label">{{ redirect.status_code }}</span>
Redirect from
</dt>
<dd>
{{ 'GET' != redirect.method ? redirect.method }}
{% if redirect.controller.class is defined -%}
{%- set link = controller.file|file_link(controller.line) -%}
{% if link %}<a href="{{ link }}" title="{{ controller.file }}">{% endif -%}
{{ redirect_route }}
{%- if link %}</a>{% endif -%}
{%- else -%}
{{ redirect_route }}
{%- endif %}
(<a href="{{ path('_profiler', { token: redirect.token }) }}">{{ redirect.token }}</a>)
</dd>
</dl>
{%- endif %}
<dl class="metadata">
<dt>Method</dt>
<dd>{{ profile.method|upper }}</dd>

View File

@ -483,11 +483,11 @@ tr.status-warning td {
#summary .status-error { background: {{ colors.error|raw }}; }
#summary .status-success h2,
#summary .status-success h2 a,
#summary .status-success a,
#summary .status-warning h2,
#summary .status-warning h2 a,
#summary .status-warning a,
#summary .status-error h2,
#summary .status-error h2 a {
#summary .status-error a {
color: #FFF;
}
@ -510,6 +510,10 @@ tr.status-warning td {
margin: 0 1.5em 0 0;
}
#summary dl.metadata .label {
background: rgba(255, 255, 255, 0.2);
}
{# Sidebar
========================================================================= #}
#sidebar {

View File

@ -36,7 +36,7 @@
.sf-toolbarreset {
background-color: #222;
bottom: 0;
box-shadow: 0 -1px 0px rgba(0, 0, 0, 0.2);
box-shadow: 0 -1px 0 rgba(0, 0, 0, 0.2);
color: #EEE;
font: 11px Arial, sans-serif;
left: 0;
@ -138,7 +138,7 @@
.sf-toolbar-block .sf-toolbar-info-piece .sf-toolbar-status {
padding: 2px 5px;
margin-bottom: 0px;
margin-bottom: 0;
}
.sf-toolbar-block .sf-toolbar-info-piece .sf-toolbar-status + .sf-toolbar-status {
margin-left: 4px;
@ -232,6 +232,16 @@
.sf-toolbar-block-request .sf-toolbar-info-piece a:hover {
text-decoration: underline;
}
.sf-toolbar-block-request .sf-toolbar-redirection-status {
font-weight: normal;
padding: 2px 4px;
line-height: 18px;
}
.sf-toolbar-block-request .sf-toolbar-info-piece span.sf-toolbar-redirection-method {
font-size: 12px;
height: 17px;
line-height: 17px;
}
.sf-toolbar-status-green .sf-toolbar-label,
.sf-toolbar-status-yellow .sf-toolbar-label,
@ -380,7 +390,7 @@
.sf-toolbarreset {
bottom: auto;
box-shadow: 0 1px 0px rgba(0, 0, 0, 0.2);
box-shadow: 0 1px 0 rgba(0, 0, 0, 0.2);
top: 0;
}
@ -450,9 +460,12 @@
padding-left: 0;
padding-right: 0;
}
.sf-toolbar-block-request .sf-toolbar-status + .sf-toolbar-label {
margin-left: 4px;
.sf-toolbar-block-request .sf-toolbar-status {
margin-right: 5px;
}
.sf-toolbar-block-request .sf-toolbar-icon svg + .sf-toolbar-label {
margin-left: 0;
}
.sf-toolbar-block-request .sf-toolbar-label + .sf-toolbar-value {
margin-right: 10px;
}

View File

@ -122,48 +122,25 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
}
if (isset($this->controllers[$request])) {
$controller = $this->controllers[$request];
if (is_array($controller)) {
try {
$r = new \ReflectionMethod($controller[0], $controller[1]);
$this->data['controller'] = array(
'class' => is_object($controller[0]) ? get_class($controller[0]) : $controller[0],
'method' => $controller[1],
'file' => $r->getFileName(),
'line' => $r->getStartLine(),
);
} catch (\ReflectionException $e) {
if (is_callable($controller)) {
// using __call or __callStatic
$this->data['controller'] = array(
'class' => is_object($controller[0]) ? get_class($controller[0]) : $controller[0],
'method' => $controller[1],
'file' => 'n/a',
'line' => 'n/a',
);
}
}
} elseif ($controller instanceof \Closure) {
$r = new \ReflectionFunction($controller);
$this->data['controller'] = array(
'class' => $r->getName(),
'method' => null,
'file' => $r->getFileName(),
'line' => $r->getStartLine(),
);
} elseif (is_object($controller)) {
$r = new \ReflectionClass($controller);
$this->data['controller'] = array(
'class' => $r->getName(),
'method' => null,
'file' => $r->getFileName(),
'line' => $r->getStartLine(),
);
} else {
$this->data['controller'] = (string) $controller ?: 'n/a';
}
$this->data['controller'] = $this->parseController($this->controllers[$request]);
unset($this->controllers[$request]);
}
if ($request->hasSession() && $request->getSession()->has('sf_redirect')) {
$this->data['redirect'] = $request->getSession()->get('sf_redirect');
$request->getSession()->remove('sf_redirect');
}
if ($request->hasSession() && $response->isRedirect()) {
$request->getSession()->set('sf_redirect', array(
'token' => $response->headers->get('x-debug-token'),
'route' => $request->attributes->get('_route', 'n/a'),
'method' => $request->getMethod(),
'controller' => $this->parseController($request->attributes->get('_controller')),
'status_code' => $statusCode,
'status_text' => Response::$statusTexts[(int) $statusCode],
));
}
}
public function getPathInfo()
@ -276,15 +253,27 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
}
/**
* Gets the controller.
* Gets the parsed controller.
*
* @return string The controller as a string
* @return array|string The controller as a string or array of data
* with keys 'class', 'method', 'file' and 'line'
*/
public function getController()
{
return $this->data['controller'];
}
/**
* Gets the previous request attributes.
*
* @return array|bool A legacy array of data from the previous redirection response
* or false otherwise
*/
public function getRedirect()
{
return isset($this->data['redirect']) ? $this->data['redirect'] : false;
}
public function onKernelController(FilterControllerEvent $event)
{
$this->controllers[$event->getRequest()] = $event->getController();
@ -339,4 +328,65 @@ class RequestDataCollector extends DataCollector implements EventSubscriberInter
return $cookie;
}
/**
* Parse a controller.
*
* @param mixed $controller The controller to parse
*
* @return array|string An array of controller data or a simple string
*/
private function parseController($controller)
{
if (is_string($controller) && false !== strpos($controller, '::')) {
$controller = explode('::', $controller);
}
if (is_array($controller)) {
try {
$r = new \ReflectionMethod($controller[0], $controller[1]);
return array(
'class' => is_object($controller[0]) ? get_class($controller[0]) : $controller[0],
'method' => $controller[1],
'file' => $r->getFileName(),
'line' => $r->getStartLine(),
);
} catch (\ReflectionException $e) {
if (is_callable($controller)) {
// using __call or __callStatic
return array(
'class' => is_object($controller[0]) ? get_class($controller[0]) : $controller[0],
'method' => $controller[1],
'file' => 'n/a',
'line' => 'n/a',
);
}
}
}
if ($controller instanceof \Closure) {
$r = new \ReflectionFunction($controller);
return array(
'class' => $r->getName(),
'method' => null,
'file' => $r->getFileName(),
'line' => $r->getStartLine(),
);
}
if (is_object($controller)) {
$r = new \ReflectionClass($controller);
return array(
'class' => $r->getName(),
'method' => null,
'file' => $r->getFileName(),
'line' => $r->getStartLine(),
);
}
return (string) $controller ?: 'n/a';
}
}

View File

@ -66,7 +66,7 @@ class RequestDataCollectorTest extends \PHPUnit_Framework_TestCase
'"Regular" callable',
array($this, 'testControllerInspection'),
array(
'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest',
'class' => __NAMESPACE__.'\RequestDataCollectorTest',
'method' => 'testControllerInspection',
'file' => __FILE__,
'line' => $r1->getStartLine(),
@ -86,8 +86,13 @@ class RequestDataCollectorTest extends \PHPUnit_Framework_TestCase
array(
'Static callback as string',
'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest::staticControllerMethod',
'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest::staticControllerMethod',
__NAMESPACE__.'\RequestDataCollectorTest::staticControllerMethod',
array(
'class' => 'Symfony\Component\HttpKernel\Tests\DataCollector\RequestDataCollectorTest',
'method' => 'staticControllerMethod',
'file' => __FILE__,
'line' => $r2->getStartLine(),
),
),
array(