Skip to content

Commit a0a98fd

Browse files
committed
QueryCheckerTreeWalker: improve error message
1 parent e713b2b commit a0a98fd

File tree

2 files changed

+45
-45
lines changed

2 files changed

+45
-45
lines changed

src/QueryCheckerTreeWalker.php

+25-25
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
use Doctrine\ORM\Query\AST\Phase2OptimizableConditional;
2222
use Doctrine\ORM\Query\AST\SelectStatement;
2323
use Doctrine\ORM\Query\AST\WhereClause;
24+
use Doctrine\ORM\Query\Parameter;
2425
use Doctrine\ORM\Query\TreeWalkerAdapter;
2526
use Psr\Log\LoggerInterface;
2627
use ShipMonk\DoctrineQueryChecker\Exception\LogicException;
@@ -157,9 +158,15 @@ protected function verifyInputParameterType(
157158
InputParameter $inputParameter,
158159
): void
159160
{
160-
$inputParameterType = $this->getInputParameterType($inputParameter);
161+
$parameter = $this->_getQuery()->getParameter($inputParameter->name);
161162

162-
if ($inputParameterType === null) {
163+
if ($parameter === null) {
164+
return; // happens when the query is analyzed by PHPStan
165+
}
166+
167+
$parameterType = $this->getParameterType($parameter);
168+
169+
if ($parameterType === null) {
163170
return;
164171
}
165172

@@ -173,27 +180,26 @@ protected function verifyInputParameterType(
173180
}
174181
}
175182

176-
if (in_array($inputParameterType, $compatibleTypesExtended, strict: true)) {
183+
if (in_array($parameterType, $compatibleTypesExtended, strict: true)) {
177184
return;
178185
}
179186

180-
if (count($compatibleTypes) === 1) {
181-
$expectedDescription = sprintf('"%s"', self::typeToName($compatibleTypes[0]));
187+
$parameterTypeName = self::typeToName($parameterType);
182188

183-
} else {
184-
$compatibleTypeNames = array_map(self::typeToName(...), $compatibleTypes);
185-
$expectedDescription = sprintf('one of: ["%s"]', implode('", "', $compatibleTypeNames));
186-
}
189+
$expressionName = $pathExpression->field !== null
190+
? "{$pathExpression->identificationVariable}.{$pathExpression->field}"
191+
: $pathExpression->identificationVariable;
187192

188-
$this->processException(
189-
new LogicException(sprintf(
190-
'QueryCheckerTreeWalker: Parameter "%s" is of type "%s", but expected %s (because it\'s used in expression with %s)',
191-
$inputParameter->name,
192-
self::typeToName($inputParameterType),
193-
$expectedDescription,
194-
$pathExpression->field !== null ? "{$pathExpression->identificationVariable}.{$pathExpression->field}" : $pathExpression->identificationVariable,
195-
)),
196-
);
193+
$expectedTypeName = count($compatibleTypes) === 1
194+
? sprintf("'%s'", self::typeToName($compatibleTypes[0]))
195+
: sprintf("one of: ['%s']", implode("', '", array_map(self::typeToName(...), $compatibleTypes)));
196+
197+
$message = $parameter->typeWasSpecified()
198+
? "Parameter '{$inputParameter->name}' is using '{$parameterTypeName}' type in 3rd argument of setParameter()"
199+
: "Parameter '{$inputParameter->name}' has no type specified in 3rd argument of setParameter(). Thus it is inferred as '{$parameterTypeName}'";
200+
201+
$message .= ", but it is compared with '{$expressionName}' which can only be compared with {$expectedTypeName}.";
202+
$this->processException(new LogicException("QueryCheckerTreeWalker: $message"));
197203
}
198204

199205
private static function typeToName(string|Type|ParameterType|ArrayParameterType|null $type): string
@@ -259,14 +265,8 @@ protected function getFieldCompatibleTypes(
259265
return $types;
260266
}
261267

262-
protected function getInputParameterType(InputParameter $node): string|Type|ParameterType|ArrayParameterType|null
268+
protected function getParameterType(Parameter $parameter): string|Type|ParameterType|ArrayParameterType|null
263269
{
264-
$parameter = $this->_getQuery()->getParameter($node->name);
265-
266-
if ($parameter === null) {
267-
return null; // happens when the query is analyzed by PHPStan
268-
}
269-
270270
if ($parameter->typeWasSpecified()) {
271271
return $this->normalizeType($parameter->getType());
272272
}

tests/QueryCheckerTreeWalkerTest.php

+20-20
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public function testWrongParameterTypeInVariousPlaces(mixed $expr): void
2727
{
2828
self::assertException(
2929
LogicException::class,
30-
'QueryCheckerTreeWalker: Parameter "stringField" is of type "float", but expected "string" (because it\'s used in expression with e.stringField)',
30+
'QueryCheckerTreeWalker: Parameter \'stringField\' has no type specified in 3rd argument of setParameter(). Thus it is inferred as \'float\', but it is compared with \'e.stringField\' which can only be compared with \'string\'.',
3131
function () use ($expr): void {
3232
$this->getEntityManager()->createQueryBuilder()
3333
->select('e')
@@ -42,7 +42,7 @@ function () use ($expr): void {
4242

4343
self::assertException(
4444
LogicException::class,
45-
'QueryCheckerTreeWalker: Parameter "stringField" is of type "float", but expected "string" (because it\'s used in expression with e.stringField)',
45+
'QueryCheckerTreeWalker: Parameter \'stringField\' has no type specified in 3rd argument of setParameter(). Thus it is inferred as \'float\', but it is compared with \'e.stringField\' which can only be compared with \'string\'.',
4646
function () use ($expr): void {
4747
$this->getEntityManager()->createQueryBuilder()
4848
->select('e')
@@ -201,113 +201,113 @@ public static function provideWrongParameterTypesData(): iterable
201201
'e.stringField',
202202
123,
203203
null,
204-
'Parameter "e_stringField" is of type "integer", but expected "string" (because it\'s used in expression with e.stringField)',
204+
'Parameter \'e_stringField\' has no type specified in 3rd argument of setParameter(). Thus it is inferred as \'integer\', but it is compared with \'e.stringField\' which can only be compared with \'string\'.',
205205
];
206206

207207
yield [
208208
'e.stringField',
209209
123.4,
210210
null,
211-
'Parameter "e_stringField" is of type "float", but expected "string" (because it\'s used in expression with e.stringField)',
211+
'Parameter \'e_stringField\' has no type specified in 3rd argument of setParameter(). Thus it is inferred as \'float\', but it is compared with \'e.stringField\' which can only be compared with \'string\'.',
212212
];
213213

214214
yield [
215215
'e.stringField',
216216
TestEntityWithManyFieldTypesStringEnum::A,
217217
null,
218-
'Parameter "e_stringField" is of type "ShipMonkTests\DoctrineQueryChecker\Fixture\Enum\TestEntityWithManyFieldTypesStringEnum", but expected "string" (because it\'s used in expression with e.stringField)',
218+
'Parameter \'e_stringField\' has no type specified in 3rd argument of setParameter(). Thus it is inferred as \'ShipMonkTests\DoctrineQueryChecker\Fixture\Enum\TestEntityWithManyFieldTypesStringEnum\', but it is compared with \'e.stringField\' which can only be compared with \'string\'.',
219219
];
220220

221221
yield [
222222
'e.textField',
223223
123,
224224
null,
225-
'Parameter "e_textField" is of type "integer", but expected "string" (because it\'s used in expression with e.textField)',
225+
'Parameter \'e_textField\' has no type specified in 3rd argument of setParameter(). Thus it is inferred as \'integer\', but it is compared with \'e.textField\' which can only be compared with \'string\'.',
226226
];
227227

228228
yield [
229229
'e.textField',
230230
123.4,
231231
null,
232-
'Parameter "e_textField" is of type "float", but expected "string" (because it\'s used in expression with e.textField)',
232+
'Parameter \'e_textField\' has no type specified in 3rd argument of setParameter(). Thus it is inferred as \'float\', but it is compared with \'e.textField\' which can only be compared with \'string\'.',
233233
];
234234

235235
yield [
236236
'e.booleanField',
237237
'ABC',
238238
null,
239-
'Parameter "e_booleanField" is of type "string", but expected "boolean" (because it\'s used in expression with e.booleanField)',
239+
'Parameter \'e_booleanField\' has no type specified in 3rd argument of setParameter(). Thus it is inferred as \'string\', but it is compared with \'e.booleanField\' which can only be compared with \'boolean\'.',
240240
];
241241

242242
yield [
243243
'e.stringEnumField',
244244
TestEntityWithManyFieldTypesIntEnum::A,
245245
null,
246-
'Parameter "e_stringEnumField" is of type "ShipMonkTests\DoctrineQueryChecker\Fixture\Enum\TestEntityWithManyFieldTypesIntEnum", but expected one of: ["ShipMonkTests\DoctrineQueryChecker\Fixture\Enum\TestEntityWithManyFieldTypesStringEnum", "string"] (because it\'s used in expression with e.stringEnumField)',
246+
'Parameter \'e_stringEnumField\' has no type specified in 3rd argument of setParameter(). Thus it is inferred as \'ShipMonkTests\DoctrineQueryChecker\Fixture\Enum\TestEntityWithManyFieldTypesIntEnum\', but it is compared with \'e.stringEnumField\' which can only be compared with one of: [\'ShipMonkTests\DoctrineQueryChecker\Fixture\Enum\TestEntityWithManyFieldTypesStringEnum\', \'string\'].',
247247
];
248248

249249
yield [
250250
'e.dateTimeImmutableField',
251251
'2021-01-01',
252252
Types::STRING,
253-
'Parameter "e_dateTimeImmutableField" is of type "string", but expected "datetime_immutable" (because it\'s used in expression with e.dateTimeImmutableField)',
253+
'Parameter \'e_dateTimeImmutableField\' is using \'string\' type in 3rd argument of setParameter(), but it is compared with \'e.dateTimeImmutableField\' which can only be compared with \'datetime_immutable\'.',
254254
];
255255

256256
$simpleTestEntityWithUuid = new SimpleTestEntityWithUuid();
257257
yield [
258258
'e.simpleTestEntity',
259259
$simpleTestEntityWithUuid,
260260
null,
261-
'Parameter "e_simpleTestEntity" is of type "ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntityWithUuid", but expected one of: ["ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntity", "integer"] (because it\'s used in expression with e.simpleTestEntity)',
261+
'Parameter \'e_simpleTestEntity\' has no type specified in 3rd argument of setParameter(). Thus it is inferred as \'ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntityWithUuid\', but it is compared with \'e.simpleTestEntity\' which can only be compared with one of: [\'ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntity\', \'integer\'].',
262262
];
263263

264264
yield [
265265
'se.id',
266266
$simpleTestEntityWithUuid,
267267
null,
268-
'Parameter "se_id" is of type "ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntityWithUuid", but expected one of: ["ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntity", "integer"] (because it\'s used in expression with se.id)',
268+
'Parameter \'se_id\' has no type specified in 3rd argument of setParameter(). Thus it is inferred as \'ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntityWithUuid\', but it is compared with \'se.id\' which can only be compared with one of: [\'ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntity\', \'integer\'].',
269269
];
270270

271271
yield [
272272
'e.simpleTestEntityWithUuid',
273273
$simpleTestEntityWithUuid->getUuid(),
274274
null,
275-
'Parameter "e_simpleTestEntityWithUuid" is of type "string", but expected one of: ["ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntityWithUuid", "uuid"] (because it\'s used in expression with e.simpleTestEntityWithUuid)',
275+
'Parameter \'e_simpleTestEntityWithUuid\' has no type specified in 3rd argument of setParameter(). Thus it is inferred as \'string\', but it is compared with \'e.simpleTestEntityWithUuid\' which can only be compared with one of: [\'ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntityWithUuid\', \'uuid\'].',
276276
];
277277

278278
yield [
279279
'e.simpleTestEntityWithUuid',
280280
$simpleTestEntityWithUuid->getUuid(),
281281
Types::STRING,
282-
'Parameter "e_simpleTestEntityWithUuid" is of type "string", but expected one of: ["ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntityWithUuid", "uuid"] (because it\'s used in expression with e.simpleTestEntityWithUuid)',
282+
'Parameter \'e_simpleTestEntityWithUuid\' is using \'string\' type in 3rd argument of setParameter(), but it is compared with \'e.simpleTestEntityWithUuid\' which can only be compared with one of: [\'ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntityWithUuid\', \'uuid\'].',
283283
];
284284

285285
yield [
286286
'sewu',
287287
$simpleTestEntityWithUuid->getUuid(),
288288
null,
289-
'Parameter "sewu" is of type "string", but expected one of: ["ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntityWithUuid", "uuid"] (because it\'s used in expression with sewu.uuid)',
289+
'Parameter \'sewu\' has no type specified in 3rd argument of setParameter(). Thus it is inferred as \'string\', but it is compared with \'sewu.uuid\' which can only be compared with one of: [\'ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntityWithUuid\', \'uuid\'].',
290290
];
291291

292292
yield [
293293
'sewu',
294294
$simpleTestEntityWithUuid->getUuid(),
295295
Types::STRING,
296-
'Parameter "sewu" is of type "string", but expected one of: ["ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntityWithUuid", "uuid"] (because it\'s used in expression with sewu.uuid)',
296+
'Parameter \'sewu\' is using \'string\' type in 3rd argument of setParameter(), but it is compared with \'sewu.uuid\' which can only be compared with one of: [\'ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntityWithUuid\', \'uuid\'].',
297297
];
298298

299299
yield [
300300
'sewu.uuid',
301301
$simpleTestEntityWithUuid->getUuid(),
302302
null,
303-
'Parameter "sewu_uuid" is of type "string", but expected one of: ["ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntityWithUuid", "uuid"] (because it\'s used in expression with sewu.uuid)',
303+
'Parameter \'sewu_uuid\' has no type specified in 3rd argument of setParameter(). Thus it is inferred as \'string\', but it is compared with \'sewu.uuid\' which can only be compared with one of: [\'ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntityWithUuid\', \'uuid\'].',
304304
];
305305

306306
yield [
307307
'sewu.uuid',
308308
$simpleTestEntityWithUuid->getUuid(),
309309
Types::STRING,
310-
'Parameter "sewu_uuid" is of type "string", but expected one of: ["ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntityWithUuid", "uuid"] (because it\'s used in expression with sewu.uuid)',
310+
'Parameter \'sewu_uuid\' is using \'string\' type in 3rd argument of setParameter(), but it is compared with \'sewu.uuid\' which can only be compared with one of: [\'ShipMonkTests\DoctrineQueryChecker\Fixture\Entity\SimpleTestEntityWithUuid\', \'uuid\'].',
311311
];
312312
}
313313

@@ -317,7 +317,7 @@ public function testWillUseLoggerIfAvailable(): void
317317
$logger->expects(self::once())
318318
->method('error')
319319
->with(
320-
'QueryCheckerTreeWalker: Parameter "value" is of type "float", but expected "boolean" (because it\'s used in expression with e.booleanField)',
320+
'QueryCheckerTreeWalker: Parameter \'value\' has no type specified in 3rd argument of setParameter(). Thus it is inferred as \'float\', but it is compared with \'e.booleanField\' which can only be compared with \'boolean\'.',
321321
self::arrayHasKey('exception'),
322322
);
323323

@@ -336,7 +336,7 @@ public function testWillUseLoggerIfAvailable(): void
336336

337337
self::assertException(
338338
LogicException::class,
339-
'QueryCheckerTreeWalker: Parameter "value" is of type "float", but expected "boolean" (because it\'s used in expression with e.booleanField)',
339+
'QueryCheckerTreeWalker: Parameter \'value\' has no type specified in 3rd argument of setParameter(). Thus it is inferred as \'float\', but it is compared with \'e.booleanField\' which can only be compared with \'boolean\'.',
340340
function (): void {
341341
$this->getEntityManager()->createQueryBuilder()
342342
->select('e')

0 commit comments

Comments
 (0)