1414namespace ApiPlatform \Api ;
1515
1616use ApiPlatform \Core \Api \ResourceClassResolverInterface ;
17+ use ApiPlatform \Core \Identifier \CompositeIdentifierParser ;
1718use ApiPlatform \Core \Metadata \Property \Factory \PropertyMetadataFactoryInterface ;
1819use ApiPlatform \Core \Metadata \Property \Factory \PropertyNameCollectionFactoryInterface ;
1920use ApiPlatform \Core \Util \ResourceClassInfoTrait ;
@@ -55,45 +56,30 @@ public function getIdentifiersFromItem($item, string $operationName = null, arra
5556 $ resourceClass = $ this ->getResourceClass ($ item , true );
5657 $ operation = $ context ['operation ' ] ?? $ this ->resourceMetadataFactory ->create ($ resourceClass )->getOperation ($ operationName );
5758
58- foreach ($ operation ->getIdentifiers () as $ parameterName => [ $ class , $ property ] ) {
59- $ identifierValue = $ this -> resolveIdentifierValue ( $ item , $ class , $ property );
60-
61- if ( null === $ identifierValue ) {
62- throw new RuntimeException ( ' No identifier value found, did you forgot to persist the entity? ' );
63- }
59+ foreach ($ operation ->getUriVariables () ?? [] as $ parameterName => $ uriVariableDefinition ) {
60+ if ( 1 < \count ( $ uriVariableDefinition [ ' identifiers ' ])) {
61+ $ compositeIdentifiers = [];
62+ foreach ( $ uriVariableDefinition [ ' identifiers ' ] as $ identifier ) {
63+ $ compositeIdentifiers [ $ identifier] = $ this -> getIdentifierValue ( $ item , $ uriVariableDefinition [ ' class ' ], $ identifier , $ parameterName );
64+ }
6465
65- // TODO: php 8 remove method_exists
66- if (is_scalar ($ identifierValue ) || method_exists ($ identifierValue , '__toString ' ) || $ identifierValue instanceof \Stringable) {
67- $ identifiers [$ parameterName ] = (string ) $ identifierValue ;
66+ $ identifiers [$ parameterName ] = CompositeIdentifierParser::stringify ($ compositeIdentifiers );
6867 continue ;
6968 }
7069
71- // we could recurse to find correct identifiers until there it is a scalar but this is not really supported and adds a lot of complexity
72- // instead we're deprecating this behavior in favor of something that can be transformed to a string
73- if ($ this ->isResourceClass ($ relatedResourceClass = $ this ->getObjectClass ($ identifierValue ))) {
74- trigger_deprecation ('api-platform/core ' , '2.7 ' , 'Using a resource class as identifier is deprecated, please make this identifier Stringable ' );
75- $ relatedOperation = $ this ->resourceMetadataFactory ->create ($ relatedResourceClass )->getOperation ();
76- $ relatedIdentifiers = $ relatedOperation ->getIdentifiers ();
77- if (1 === \count ($ relatedIdentifiers )) {
78- $ identifierValue = $ this ->resolveIdentifierValue ($ identifierValue , $ relatedResourceClass , current ($ relatedIdentifiers )[1 ]);
79-
80- if (is_scalar ($ identifierValue ) || method_exists ($ identifierValue , '__toString ' ) || $ identifierValue instanceof \Stringable) {
81- $ identifiers [$ parameterName ] = (string ) $ identifierValue ;
82- continue ;
83- }
84- }
85- }
86-
87- throw new RuntimeException (sprintf ('We were not able to resolve the identifier matching parameter "%s". ' , $ parameterName ));
70+ $ identifiers [$ parameterName ] = $ this ->getIdentifierValue ($ item , $ uriVariableDefinition ['class ' ], $ uriVariableDefinition ['identifiers ' ][0 ], $ parameterName );
8871 }
8972
9073 return $ identifiers ;
9174 }
9275
93- private function resolveIdentifierValue ($ item , string $ class , string $ property )
76+ /**
77+ * Gets the value of the given class property.
78+ */
79+ private function getIdentifierValue ($ item , string $ class , string $ property , string $ parameterName )
9480 {
9581 if ($ item instanceof $ class ) {
96- return $ this ->propertyAccessor ->getValue ($ item , $ property );
82+ return $ this ->resolveIdentifierValue ( $ this -> propertyAccessor ->getValue ($ item , $ property), $ parameterName );
9783 }
9884
9985 $ resourceClass = $ this ->getResourceClass ($ item , true );
@@ -105,14 +91,49 @@ private function resolveIdentifierValue($item, string $class, string $property)
10591 }
10692
10793 if ($ type ->getClassName () === $ class ) {
108- return $ this ->propertyAccessor ->getValue ($ item , "$ propertyName. $ property " );
94+ return $ this ->resolveIdentifierValue ( $ this -> propertyAccessor ->getValue ($ item , "$ propertyName. $ property " ), $ parameterName );
10995 }
11096
11197 if ($ type ->isCollection () && ($ collectionValueType = $ type ->getCollectionValueType ()) && $ collectionValueType ->getClassName () === $ class ) {
112- return $ this ->propertyAccessor ->getValue ($ item , sprintf ('%s[0].%s ' , $ propertyName , $ property ));
98+ return $ this ->resolveIdentifierValue ( $ this -> propertyAccessor ->getValue ($ item , sprintf ('%s[0].%s ' , $ propertyName , $ property )), $ parameterName );
11399 }
114100 }
115101
116102 throw new RuntimeException ('Not able to retrieve identifiers. ' );
117103 }
104+
105+ /**
106+ * TODO: in 3.0 this method just uses $identifierValue instanceof \Stringable and we remove the weird behavior.
107+ *
108+ * @param mixed|\Stringable $identifierValue
109+ */
110+ private function resolveIdentifierValue ($ identifierValue , string $ parameterName )
111+ {
112+ if (null === $ identifierValue ) {
113+ throw new RuntimeException ('No identifier value found, did you forgot to persist the entity? ' );
114+ }
115+
116+ // TODO: php 8 remove method_exists
117+ if (is_scalar ($ identifierValue ) || method_exists ($ identifierValue , '__toString ' ) || $ identifierValue instanceof \Stringable) {
118+ return (string ) $ identifierValue ;
119+ }
120+
121+ // TODO: remove this in 3.0
122+ // we could recurse to find correct identifiers until there it is a scalar but this is not really supported and adds a lot of complexity
123+ // instead we're deprecating this behavior in favor of something that can be transformed to a string
124+ if ($ this ->isResourceClass ($ relatedResourceClass = $ this ->getObjectClass ($ identifierValue ))) {
125+ trigger_deprecation ('api-platform/core ' , '2.7 ' , 'Using a resource class as identifier is deprecated, please make this identifier Stringable ' );
126+ $ relatedOperation = $ this ->resourceMetadataFactory ->create ($ relatedResourceClass )->getOperation ();
127+ $ relatedIdentifiers = $ relatedOperation ->getUriVariables ();
128+ if (1 === \count ($ relatedIdentifiers )) {
129+ $ identifierValue = $ this ->getIdentifierValue ($ identifierValue , $ relatedResourceClass , current ($ relatedIdentifiers )['identifiers ' ][0 ], $ parameterName );
130+
131+ if ($ identifierValue instanceof \Stringable || is_scalar ($ identifierValue ) || method_exists ($ identifierValue , '__toString ' )) {
132+ return (string ) $ identifierValue ;
133+ }
134+ }
135+ }
136+
137+ throw new RuntimeException (sprintf ('We were not able to resolve the identifier matching parameter "%s". ' , $ parameterName ));
138+ }
118139}
0 commit comments