8000 [DoctrineBridge] Improve queries parameters display in Profiler · symfony/symfony@0de1c78 · GitHub
[go: up one dir, main page]

Skip to content
8000

Commit 0de1c78

Browse files
committed
[DoctrineBridge] Improve queries parameters display in Profiler
1 parent 43b753d commit 0de1c78

File tree

3 files changed

+168
-21
lines changed

3 files changed

+168
-21
lines changed

src/Symfony/Bridge/Doctrine/DataCollector/DoctrineDataCollector.php

Lines changed: 53 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,8 @@
1818
use Symfony\Component\HttpFoundation\Request;
1919
use Symfony\Component\HttpFoundation\Response;
2020
use Symfony\Component\HttpKernel\DataCollector\DataCollector;
21+
use Symfony\Component\VarDumper\Caster\Caster;
22+
use Symfony\Component\VarDumper\Cloner\Stub;
2123

2224
/**
2325
* DoctrineDataCollector.
@@ -121,6 +123,38 @@ public function getName()
121123
return 'db';
122124
}
123125

126+
/**
127+
* {@inheritdoc}
128+
*/
129+
protected function getCasters()
130+
{
131+
return parent::getCasters() + [
132+
ObjectParameter::class => static function (ObjectParameter $o, array $a, Stub $s): array {
133+
$s->class = $o->getClass();
134+
$s->value = $o->getObject();
135+
136+
$r = new \ReflectionClass($o->getClass());
137+
if ($f = $r->getFileName()) {
138+
$s->attr['file'] = $f;
139+
$s->attr['line'] = $r->getStartLine();
140+
} else {
141+
unset($s->attr['file']);
142+
unset($s->attr['line']);
143+
}
144+
145+
if ($error = $o->getError()) {
146+
return [Caster::PREFIX_VIRTUAL.'' => $error->getMessage()];
147+
}
148+
149+
if ($o->isStringable()) {
150+
return [Caster::PREFIX_VIRTUAL.'__toString()' => (string) $o->getObject()];
151+
}
152+
153+
return [Caster::PREFIX_VIRTUAL.'' => sprintf('Object of class "%s" could not be converted to string.', $o->getClass())];
154+
},
155+
];
156+
}
157+
124158
private function sanitizeQueries(string $connectionName, array $queries): array
125159
{
126160
foreach ($queries as $i => $query) {
@@ -133,6 +167,7 @@ private function sanitizeQueries(string $connectionName, array $queries): array
133167
private function sanitizeQuery(string $connectionName, array $query): array
134168
{
135169
$query['explainable'] = true;
170+
$query['runnable'] = true;
136171
if (null === $query['params']) {
137172
$query['params'] = [];
138173
}
@@ -143,6 +178,7 @@ private function sanitizeQuery(string $connectionName, array $query): array
143178
$query['types'] = [];
144179
}
145180
foreach ($query['params'] as $j => $param) {
181+
$e = null;
146182
if (isset($query['types'][$j])) {
147183
// Transform the param according to the type
148184
$type = $query['types'][$j];
@@ -162,10 +198,14 @@ private function sanitizeQuery(string $connectionName, array $query): array
162198
}
163199
}
164200

165-
list($query['params'][$j], $explainable) = $this->sanitizeParam($param);
201+
list($query['params'][$j], $explainable, $runnable) = $this->sanitizeParam($param, $e);
166202
if (!$explainable) {
167203
$query['explainable'] = false;
168204
}
205+
206+
if (!$runnable) {
207+
$query['runnable'] = false;
208+
}
169209
}
170210

171211
$query['params'] = $this->cloneVar($query['params']);
@@ -180,32 +220,33 @@ private function sanitizeQuery(string $connectionName, array $query): array
180220
* indicating if the original value was kept (allowing to use the sanitized
181221
* value to explain the query).
182222
*/
183-
private function sanitizeParam($var): array
223+
private function sanitizeParam($var, ?\Throwable $error): array
184224
{
185225
if (\is_object($var)) {
186-
$className = \get_class($var);
226+
return [new ObjectParameter($var, $stringable = method_exists($var, '__toString'), $error), false, $stringable && !$error];
227+
}
187228

188-
return method_exists($var, '__toString') ?
189-
[sprintf('/* Object(%s): */"%s"', $className, $var->__toString()), false] :
190-
[sprintf('/* Object(%s) */', $className), false];
229+
if ($error) {
230+
return [''.$error->getMessage(), false, false];
191231
}
192232

193233
if (\is_array($var)) {
194234
$a = [];
195-
$original = true;
235+
$explainable = $runnable = true;
196236
foreach ($var as $k => $v) {
197-
list($value, $orig) = $this->sanitizeParam($v);
198-
$original = $original && $orig;
237+
list($value, $e, $r) = $this->sanitizeParam($v, null);
238+
$explainable = $explainable && $e;
239+
$runnable = $runnable && $r;
199240
$a[$k] = $value;
200241
}
201242

202-
return [$a, $original];
243+
return [$a, $explainable, $runnable];
203244
}
204245

205246
if (\is_resource($var)) {
206-
return [sprintf('/* Resource(%s) */', get_resource_type($var)), false];
247+
return [sprintf('/* Resource(%s) */', get_resource_type($var)), false, false];
207248
}
208249

209-
return [$var, true];
250+
return [$var, true, true];
210251
}
211252
}
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
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\Bridge\Doctrine\DataCollector;
13+
14+
final class ObjectParameter
15+
{
16+
private $object;
17+
private $stringable;
18+
private $error;
19+
private $class;
20+
21+
/**
22+
* @param object $object
23+
*/
24+
public function __construct($object, bool $stringable, ?\Throwable $error)
25+
{
26+
$this->object = $object;
27+
$this->stringable = $stringable;
28+
$this->error = $error;
29+
$this->class = \get_class($object);
30+
}
31+
32+
/**
33+
* @return object
34+
*/
35+
public function getObject()
36+
{
37+
return $this->object;
38+
}
39+
40+
public function isStringable(): bool
41+
{
42+
return $this->stringable;
43+
}
44+
45+
public function getError(): ?\Throwable
46+
{
47+
return $this->error;
48+
}
49+
50+
public function getClass(): string
51+
{
52+
return $this->class;
53+
}
54+
}

