@@ -376,6 +376,100 @@ public function testQueryParameterFromPropertyAttributeThrowsExceptionWhenProper
376376 $ parameterFactory ->create (ParameterOnPropertiesMismatchPropertiesException::class);
377377 }
378378
379+ public function testQueryParameterFromPropertyAttributeThrowsExceptionWhenPropertiesHasMultipleWithoutSelf (): void
380+ {
381+ $ this ->expectException (RuntimeException::class);
382+ $ this ->expectExceptionMessage ('Parameter attribute on property "name" must target itself or have no explicit properties. Got "properties: [description, active]" instead. ' );
383+
384+ $ nameCollection = $ this ->createStub (PropertyNameCollectionFactoryInterface::class);
385+ $ nameCollection ->method ('create ' )->willReturn (new PropertyNameCollection (['id ' , 'name ' , 'description ' , 'active ' ]));
386+
387+ $ propertyMetadata = $ this ->createStub (PropertyMetadataFactoryInterface::class);
388+ $ propertyMetadata ->method ('create ' )->willReturn (
389+ new ApiProperty (readable: true ),
390+ );
391+
392+ $ filterLocator = $ this ->createStub (ContainerInterface::class);
393+ $ filterLocator ->method ('has ' )->willReturn (false );
394+
395+ $ parameterFactory = new ParameterResourceMetadataCollectionFactory (
396+ $ nameCollection ,
397+ $ propertyMetadata ,
398+ new AttributesResourceMetadataCollectionFactory (),
399+ $ filterLocator
400+ );
401+
402+ $ parameterFactory ->create (ParameterOnPropertiesMismatchMultiplePropertiesException::class);
403+ }
404+
405+ public function testQueryParameterFromPropertyAttributePropertiesSingleCorrectProperty (): void
406+ {
407+ $ nameCollection = $ this ->createStub (PropertyNameCollectionFactoryInterface::class);
408+ $ nameCollection ->method ('create ' )->willReturn (new PropertyNameCollection (['id ' , 'name ' , 'description ' ]));
409+
410+ $ propertyMetadata = $ this ->createStub (PropertyMetadataFactoryInterface::class);
411+ $ propertyMetadata ->method ('create ' )->willReturn (
412+ new ApiProperty (readable: true ),
413+ );
414+
415+ $ filterLocator = $ this ->createStub (ContainerInterface::class);
416+ $ filterLocator ->method ('has ' )->willReturn (false );
417+
418+ $ parameterFactory = new ParameterResourceMetadataCollectionFactory (
419+ $ nameCollection ,
420+ $ propertyMetadata ,
421+ new AttributesResourceMetadataCollectionFactory (),
422+ $ filterLocator
423+ );
424+
425+ $ resourceMetadataCollection = $ parameterFactory ->create (ParameterOnPropertiesSingleCorrectProperty::class);
426+ $ operation = $ resourceMetadataCollection ->getOperation (forceCollection: true );
427+ $ parameters = $ operation ->getParameters ();
428+
429+ $ this ->assertInstanceOf (Parameters::class, $ parameters );
430+
431+ $ this ->assertTrue ($ parameters ->has ('search ' ));
432+ $ searchParam = $ parameters ->get ('search ' , QueryParameter::class);
433+ $ this ->assertInstanceOf (QueryParameter::class, $ searchParam );
434+ $ this ->assertSame ('search ' , $ searchParam ->getKey ());
435+ $ this ->assertSame ('name ' , $ searchParam ->getProperty ());
436+ $ this ->assertSame (['name ' ], $ searchParam ->getProperties ());
437+ }
438+
439+ public function testQueryParameterFromPropertyAttributePropertiesHasMultipleIncludingSelf (): void
440+ {
441+ $ nameCollection = $ this ->createStub (PropertyNameCollectionFactoryInterface::class);
442+ $ nameCollection ->method ('create ' )->willReturn (new PropertyNameCollection (['id ' , 'name ' , 'description ' ]));
443+
444+ $ propertyMetadata = $ this ->createStub (PropertyMetadataFactoryInterface::class);
445+ $ propertyMetadata ->method ('create ' )->willReturn (
446+ new ApiProperty (readable: true ),
447+ );
448+
449+ $ filterLocator = $ this ->createStub (ContainerInterface::class);
450+ $ filterLocator ->method ('has ' )->willReturn (false );
451+
452+ $ parameterFactory = new ParameterResourceMetadataCollectionFactory (
453+ $ nameCollection ,
454+ $ propertyMetadata ,
455+ new AttributesResourceMetadataCollectionFactory (),
456+ $ filterLocator
457+ );
458+
459+ $ resourceMetadataCollection = $ parameterFactory ->create (ParameterOnPropertiesMultiplePropertiesIncludingSelf::class);
460+ $ operation = $ resourceMetadataCollection ->getOperation (forceCollection: true );
461+ $ parameters = $ operation ->getParameters ();
462+
463+ $ this ->assertInstanceOf (Parameters::class, $ parameters );
464+
465+ $ this ->assertTrue ($ parameters ->has ('search ' ));
466+ $ searchParam = $ parameters ->get ('search ' , QueryParameter::class);
467+ $ this ->assertInstanceOf (QueryParameter::class, $ searchParam );
468+ $ this ->assertSame ('search ' , $ searchParam ->getKey ());
469+ $ this ->assertSame ('name ' , $ searchParam ->getProperty ());
470+ $ this ->assertSame (['name ' ], $ searchParam ->getProperties ());
471+ }
472+
379473 public function testNestedPropertyWithNameConverter (): void
380474 {
381475 $ nameCollection = $ this ->createStub (PropertyNameCollectionFactoryInterface::class);
@@ -660,4 +754,31 @@ class ParameterOnPropertiesMismatchPropertiesException
660754 public string $ description = '' ;
661755}
662756
757+ #[ApiResource]
758+ class ParameterOnPropertiesMismatchMultiplePropertiesException
759+ {
760+ #[QueryParameter(key: 'search ' , properties: ['description ' , 'active ' ])]
761+ public string $ name = '' ;
762+
763+ public string $ description = '' ;
764+
765+ public bool $ active = true ;
766+ }
767+
768+ #[ApiResource]
769+ class ParameterOnPropertiesSingleCorrectProperty
770+ {
771+ #[QueryParameter(key: 'search ' , properties: ['name ' ])]
772+ public string $ name = '' ;
773+
774+ public string $ description = '' ;
775+ }
663776
777+ #[ApiResource]
778+ class ParameterOnPropertiesMultiplePropertiesIncludingSelf
779+ {
780+ #[QueryParameter(key: 'search ' , properties: ['description ' , 'name ' ])]
781+ public string $ name = '' ;
782+
783+ public string $ description = '' ;
784+ }
0 commit comments