Skip to content

Commit abd67eb

Browse files
committed
[Profiler][Validator] Add a validator panel in profiler
1 parent 4753467 commit abd67eb

File tree

5 files changed

+399
-0
lines changed

5 files changed

+399
-0
lines changed
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 <[email protected]>
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 <[email protected]>
27+
*/
28+
class ValidatorDataCollector extends DataCollector implements LateDataCollectorInterface
29+
{
30+
private $validator;
31+
private $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 <[email protected]>
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(ConstraintViolation::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+
}
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
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\Validator;
13+
14+
use PHPUnit\Framework\TestCase;
15+
use Symfony\Component\Validator\Constraint;
16+
use Symfony\Component\Validator\ConstraintViolation;
17+
use Symfony\Component\Validator\ConstraintViolationList;
18+
use Symfony\Component\Validator\ConstraintViolationListInterface;
19+
use Symfony\Component\Validator\Context\ExecutionContextInterface;
20+
use Symfony\Component\Validator\Mapping\MetadataInterface;
21+
use Symfony\Component\Validator\Validator\ContextualValidatorInterface;
22+
use Symfony\Component\Validator\Validator\TraceableValidator;
23+
use Symfony\Component\Validator\Validator\ValidatorInterface;
24+
25+
class TraceableValidatorTest extends TestCase
26+
{
27+
public function testValidate()
28+
{
29+
$originalValidator = $this->createMock(ValidatorInterface::class);
30+
$violations = new ConstraintViolationList(array(
31+
$this->createMock(ConstraintViolation::class),
32+
$this->createMock(ConstraintViolation::class),
33+
));
34+
$originalValidator->expects($this->exactly(2))->method('validate')->willReturn($violations);
35+
36+
$validator = new TraceableValidator($originalValidator);
37+
38+
$object = new \stdClass();
39+
$constraints = array($this->createMock(Constraint::class));
40+
$groups = array('Default', 'Create');
41+
42+
$validator->validate($object, $constraints, $groups);
43+
$line = __LINE__ - 1;
44+
45+
$collectedData = $validator->getCollectedData();
46+
47+
$this->assertCount(1, $collectedData);
48+
49+
$callData = $collectedData[0];
50+
51+
$this->assertSame(iterator_to_array($violations), $callData['violations']);
52+
53+
$this->assertSame(array(
54+
'value' => $object,
55+
'constraints' => $constraints,
56+
'groups' => $groups,
57+
), $callData['context']);
58+
59+
$this->assertEquals(array(
60+
'name' => 'TraceableValidatorTest.php',
61+
'file' => __FILE__,
62+
'line' => $line,
63+
), $callData['caller']);
64+
65+
$validator->validate($object, $constraints, $groups);
66+
$collectedData = $validator->getCollectedData();
67+
68+
$this->assertCount(2, $collectedData);
69+
}
70+
71+
public function testForwardsToOriginalValidator()
72+
{
73+
$originalValidator = $this->createMock(ValidatorInterface::class);
74+
$validator = new TraceableValidator($originalValidator);
75+
76+
$expects = function ($method) use ($originalValidator) { return $originalValidator->expects($this->once())->method($method); };
77+
78+
$expects('getMetadataFor')->willReturn($expected = $this->createMock(MetadataInterface::class));
79+
$this->assertSame($expected, $validator->getMetadataFor('value'), 'returns original validator getMetadataFor() result');
80+
81+
$expects('hasMetadataFor')->willReturn($expected = false);
82+
$this->assertSame($expected, $validator->hasMetadataFor('value'), 'returns original validator hasMetadataFor() result');
83+
84+
$expects('inContext')->willReturn($expected = $this->createMock(ContextualValidatorInterface::class));
85+
$this->assertSame($expected, $validator->inContext($this->createMock(ExecutionContextInterface::class)), 'returns original validator inContext() result');
86+
87+
$expects('startContext')->willReturn($expected = $this->createMock(ContextualValidatorInterface::class));
88+
$this->assertSame($expected, $validator->startContext(), 'returns original validator startContext() result');
89+
90+
$expects('validate')->willReturn($expected = $this->createMock(ConstraintViolationListInterface::class));
91+
$this->assertSame($expected, $validator->validate('value'), 'returns original validator validate() result');
92+
93+
$expects('validateProperty')->willReturn($expected = $this->createMock(ConstraintViolationListInterface::class));
94+
$this->assertSame($expected, $validator->validateProperty(new \stdClass(), 'property'), 'returns original validator validateProperty() result');
95+
96+
$expects('validatePropertyValue')->willReturn($expected = $this->createMock(ConstraintViolationListInterface::class));
97+
$this->assertSame($expected, $validator->validatePropertyValue(new \stdClass(), 'property', 'value'), 'returns original validator validatePropertyValue() result');
98+
}
99+
100+
protected function createMock($classname)
101+
{
102+
return $this->getMockBuilder($classname)->disableOriginalConstructor()->getMock();
103+
}
104+
}

0 commit comments

Comments
 (0)