src/Symfony/Bridge/Doctrine/Tests/DataCollector/DoctrineDataCollectorTest.php

Lines changed: 61 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@
1818
use Symfony\Component\HttpFoundation\Request;
1919
use Symfony\Component\HttpFoundation\Response;
2020
use Symfony\Component\VarDumper\Cloner\Data;
21+
use Symfony\Component\VarDumper\Dumper\CliDumper;
2122

2223
class DoctrineDataCollectorTest extends TestCase
2324
{
@@ -74,7 +75,7 @@ public function testCollectTime()
7475
/**
7576
* @dataProvider paramProvider
7677
*/
77-
public function testCollectQueries($param, $types, $expected, $explainable)
78+
public function testCollectQueries($param, $types, $expected, $explainable, bool $runnable = true)
7879
{
7980
$queries = [
8081
['sql' => 'SELECT * FROM table1 WHERE field1 = ?1', 'params' => [$param], 'types' => $types, 'executionMS' => 1],
@@ -83,8 +84,19 @@ public function testCollectQueries($param, $types, $expected, $explainable)
8384
$c->collect(new Request(), new Response());
8485

8586
$collectedQueries = $c->getQueries();
86-
$this->assertEquals($expected, $collectedQueries['default'][0]['params'][0]);
87+
88+
$collectedParam = $collectedQueries['default'][0]['params'][0];
89+
if ($collectedParam instanceof Data) {
90+
$dumper = new CliDumper($out = fopen('php://memory', 'r+b'));
91+
$dumper->setColors(false);
92+
$collectedParam->dump($dumper);
93+
$this->assertStringMatchesFormat($expected, print_r(stream_get_contents($out, -1, 0), true));
94+
} else {
95+
$this->assertEquals($expected, $collectedParam);
96+
}
97+
8798
$this->assertEquals($explainable, $collectedQueries['default'][0]['explainable']);
99+
$this->assertSame($runnable, $collectedQueries['default'][0]['runnable']);
88100
}
89101

90102
public function testCollectQueryWithNoParams()
@@ -100,9 +112,11 @@ public function testCollectQueryWithNoParams()
100112
$this->assertInstanceOf(Data::class, $collectedQueries['default'][0]['params']);
101113
$this->assertEquals([], $collectedQueries['default'][0]['params']->getValue());
102114
$this->assertTrue($collectedQueries['default'][0]['explainable']);
115+
$this->assertTrue($collectedQueries['default'][0]['runnable']);
103116
$this->assertInstanceOf(Data::class, $collectedQueries['default'][1]['params']);
104117
$this->assertEquals([], $collectedQueries['default'][1]['params']->getValue());
105118
$this->assertTrue($collectedQueries['default'][1]['explainable']);
119+
$this->assertTrue($collectedQueries['default'][1]['runnable']);
106120
}
107121

108122
public function testCollectQueryWithNoTypes()
@@ -134,7 +148,7 @@ public function testReset()
134148
/**
135149
* @dataProvider paramProvider
136150
*/
137-
public function testSerialization($param, $types, $expected, $explainable)
151+
public function testSerialization($param, $types, $expected, $explainable, bool $runnable = true)
138152
{
139153
$queries = [
140154
['sql' => 'SELECT * FROM table1 WHERE field1 = ?1', 'params' => [$param], 'types' => $types, 'executionMS' => 1],
@@ -144,8 +158,19 @@ public function testSerialization($param, $types, $expected, $explainable)
144158
$c = unserialize(serialize($c));
145159

146160
$collectedQueries = $c->getQueries();
147-
$this->assertEquals($expected, $collectedQueries['default'][0]['params'][0]);
161+
162+
$collectedParam = $collectedQueries['default'][0]['params'][0];
163+
if ($collectedParam instanceof Data) {
164+
$dumper = new CliDumper($out = fopen('php://memory', 'r+b'));
165+
$dumper->setColors(false);
166+
$collectedParam->dump($dumper);
167+
$this->assertStringMatchesFormat($expected, print_r(stream_get_contents($out, -1, 0), true));
168+
} else {
169+
$this->assertEquals($expected, $collectedParam);
170+
}
171+
148172
$this->assertEquals($explainable, $collectedQueries['default'][0]['explainable']);
173+
$this->assertSame($runnable, $collectedQueries['default'][0]['runnable']);
149174
}
150175

151176
public function paramProvider()
@@ -156,19 +181,46 @@ public function paramProvider()
156181
[true, [], true, true],
157182
[null, [], null, true],
158183
[new \DateTime('2011-09-11'), ['date'], '2011-09-11', true],
159-
[fopen(__FILE__, 'r'), [], '/* Resource(stream) */', false],
160-
[new \stdClass(), [], '/* Object(stdClass) */', false],
184+
[fopen(__FILE__, 'r'), [], '/* Resource(stream) */', false, false],
185+
[
186+
new \stdClass(),
187+
[],
188+
<<<EOTXT
189+
{#%d
190+
⚠: "Object of class "stdClass" could not be converted to string."
191+
}
192+
EOTXT
193+
,
194+
false,
195+
false,
196+
],
161197
[
162198
new StringRepresentableClass(),
163199
[],
164-
'/* Object(Symfony\Bridge\Doctrine\Tests\DataCollector\StringRepresentableClass): */"string representation"',
200+
<<<EOTXT
201+
Symfony\Bridge\Doctrine\Tests\DataCollector\StringRepresentableClass {#%d
202+
__toString(): "string representation"
203+
}
204+
EOTXT
205+
,
165206
false,
166207
],
167208
];
168209

169210
if (version_compare(Version::VERSION, '2.6', '>=')) {
170-
$tests[] = ['this is not a date', ['date'], 'this is not a date', false];
171-
$tests[] = [new \stdClass(), ['date'], '/* Object(stdClass) */', false];
211+
$tests[] = ['this is not a date', ['date'], "⚠ Could not convert PHP value 'this is not a date' of type 'string' to type 'date'. Expected one of the following types: null, DateTime", false, false];
212+
$tests[] = [
213+
new \stdClass(),
214+
['date'],
215+
<<<EOTXT
216+
{#%d
217+
⚠: "Could not convert PHP value of type 'stdClass' to type 'date'. Expected one of the following types: null, DateTime"
218+
}
219+
EOTXT
220+
,
221+
false,
222+
false,
223+
];
172224
}
173225

174226
return $tests;

0 commit comments

Comments
 (0)
0