1313
1414namespace ApiPlatform \Core \Metadata \Property \Factory ;
1515
16+ use ApiPlatform \Core \Api \ResourceClassResolverInterface ;
1617use ApiPlatform \Core \Exception \ResourceClassNotFoundException ;
1718use ApiPlatform \Core \Metadata \Property \PropertyMetadata ;
1819use ApiPlatform \Core \Metadata \Resource \Factory \ResourceMetadataFactoryInterface ;
@@ -29,12 +30,14 @@ final class SerializerPropertyMetadataFactory implements PropertyMetadataFactory
2930 private $ resourceMetadataFactory ;
3031 private $ serializerClassMetadataFactory ;
3132 private $ decorated ;
33+ private $ resourceClassResolver ;
3234
33- public function __construct (ResourceMetadataFactoryInterface $ resourceMetadataFactory , SerializerClassMetadataFactoryInterface $ serializerClassMetadataFactory , PropertyMetadataFactoryInterface $ decorated )
35+ public function __construct (ResourceMetadataFactoryInterface $ resourceMetadataFactory , SerializerClassMetadataFactoryInterface $ serializerClassMetadataFactory , PropertyMetadataFactoryInterface $ decorated, ResourceClassResolverInterface $ resourceClassResolver = null )
3436 {
3537 $ this ->resourceMetadataFactory = $ resourceMetadataFactory ;
3638 $ this ->serializerClassMetadataFactory = $ serializerClassMetadataFactory ;
3739 $ this ->decorated = $ decorated ;
40+ $ this ->resourceClassResolver = $ resourceClassResolver ;
3841 }
3942
4043 /**
@@ -52,14 +55,14 @@ public function create(string $resourceClass, string $property, array $options =
5255
5356 try {
5457 [$ normalizationGroups , $ denormalizationGroups ] = $ this ->getEffectiveSerializerGroups ($ options , $ resourceClass );
55-
56- $ propertyMetadata = $ this ->transformReadWrite ($ propertyMetadata , $ resourceClass , $ property , $ normalizationGroups , $ denormalizationGroups );
57- $ propertyMetadata = $ this ->transformLinkStatus ($ propertyMetadata , $ normalizationGroups , $ denormalizationGroups );
5858 } catch (ResourceClassNotFoundException $ e ) {
59- // No need to check link status if related class is not a resource
59+ // TODO: for input/output classes, the serializer groups must be read from the actual resource class
60+ return $ propertyMetadata ;
6061 }
6162
62- return $ propertyMetadata ;
63+ $ propertyMetadata = $ this ->transformReadWrite ($ propertyMetadata , $ resourceClass , $ property , $ normalizationGroups , $ denormalizationGroups );
64+
65+ return $ this ->transformLinkStatus ($ propertyMetadata , $ normalizationGroups , $ denormalizationGroups );
6366 }
6467
6568 /**
@@ -94,8 +97,6 @@ private function transformReadWrite(PropertyMetadata $propertyMetadata, string $
9497 *
9598 * @param string[]|null $normalizationGroups
9699 * @param string[]|null $denormalizationGroups
97- *
98- * @throws ResourceClassNotFoundException
99100 */
100101 private function transformLinkStatus (PropertyMetadata $ propertyMetadata , array $ normalizationGroups = null , array $ denormalizationGroups = null ): PropertyMetadata
101102 {
@@ -111,12 +112,18 @@ private function transformLinkStatus(PropertyMetadata $propertyMetadata, array $
111112
112113 $ relatedClass = $ type ->isCollection () && ($ collectionValueType = $ type ->getCollectionValueType ()) ? $ collectionValueType ->getClassName () : $ type ->getClassName ();
113114
114- if (null === $ relatedClass ) {
115- return $ propertyMetadata ->withReadableLink (true )->withWritableLink (true );
115+ // if property is not a resource relation, don't set link status (as it would have no meaning)
116+ if (null === $ relatedClass || !$ this ->isResourceClass ($ relatedClass )) {
117+ return $ propertyMetadata ;
116118 }
117119
118- $ this ->resourceMetadataFactory ->create ($ relatedClass );
119- $ relatedGroups = $ this ->getResourceSerializerGroups ($ relatedClass );
120+ // find the resource class
121+ // this prevents serializer groups on non-resource child class from incorrectly influencing the decision
122+ if (null !== $ this ->resourceClassResolver ) {
123+ $ relatedClass = $ this ->resourceClassResolver ->getResourceClass (null , $ relatedClass );
124+ }
125+
126+ $ relatedGroups = $ this ->getClassSerializerGroups ($ relatedClass );
120127
121128 if (null === $ propertyMetadata ->isReadableLink ()) {
122129 $ propertyMetadata = $ propertyMetadata ->withReadableLink (null !== $ normalizationGroups && !empty (array_intersect ($ normalizationGroups , $ relatedGroups )));
@@ -138,6 +145,8 @@ private function transformLinkStatus(PropertyMetadata $propertyMetadata, array $
138145 * - From metadata of the given operation ("collection_operation_name" and "item_operation_name" keys).
139146 * - From metadata of the current resource.
140147 *
148+ * @throws ResourceClassNotFoundException
149+ *
141150 * @return (string[]|null)[]
142151 */
143152 private function getEffectiveSerializerGroups (array $ options , string $ resourceClass ): array
@@ -174,9 +183,9 @@ private function getEffectiveSerializerGroups(array $options, string $resourceCl
174183 *
175184 * @return string[]
176185 */
177- private function getPropertySerializerGroups (string $ resourceClass , string $ property ): array
186+ private function getPropertySerializerGroups (string $ class , string $ property ): array
178187 {
179- $ serializerClassMetadata = $ this ->serializerClassMetadataFactory ->getMetadataFor ($ resourceClass );
188+ $ serializerClassMetadata = $ this ->serializerClassMetadataFactory ->getMetadataFor ($ class );
180189
181190 foreach ($ serializerClassMetadata ->getAttributesMetadata () as $ serializerAttributeMetadata ) {
182191 if ($ property === $ serializerAttributeMetadata ->getName ()) {
@@ -188,19 +197,34 @@ private function getPropertySerializerGroups(string $resourceClass, string $prop
188197 }
189198
190199 /**
191- * Gets the serializer groups defined in a resource .
200+ * Gets all serializer groups used in a class .
192201 *
193202 * @return string[]
194203 */
195- private function getResourceSerializerGroups (string $ resourceClass ): array
204+ private function getClassSerializerGroups (string $ class ): array
196205 {
197- $ serializerClassMetadata = $ this ->serializerClassMetadataFactory ->getMetadataFor ($ resourceClass );
206+ $ serializerClassMetadata = $ this ->serializerClassMetadataFactory ->getMetadataFor ($ class );
198207
199208 $ groups = [];
200209 foreach ($ serializerClassMetadata ->getAttributesMetadata () as $ serializerAttributeMetadata ) {
201- $ groups += array_flip ($ serializerAttributeMetadata ->getGroups ());
210+ $ groups = array_merge ($ groups , $ serializerAttributeMetadata ->getGroups ());
211+ }
212+
213+ return array_unique ($ groups );
214+ }
215+
216+ private function isResourceClass (string $ class ): bool
217+ {
218+ if (null !== $ this ->resourceClassResolver ) {
219+ return $ this ->resourceClassResolver ->isResourceClass ($ class );
202220 }
203221
204- return array_keys ($ groups );
222+ try {
223+ $ this ->resourceMetadataFactory ->create ($ class );
224+
225+ return true ;
226+ } catch (ResourceClassNotFoundException $ e ) {
227+ return false ;
228+ }
205229 }
206230}
0 commit comments