2828use ApiPlatform \Metadata \Util \ClassInfoTrait ;
2929use ApiPlatform \Serializer \AbstractItemNormalizer ;
3030use ApiPlatform \Serializer \ContextTrait ;
31+ use ApiPlatform \Serializer \OperationResourceResolverInterface ;
3132use ApiPlatform \Serializer \TagCollectorInterface ;
3233use Symfony \Component \PropertyAccess \PropertyAccessorInterface ;
3334use Symfony \Component \Serializer \Exception \LogicException ;
@@ -71,9 +72,9 @@ final class ItemNormalizer extends AbstractItemNormalizer
7172 '@vocab ' ,
7273 ];
7374
74- public function __construct (ResourceMetadataCollectionFactoryInterface $ resourceMetadataCollectionFactory , PropertyNameCollectionFactoryInterface $ propertyNameCollectionFactory , PropertyMetadataFactoryInterface $ propertyMetadataFactory , IriConverterInterface $ iriConverter , ResourceClassResolverInterface $ resourceClassResolver , private readonly ContextBuilderInterface $ contextBuilder , ?PropertyAccessorInterface $ propertyAccessor = null , ?NameConverterInterface $ nameConverter = null , ?ClassMetadataFactoryInterface $ classMetadataFactory = null , array $ defaultContext = [], ?ResourceAccessCheckerInterface $ resourceAccessChecker = null , protected ?TagCollectorInterface $ tagCollector = null , private ?OperationMetadataFactoryInterface $ operationMetadataFactory = null )
75+ public function __construct (ResourceMetadataCollectionFactoryInterface $ resourceMetadataCollectionFactory , PropertyNameCollectionFactoryInterface $ propertyNameCollectionFactory , PropertyMetadataFactoryInterface $ propertyMetadataFactory , IriConverterInterface $ iriConverter , ResourceClassResolverInterface $ resourceClassResolver , private readonly ContextBuilderInterface $ contextBuilder , ?PropertyAccessorInterface $ propertyAccessor = null , ?NameConverterInterface $ nameConverter = null , ?ClassMetadataFactoryInterface $ classMetadataFactory = null , array $ defaultContext = [], ?ResourceAccessCheckerInterface $ resourceAccessChecker = null , protected ?TagCollectorInterface $ tagCollector = null , private ?OperationMetadataFactoryInterface $ operationMetadataFactory = null , ? OperationResourceResolverInterface $ operationResourceResolver = null )
7576 {
76- parent ::__construct ($ propertyNameCollectionFactory , $ propertyMetadataFactory , $ iriConverter , $ resourceClassResolver , $ propertyAccessor , $ nameConverter , $ classMetadataFactory , $ defaultContext , $ resourceMetadataCollectionFactory , $ resourceAccessChecker , $ tagCollector );
77+ parent ::__construct ($ propertyNameCollectionFactory , $ propertyMetadataFactory , $ iriConverter , $ resourceClassResolver , $ propertyAccessor , $ nameConverter , $ classMetadataFactory , $ defaultContext , $ resourceMetadataCollectionFactory , $ resourceAccessChecker , $ tagCollector, $ operationResourceResolver );
7778 }
7879
7980 /**
@@ -109,11 +110,12 @@ public function normalize(mixed $data, ?string $format = null, array $context =
109110 // from the output class (not from the item_uri_template operation).
110111 $ itemUriTemplate = $ context ['item_uri_template ' ];
111112 unset($ context ['item_uri_template ' ]);
113+ $ originalData = $ data ;
112114 $ data = parent ::normalize ($ data , $ format , $ context );
113115 if (\is_array ($ data )) {
114116 try {
115117 $ context ['item_uri_template ' ] = $ itemUriTemplate ;
116- $ data ['@id ' ] = $ this ->iriConverter ->getIriFromResource ($ resourceClass , UrlGeneratorInterface::ABS_PATH , null , $ context );
118+ $ data ['@id ' ] = $ this ->iriConverter ->getIriFromResource ($ originalData , UrlGeneratorInterface::ABS_PATH , null , $ context );
117119 } catch (\Exception ) {
118120 }
119121 }
@@ -139,7 +141,14 @@ public function normalize(mixed $data, ?string $format = null, array $context =
139141 }
140142
141143 if (isset ($ context ['item_uri_template ' ]) && $ this ->operationMetadataFactory ) {
142- $ context ['output ' ]['operation ' ] = $ this ->operationMetadataFactory ->create ($ context ['item_uri_template ' ]);
144+ $ itemOp = $ this ->operationMetadataFactory ->create ($ context ['item_uri_template ' ]);
145+ // Use resource-level shortName for @type, not operation-specific shortName
146+ try {
147+ $ itemResourceShortName = $ this ->resourceMetadataCollectionFactory ->create ($ itemOp ->getClass ())[0 ]->getShortName ();
148+ $ context ['output ' ]['operation ' ] = $ itemOp ->withShortName ($ itemResourceShortName );
149+ } catch (\Exception ) {
150+ $ context ['output ' ]['operation ' ] = $ itemOp ;
151+ }
143152 } elseif ($ this ->resourceClassResolver ->isResourceClass ($ resourceClass )) {
144153 $ context ['output ' ]['operation ' ] = $ this ->resourceMetadataCollectionFactory ->create ($ resourceClass )->getOperation ();
145154 }
@@ -178,7 +187,13 @@ public function normalize(mixed $data, ?string $format = null, array $context =
178187 if (!isset ($ metadata ['@type ' ]) && $ operation ) {
179188 $ types = $ operation instanceof HttpOperation ? $ operation ->getTypes () : null ;
180189 if (null === $ types ) {
181- $ types = [$ operation ->getShortName ()];
190+ // Use resource-level shortName to avoid operation-specific overrides
191+ $ typeClass = $ isResourceClass ? $ resourceClass : ($ operation ->getClass () ?? $ resourceClass );
192+ try {
193+ $ types = [$ this ->resourceMetadataCollectionFactory ->create ($ typeClass )[0 ]->getShortName ()];
194+ } catch (\Exception ) {
195+ $ types = [$ operation ->getShortName ()];
196+ }
182197 }
183198 $ metadata ['@type ' ] = 1 === \count ($ types ) ? $ types [0 ] : $ types ;
184199 }
0 commit comments