18
18
use ApiPlatform \Core \Bridge \Doctrine \Orm \Util \QueryNameGeneratorInterface ;
19
19
use ApiPlatform \Core \Exception \InvalidArgumentException ;
20
20
use Doctrine \Common \Persistence \ManagerRegistry ;
21
+ use Doctrine \Common \Persistence \Mapping \ClassMetadata ;
21
22
use Doctrine \DBAL \Types \Type ;
23
+ use Doctrine \ORM \Mapping \MappingException ;
22
24
use Doctrine \ORM \QueryBuilder ;
23
25
use Psr \Log \LoggerInterface ;
24
26
use Symfony \Component \HttpFoundation \RequestStack ;
@@ -138,7 +140,7 @@ public function getDescription(string $resourceClass): array
138
140
*
139
141
* @return string
140
142
*/
141
- private function getType (string $ doctrineType ): string
143
+ private function getType (string $ doctrineType = null ): string
142
144
{
143
145
switch ($ doctrineType ) {
144
146
case Type::TARRAY :
@@ -207,8 +209,16 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
207
209
$ caseSensitive = true ;
208
210
209
211
if ($ metadata ->hasField ($ field )) {
210
- if ('id ' === $ field ) {
211
- $ values = array_map ([$ this , 'getIdFromValue ' ], $ values );
212
+ if ($ metadata ->isIdentifier ($ field )) {
213
+ $ values = $ this ->getValueIdentifiers ($ values , $ field );
214
+ }
215
+
216
+ if (!$ this ->hasValidValues ($ values , $ field , $ metadata )) {
217
+ $ this ->logger ->notice ('Invalid filter ignored ' , [
218
+ 'exception ' => new InvalidArgumentException (sprintf ('Values for field "%s" are not valid according to the doctrine type. ' , $ field )),
219
+ ]);
220
+
221
+ return ;
212
222
}
213
223
214
224
$ strategy = $ this ->properties [$ property ] ?? self ::STRATEGY_EXACT ;
@@ -246,14 +256,33 @@ protected function filterProperty(string $property, $value, QueryBuilder $queryB
246
256
return ;
247
257
}
248
258
249
- $ values = array_map ([$ this , 'getIdFromValue ' ], $ values );
259
+ $ values = $ this ->getValueIdentifiers ($ values , $ field );
260
+
261
+ if (!$ this ->hasValidValues ($ values , $ field , $ metadata )) {
262
+ $ this ->logger ->notice ('Invalid filter ignored ' , [
263
+ 'exception ' => new InvalidArgumentException (sprintf ('Values for field "%s" are not valid according to the doctrine type. ' , $ field )),
264
+ ]);
265
+
266
+ return ;
267
+ }
250
268
251
269
$ association = $ field ;
252
270
$ valueParameter = $ queryNameGenerator ->generateParameterName ($ association );
253
271
254
272
if ($ metadata ->isCollectionValuedAssociation ($ association )) {
255
273
$ associationAlias = QueryBuilderHelper::addJoinOnce ($ queryBuilder , $ queryNameGenerator , $ alias , $ association );
256
- $ associationField = 'id ' ;
274
+ $ targetClass = $ metadata ->getAssociationTargetClass ($ association );
275
+ $ associationMetadata = $ this ->getClassMetadata ($ targetClass );
276
+
277
+ try {
278
+ $ associationField = $ associationMetadata ->getSingleIdentifierFieldName ();
279
+ } catch (MappingException $ e ) {
280
+ $ this ->logger ->notice ('Invalid filter ignored ' , [
281
+ 'exception ' => new InvalidArgumentException (sprintf ('"%s" association has a composite identifier which is not supported. ' , $ association )),
282
+ ]);
283
+
284
+ return ;
285
+ }
257
286
} else {
258
287
$ associationAlias = $ alias ;
259
288
$ associationField = $ field ;
@@ -347,10 +376,14 @@ protected function createWrapCase(bool $caseSensitive): \Closure
347
376
*
348
377
* @param string $value
349
378
*
379
+ * @deprecated The "getValueIdentifiers" method should be used instead. Indeed this method has a hard-coded `id` property.
380
+ *
350
381
* @return mixed
351
382
*/
352
383
protected function getIdFromValue (string $ value )
353
384
{
385
+ @trigger_error (sprintf ('The method "%s"::"%s" is deprecated since API Platform 2.2 and will be removed in API Platform 3. Please use "getValueIdentifiers" instead. ' , __CLASS__ , __METHOD__ ), E_USER_DEPRECATED );
386
+
354
387
try {
355
388
if ($ item = $ this ->iriConverter ->getItemFromIri ($ value , ['fetch_data ' => false ])) {
356
389
return $ this ->propertyAccessor ->getValue ($ item , 'id ' );
@@ -362,6 +395,42 @@ protected function getIdFromValue(string $value)
362
395
return $ value ;
363
396
}
364
397
398
+ /**
399
+ * Transforms IRI to raw identifiers if possible.
400
+ */
401
+ protected function getValueIdentifiers (array $ values , string $ field ): array
402
+ {
403
+ foreach ($ values as $ key => $ value ) {
404
+ if (is_numeric ($ value )) {
405
+ continue ;
406
+ }
407
+
408
+ try {
409
+ $ identifiers = $ this ->iriConverter ->getIdentifiersFromIri ($ value , ['fetch_data ' => false ]);
410
+ $ values [$ key ] = $ identifiers [$ field ] ?? reset ($ identifiers ); //an assumption is being made for composite identifiers
411
+ } catch (InvalidArgumentException $ e ) {
412
+ // Do nothing, return the raw value
413
+ }
414
+ }
415
+
416
+ return $ values ;
417
+ }
418
+
419
+ /**
420
+ * When the field should be an integer, check that the given value is a valid one.
421
+ */
422
+ protected function hasValidValues (array $ values , string $ field , ClassMetadata $ metadata ): bool
423
+ {
424
+ foreach ($ values as $ key => $ value ) {
425
+ $ type = $ metadata ->getTypeOfField ($ field );
426
+ if (Type::INTEGER === $ type && null !== $ value && false === filter_var ($ value , FILTER_VALIDATE_INT )) {
427
+ return false ;
428
+ }
429
+ }
430
+
431
+ return true ;
432
+ }
433
+
365
434
/**
366
435
* Normalize the values array.
367
436
*
0 commit comments