8000 feature #22554 [Profiler][Validator] Add a validator panel in profile… · symfony/symfony@30e817a · GitHub
[go: up one dir, main page]

Skip to content

Commit 30e817a

Browse files
committed
feature #22554 [Profiler][Validator] Add a validator panel in profiler (ogizanagi)
This PR was merged into the 3.4 branch. Discussion ---------- [Profiler][Validator] Add a validator panel in profiler | Q | A | ------------- | --- | Branch? | 3.4 | Bug fix? | no | New feature? | yes | BC breaks? | no | Deprecations? | no | Tests pass? | yes | Fixed tickets | N/A | License | MIT | Doc PR | N/A I'm exploring the possibility of having a validator panel in the profiler. The integration in the form panel is great, but there are a lot of other use-cases where you're likely to call the validator. The idea of this panel is to reference every calls made to the validator (`ValidatorInterface::validate()` at least) along with detailed informations. Dealing with apis and a mobile app, it's not always easy to get the response body within the app to get what's wrong with the call. So now with this panel, I'm able to get the details without the api response. In action with Symfony demo (on the admin new post form): ![symfony-demo](https://cloud.githubusercontent.com/assets/2211145/25490828/579a2c96-2b6e-11e7-9574-fb0975a5db83.gif) ![capture d ecran 2017-04-27 a 17 14 24](https://cloud.githubusercontent.com/assets/2211145/25490866/77d76988-2b6e-11e7-83c7-a10613442a5e.png) On another app, by calling the validator elsewhere: |No violations|With violations| |--|--| |![capture d ecran 2017-04-27 a 17 16 41](https://cloud.githubusercontent.com/assets/2211145/25490861/741886f6-2b6e-11e7-9e18-5948312d0096.png)|![capture d ecran 2017-04-27 a 17 17 32](https://cloud.githubusercontent.com/assets/2211145/25490860/74128daa-2b6e-11e7-979f-0d39741cc172.png)| What do you think ? --- Note: the SVG icon used should be changed. If anyone is willing to contribute and provide one, I'll be glad to add it! Commits ------- ac5e884 [Profiler][Validator] Add a validator panel in profiler
2 parents b223241 + ac5e884 commit 30e817a

File tree

11 files changed

+551
-3
lines changed

11 files changed

+551
-3
lines changed

src/Symfony/Bundle/FrameworkBundle/DependencyInjection/FrameworkExtension.php

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,7 @@ class FrameworkExtension extends Extension
8181
private $translationConfigEnabled = false;
8282
private $sessionConfigEnabled = false;
8383
private $annotationsConfigEnabled = false;
84+
private $validatorConfigEnabled = false;
8485

