2323
2424namespace App \Serializer \APIPlatform ;
2525
26+ use ApiPlatform \Metadata \Exception \InvalidArgumentException ;
27+ use ApiPlatform \Metadata \Exception \ItemNotFoundException ;
28+ use ApiPlatform \Metadata \IriConverterInterface ;
2629use ApiPlatform \Serializer \ItemNormalizer ;
2730use Symfony \Component \DependencyInjection \Attribute \AsDecorator ;
28- use Symfony \Component \Serializer \Normalizer \CacheableSupportsMethodInterface ;
31+ use Symfony \Component \Serializer \Exception \NotNormalizableValueException ;
32+ use Symfony \Component \Serializer \Exception \UnexpectedValueException ;
2933use Symfony \Component \Serializer \Normalizer \DenormalizerInterface ;
3034use Symfony \Component \Serializer \Normalizer \NormalizerInterface ;
3135use Symfony \Component \Serializer \SerializerAwareInterface ;
3539 * This class decorates API Platform's ItemNormalizer to allow skipping the normalization process by setting the
3640 * DISABLE_ITEM_NORMALIZER context key to true. This is useful for all kind of serialization operations, where the API
3741 * Platform subsystem should not be used.
42+ *
43+ * It also works around a bug in API Platform's AbstractItemNormalizer where IRI strings for abstract resource classes
44+ * with a discriminator map fail deserialization when objectToPopulate is null (the discriminator is checked before
45+ * the IRI string check). See: https://github.com/Part-DB/Part-DB-server/issues/1370
3846 */
3947#[AsDecorator("api_platform.serializer.normalizer.item " )]
4048class SkippableItemNormalizer implements NormalizerInterface, DenormalizerInterface, SerializerAwareInterface
4149{
4250
4351 public const DISABLE_ITEM_NORMALIZER = 'DISABLE_ITEM_NORMALIZER ' ;
4452
45- public function __construct (private readonly ItemNormalizer $ inner )
46- {
47-
53+ public function __construct (
54+ private readonly ItemNormalizer $ inner ,
55+ private readonly IriConverterInterface $ iriConverter ,
56+ ) {
4857 }
4958
5059 public function denormalize (mixed $ data , string $ type , ?string $ format = null , array $ context = []): mixed
5160 {
61+ // API Platform's AbstractItemNormalizer has a bug: when objectToPopulate is null and data is an IRI
62+ // string, it tries to resolve the discriminator class from [$iri_string] before reaching the IRI
63+ // check (line 271). For abstract resource classes with a discriminator map (e.g. Attachment), this
64+ // fails because the array has no _type key. Fix by resolving IRI strings directly.
65+ // See: https://github.com/Part-DB/Part-DB-server/issues/1370
66+ if (is_string ($ data )) {
67+ try {
68+ return $ this ->iriConverter ->getResourceFromIri ($ data , $ context + ['fetch_data ' => true ]);
69+ } catch (ItemNotFoundException $ e ) {
70+ if (false === ($ context ['denormalize_throw_on_relation_not_found ' ] ?? true )) {
71+ return null ;
72+ }
73+ if (!isset ($ context ['not_normalizable_value_exceptions ' ])) {
74+ throw new UnexpectedValueException ($ e ->getMessage (), $ e ->getCode (), $ e );
75+ }
76+ throw NotNormalizableValueException::createForUnexpectedDataType ($ e ->getMessage (), $ data , [$ type ], $ context ['deserialization_path ' ] ?? null , true , $ e ->getCode (), $ e );
77+ } catch (InvalidArgumentException $ e ) {
78+ if (!isset ($ context ['not_normalizable_value_exceptions ' ])) {
79+ throw new UnexpectedValueException (sprintf ('Invalid IRI "%s". ' , $ data ), $ e ->getCode (), $ e );
80+ }
81+ throw NotNormalizableValueException::createForUnexpectedDataType (sprintf ('Invalid IRI "%s". ' , $ data ), $ data , [$ type ], $ context ['deserialization_path ' ] ?? null , true , $ e ->getCode (), $ e );
82+ }
83+ }
84+
5285 return $ this ->inner ->denormalize ($ data , $ type , $ format , $ context );
5386 }
5487
@@ -87,4 +120,4 @@ public function getSupportedTypes(?string $format): array
87120 'object ' => false
88121 ];
89122 }
90- }
123+ }
0 commit comments