8586
/**
8687
* @var string|null
@@ -456,6 +457,10 @@ private function registerProfilerConfiguration(array $config, ContainerBuilder $
456457
$loader->load('form_debug.xml');
457458
}
458459

460+
if ($this->validatorConfigEnabled) {
461+
$loader->load('validator_debug.xml');
462+
}
463+
459464
if ($this->translationConfigEnabled) {
460465
$loader->load('translation_debug.xml');
461466
$container->getDefinition('translator.data_collector')->setDecoratedService('translator');
@@ -1107,7 +1112,7 @@ private function registerTranslatorConfiguration(array $config, ContainerBuilder
11071112
*/
11081113
private function registerValidationConfiguration(array $config, ContainerBuilder $container, XmlFileLoader $loader)
11091114
{
1110-
if (!$this->isConfigEnabled($container, $config)) {
1115+
if (!$this->validatorConfigEnabled = $this->isConfigEnabled($container, $config)) {
11111116
return;
11121117
}
11131118

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?xml version="1.0" ?>
2+
3+
<container xmlns="http://symfony.com/schema/dic/services"
4+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
5+
xsi:schemaLocation="http://symfony.com/schema/dic/services http://symfony.com/schema/dic/services/services-1.0.xsd">
6+
7+
<services>
8+
<defaults public="false" />
9+
10+
<service id="debug.validator" decorates="validator" decoration-priority="255" class="Symfony\Component\Validator\Validator\TraceableValidator">
11+
<argument type="service" id="debug.validator.inner" />
12+
</service>
13+
14+
<!-- DataCollector -->
15+
<service id="data_collector.validator" class="Symfony\Component\Validator\DataCollector\ValidatorDataCollector">
16+
<argument type="service" id="debug.validator"/>
17+
<tag name="data_collector" template="@WebProfiler/Collector/validator.html.twig" id="validator" priority="320" />
18+
</service>
19+
</services>
20+
</container>

src/Symfony/Bundle/FrameworkBundle/composer.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -49,7 +49,7 @@
4949
"symfony/stopwatch": "~2.8|~3.0|~4.0",
5050
"symfony/translation": "~3.2|~4.0",
5151
"symfony/templating": "~2.8|~3.0|~4.0",
52-
"symfony/validator": "~3.3|~4.0",
52+
"symfony/validator": "~3.4|~4.0",
5353
"symfony/var-dumper": "~3.3|~4.0",
5454
"symfony/workflow": "~3.3|~4.0",
5555
"symfony/yaml": "~3.2|~4.0",
@@ -69,7 +69,7 @@
6969
"symfony/property-info": "<3.3",
7070
"symfony/serializer": "<3.3",
7171
"symfony/translation": "<3.2",
72-
"symfony/validator": "<3.3",
72+
"symfony/validator": "<3.4",
7373
"symfony/workflow": "<3.3"
7474
},
7575
"suggest": {
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
{% extends '@WebProfiler/Profiler/layout.html.twig' %}
2+
3+
{% block toolbar %}
4+
{% if collector.violationsCount > 0 or collector.calls|length %}
5+
{% set status_color = collector.violationsCount ? 'red' : '' %}
6+
{% set icon %}
7+
{{ include('@WebProfiler/Icon/validator.svg') }}
8+
<span class="sf-toolbar-value">
9+
{{ collector.violationsCount }}
10+
</span>
11+
{% endset %}
12+
13+
{% set text %}
14+
<div class="sf-toolbar-info-piece">
15+
<b>Validator calls</b>
16+
<span class="sf-toolbar-status">{{ collector.calls|length }}</span>
17+
</div>
18+
<div class="sf-toolbar-info-piece">
19+
<b>Number of violations</b>
20+
<span class="sf-toolbar-status {{- collector.violationsCount > 0 ? ' sf-toolbar-status-red' }}">{{ collector.violationsCount }}</span>
21+
</div>
22+
{% endset %}
23+
24+
{{ include('@WebProfiler/Profiler/toolbar_item.html.twig', { link: profiler_url, status: status_color }) }}
25+
{% endif %}
26+
{% endblock %}
27+
28+
{% block menu %}
29+
<span class="label {{- collector.violationsCount ? ' label-status-error' }} {{ collector.calls is empty ? 'disabled' }}">
30+
<span class="icon">{{ include('@WebProfiler/Icon/validator.svg') }}</span>
31+
<strong>Validator</strong>
32+
{% if collector.violationsCount > 0 %}
33+
<span class="count">
34+
<span>{{ collector.violationsCount }}</span>
35+
</span>
36+
{% endif %}
37+
</span>
38+
{% endblock %}
39+
40+
{% block panel %}
41+
<h2>Validator calls</h2>
42+
43+
{% for call in collector.calls %}
44+
<div class="sf-validator sf-reset">
45+
<span class="metadata">In
46+
{% set caller = call.caller %}
47+
{% if caller.line %}
48+
{% set link = caller.file|file_link(caller.line) %}
49+
{% if link %}
50+
<a href="{{ link }}" title="{{ caller.file }}">{{ caller.name }}</a>
51+
{% else %}
52+
<abbr title="{{ caller.file }}">{{ caller.name }}</abbr>
53+
{% endif %}
54+
{% else %}
55+
{{ caller.name }}
56+
{% endif %}
57+
line <a class="text-small sf-toggle" data-toggle-selector="#sf-trace-{{ loop.index0 }}">{{ caller.line }}</a> (<a class="text-small sf-toggle" data-toggle-selector="#sf-context-{{ loop.index0 }}">context</a>):
58+
</span>
59+
60+
<div class="sf-validator-compact hidden" id="sf-trace-{{ loop.index0 }}">
61+
<div class="trace">
62+
{{ caller.file|file_excerpt(caller.line) }}
63+
</div>
64+
</div>
65+
66+
<div class="sf-validator-compact hidden sf-validator-context" id="sf-context-{{ loop.index0 }}">
67+
{{ profiler_dump(call.context, maxDepth=1) }}
68+
</div>
69+
70+
{% if call.violations|length %}
71+
<table>
72+
<thead>
73+
<tr>
74+
<th>Path</th>
75+
<th>Message</th>
76+
<th>Invalid value</th>
77+
<th>Violation</th>
78+
</tr>
79+
</thead>
80+
{% for violation in call.violations %}
81+
<tr>
82+
<td>{{ violation.propertyPath }}</td>
83+
<td>{{ violation.message }}</td>
84+
<td>{{ profiler_dump(violation.seek('invalidValue')) }}</td>
85+
<td>{{ profiler_dump(violation) }}</td>
86+
</tr>
87+
{% endfor %}
88+
</table>
89+
{% else %}
90+
No violations
91+
{% endif %}
92+
</div>
93+
{% else %}
94+
<div class="empty">
95+
<p>No calls to the validator were collected during this request.</p>
96+
</div>
97+
{% endfor %}
98+
{% endblock %}
Lines changed: 1 addition & 0 deletions
Loading

src/Symfony/Bundle/WebProfilerBundle/Resources/views/Profiler/profiler.css.twig

Lines changed: 25 additions & 0 deletions
< 10000 td data-grid-cell-id="diff-6ad30ea9f42e2a40b1e9664347c486bfe5ca5222c03b51f2ee610edf4895b3a7-884-909-1" data-selected="false" role="gridcell" style="background-color:var(--bgColor-default);text-align:center" tabindex="-1" valign="top" class="focusable-grid-cell diff-line-number position-relative diff-line-number-neutral left-side">909
Original file line numberDiff line numberDiff line change
@@ -880,6 +880,31 @@ table.logs .metadata {
880880
white-space: pre-wrap;
881881
}
882882

883+
{# Validator panel
884+
========================================================================= #}
885+
886+
#collector-content .sf-validator {
887+
margin-bottom: 2em;
888+
}
889+
890+
#collector-content .sf-validator .sf-validator-context,
891+
#collector-content .sf-validator .trace {
892+
border: 1px solid #DDD;
893+
background: #FFF;
894+
padding: 10px;
895+
margin: 0.5em 0;
896+
}
897+
#collector-content .sf-validator .trace {
898+
font-size: 12px;
899+
}
900+
#collector-content .sf-validator .trace li {
901+
margin-bottom: 0;
902+
padding: 0;
903+
}
904+
#collector-content .sf-validator .trace li.selected {
905+
background: rgba(255, 255, 153, 0.5);
906+
}
907+
883908
{# Dump panel
884
========================================================================= #}
885910
#collector-content .sf-dump {
Lines changed: 105 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\DataCollector;
13+
14+
use Symfony\Component\Form\FormInterface;
15+
use Symfony\Component\HttpFoundation\Request;
16+
use Symfony\Component\HttpFoundation\Response;
17+
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
18+
use Symfony\Component\HttpKernel\DataCollector\LateDataCollectorInterface;
19+
use Symfony\Component\Validator\Validator\TraceableValidator;
20+
use Symfony\Component\VarDumper\Caster\Caster;
21+
use Symfony\Component\VarDumper\Caster\ClassStub;
22+
use Symfony\Component\VarDumper\Cloner\Data;
23+
use Symfony\Component\VarDumper\Cloner\VarCloner;
24+
25+
/**
26+
* @author Maxime Steinhausser <maxime.steinhausser@gmail.com>
27+
*/
28+
class ValidatorDataCollector extends DataCollector implements LateDataCollectorInterface
29+
{
30+
private $validator;
31+
private < 10000 span class=pl-c1>$cloner;
32+
33+
public function __construct(TraceableValidator $validator)
34+
{
35+
$this->validator = $validator;
36+
$this->data = array(
37+
'calls' => array(),
38+
'violations_count' => 0,
39+
);
40+
}
41+
42+
/**
43+
* {@inheritdoc}
44+
*/
45+
public function collect(Request $request, Response $response, \Exception $exception = null)
46+
{
47+
// Everything is collected once, on kernel terminate.
48+
}
49+
50+
/**
51+
* {@inheritdoc}
52+
*/
53+
public function lateCollect()
54+
{
55+
$collected = $this->validator->getCollectedData();
56+
$this->data['calls'] = $this->cloneVar($collected);
57+
$this->data['violations_count'] += array_reduce($collected, function ($previous, $item) {
58+
return $previous += count($item['violations']);
59+
}, 0);
60+
}
61+
62+
public function getCalls()
63+
{
64+
return $this->data['calls'];
65+
}
66+
67+
public function getViolationsCount()
68+
{
69+
return $this->data['violations_count'];
70+
}
71+
72+
/**
73+
* {@inheritdoc}
74+
*/
75+
public function getName()
76+
{
77+
return 'validator';
78+
}
79+
80+
/**
81+
* {@inheritdoc}
82+
*/
83+
protected function cloneVar($var)
84+
{
85+
if ($var instanceof Data) {
86+
return $var;
87+
}
88+
89+
if (null === $this->cloner) {
90+
$this->cloner = new VarCloner();
91+
$this->cloner->setMaxItems(-1);
92+
$this->cloner->addCasters(array(
93+
FormInterface::class => function (FormInterface $f, array $a) {
94+
return array(
95+
Caster::PREFIX_VIRTUAL.'name' => $f->getName(),
96+
Caster::PREFIX_VIRTUAL.'type_class' => new ClassStub(get_class($f->getConfig()->getType()->getInnerType())),
97+
Caster::PREFIX_VIRTUAL.'data' => $f->getData(),
98+
);
99+
},
100+
));
101+
}
102+
103+
return $this->cloner->cloneVar($var, Caster::EXCLUDE_VERBOSE);
104+
}
105+
}
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <fabien@symfony.com>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\Component\Validator\Tests\DataCollector;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Validator\ConstraintViolation;
16+
use Symfony\Component\Validator\ConstraintViolationList;
17+
use Symfony\Component\Validator\DataCollector\ValidatorDataCollector;
18+
use Symfony\Component\Validator\Validator\TraceableValidator;
19+
use Symfony\Component\Validator\Validator\ValidatorInterface;
20+
21+
class ValidatorDataCollectorTest extends TestCase
22+
{
23+
public function testCollectsValidatorCalls()
24+
{
25+
$originalValidator = $this->createMock(ValidatorInterface::class);
26+
$validator = new TraceableValidator($originalValidator);
27+
28+
$collector = new ValidatorDataCollector($validator);
29+
30+
$violations = new ConstraintViolationList(array(
31+
$this->createMock(ConstraintViolation::class),
32+
$this->createMock(C E620 onstraintViolation::class),
33+
));
34+
$originalValidator->method('validate')->willReturn($violations);
35+
36+
$validator->validate(new \stdClass());
37+
38+
$collector->lateCollect();
39+
40+
$calls = $collector->getCalls();
41+
42+
$this->assertCount(1, $calls);
43+
$this->assertSame(2, $collector->getViolationsCount());
44+
45+
$call = $calls[0];
46+
47+
$this->assertArrayHasKey('caller', $call);
48+
$this->assertArrayHasKey('context', $call);
49+
$this->assertArrayHasKey('violations', $call);
50+
$this->assertCount(2, $call['violations']);
51+
}
52+
53+
protected function createMock($classname)
54+
{
55+
return $this->getMockBuilder($classname)->disableOriginalConstructor()->getMock();
56+
}
57+
}

0 commit comments

Comments
 (0)
0