api-platform / core

The server component of API Platform: hypermedia and GraphQL APIs in minutes
https://api-platform.com
MIT License
2.45k stars 876 forks source link

Upgrade symfony/framework-bundle 6.4.4 => 6.4.6 broke BackedEnum item serialization #6279

Closed ambroisemaupate closed 7 months ago

ambroisemaupate commented 7 months ago

After packages upgrade (Symfony and Api PLatform) all Enums resource serialization broke leading in Expected normalized relation to be an IRI, array, \\ArrayObject or null error for relations with Enum normalization.

I don't know if it's related to Api Platform or Symfony? Maybe related to #6264

Resource

#[
    ApiResource(normalizationContext: ['groups' => ['get']]),
    GetCollection(provider: Availability::class . '::getCases'),
    Get(provider: Availability::class . '::getCase')
]
enum Availability: int
{
    case AVAILABLE = 0;
    case LAST_SEATS = 10;
    case NO_VACANCY = 20;
    case CANCELLED = 30;
    case POSTPONED = 40;
    case PROGRAMMING_IN_PROGRESS = 50;
    case PARTNER_SHOP_ONLY = 60;

    public static function values(): array
    {
        return array_map(
            fn (Availability $feature) => $feature->value,
            self::cases()
        );
    }

    public function getId(): string
    {
        return $this->name;
    }

    #[
        Groups(['get'])
    ]
    public function getValue(): int
    {
        return $this->value;
    }

    public static function getCases(): array
    {
        return self::cases();
    }

    public static function getCase(Operation $operation, array $uriVariables): ?self
    {
        return self::tryFrom((int) $uriVariables['id']);
    }
}

Symptoms:

Enum operations do not output Enum normalized objects anymore, but their raw value.

- Upgrading php-http/discovery (1.19.2 => 1.19.4): Extracting archive
- Upgrading symfony/translation-contracts (v3.4.1 => v3.4.2): Extracting archive
- Upgrading symfony/serializer (v6.4.4 => v6.4.6): Extracting archive
- Upgrading symfony/property-info (v6.4.3 => v6.4.6): Extracting archive
- Upgrading symfony/property-access (v6.4.4 => v6.4.6): Extracting archive
- Upgrading symfony/event-dispatcher-contracts (v2.5.2 => v2.5.3): Extracting archive
- Upgrading symfony/var-dumper (v6.4.4 => v6.4.6): Extracting archive
- Upgrading symfony/error-handler (v6.4.4 => v6.4.6): Extracting archive
- Upgrading symfony/http-kernel (v6.4.5 => v6.4.6): Extracting archive
- Upgrading api-platform/core (v3.2.18 => v3.2.19): Extracting archive
- Upgrading symfony/service-contracts (v3.4.1 => v3.4.2): Extracting archive
- Upgrading symfony/console (v6.4.4 => v6.4.6): Extracting archive
- Upgrading symfony/routing (v6.4.5 => v6.4.6): Extracting archive
- Upgrading symfony/filesystem (v6.4.3 => v6.4.6): Extracting archive
- Upgrading symfony/var-exporter (v6.4.4 => v6.4.6): Extracting archive
- Upgrading symfony/dependency-injection (v6.4.4 => v6.4.6): Extracting archive
- Upgrading symfony/config (v6.4.4 => v6.4.6): Extracting archive
- Upgrading symfony/cache-contracts (v3.4.0 => v3.4.2): Extracting archive
- Upgrading symfony/cache (v6.4.4 => v6.4.6): Extracting archive
- Upgrading symfony/framework-bundle (v6.4.4 => v6.4.6): Extracting archive
- Upgrading symfony/doctrine-bridge (v6.4.5 => v6.4.6): Extracting archive
- Upgrading symfony/security-bundle (v6.4.5 => v6.4.6): Extracting archive
- Upgrading phpstan/phpstan (1.10.65 => 1.10.66): Extracting archive
- Upgrading symfony/mime (v6.4.3 => v6.4.6): Extracting archive
- Upgrading symfony/validator (v6.4.4 => v6.4.6): Extracting archive
- Upgrading symfony/form (v6.4.4 => v6.4.6): Extracting archive
- Upgrading symfony/twig-bridge (v6.4.4 => v6.4.6): Extracting archive
- Upgrading symfony/proxy-manager-bridge (v6.4.3 => v6.4.6): Extracting archive
- Upgrading symfony/messenger (v6.4.4 => v6.4.6): Extracting archive
- Upgrading symfony/mailer (v6.4.4 => v6.4.6): Extracting archive
- Upgrading symfony/lock (v6.4.3 => v6.4.6): Extracting archive
- Upgrading symfony/http-client-contracts (v3.4.0 => v3.4.2): Extracting archive
- Upgrading symfony/http-client (v6.4.5 => v6.4.6): Extracting archive
- Upgrading symfony/psr-http-message-bridge (v6.4.3 => v6.4.6): Extracting archive
- Upgrading squizlabs/php_codesniffer (3.9.0 => 3.9.1): Extracting archive
- Upgrading symfony/doctrine-messenger (v6.4.4 => v6.4.6): Extracting archive
- Upgrading symfony/phpunit-bridge (v6.4.4 => v6.4.6): Extracting archive
soyuka commented 7 months ago

https://github.com/api-platform/core/issues/6264 ?

ambroisemaupate commented 7 months ago

I try to revert one package at one to find which one is faulty.

ambroisemaupate commented 7 months ago

This is related to symfony/symfony/pull/54315

If I set serializer.normalizer.backed_enum service tag priority back to -915 issue is resolved.

ambroisemaupate commented 7 months ago

Before change:

Symfony Container Services Tagged with "serializer.normalizer" Tag
==================================================================

 ------------------------------------------------------------------------------- ---------- ------------------------------------------------------------------------------------------- 
  Service ID                                                                      priority   Class name                                                                                 
 ------------------------------------------------------------------------------- ---------- ------------------------------------------------------------------------------------------- 
  serializer.denormalizer.unwrapping                                              1000       Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer                             
  RZ\Roadiz\CoreBundle\Serializer\Normalizer\RealmSerializationGroupNormalizer    64         RZ\Roadiz\CoreBundle\Serializer\Normalizer\RealmSerializationGroupNormalizer               
   (same service as previous, another tag)                                                                                                                                              
  RZ\Roadiz\CoreBundle\Serializer\Normalizer\TranslationAwareNormalizer                      RZ\Roadiz\CoreBundle\Serializer\Normalizer\TranslationAwareNormalizer                      
  api_platform.serializer.uuid_denormalizer                                                  ApiPlatform\RamseyUuid\Serializer\UuidDenormalizer                                         
  api_platform.openapi.normalizer.legacy                                                     ApiPlatform\OpenApi\Serializer\LegacyOpenApiNormalizer                                     
  Limenius\Liform\Serializer\Normalizer\FormErrorNormalizer                       -10        Limenius\Liform\Serializer\Normalizer\FormErrorNormalizer                                  
  Limenius\Liform\Serializer\Normalizer\InitialValuesNormalizer                   -10        Limenius\Liform\Serializer\Normalizer\InitialValuesNormalizer                              
  api_platform.normalizer.constraint_violation_list                               -780       ApiPlatform\Serializer\ConstraintViolationListNormalizer                                   
  api_platform.hydra.normalizer.constraint_violation_list                         -780       ApiPlatform\Hydra\Serializer\ConstraintViolationListNormalizer                             
  api_platform.problem.normalizer.constraint_violation_list                       -780       ApiPlatform\Problem\Serializer\ConstraintViolationListNormalizer                           
  api_platform.graphql.normalizer.validation_exception                            -780       ApiPlatform\GraphQl\Serializer\Exception\ValidationExceptionNormalizer                     
  api_platform.graphql.normalizer.http_exception                                  -780       ApiPlatform\GraphQl\Serializer\Exception\HttpExceptionNormalizer                           
  api_platform.graphql.normalizer.runtime_exception                               -780       ApiPlatform\GraphQl\Serializer\Exception\RuntimeExceptionNormalizer                        
  api_platform.graphql.normalizer.error                                           -790       ApiPlatform\GraphQl\Serializer\Exception\ErrorNormalizer                                   
  api_platform.jsonld.normalizer.validation_exception                             -800       ApiPlatform\Symfony\Validator\Serializer\ValidationExceptionNormalizer                     
  api_platform.hydra.normalizer.documentation                                     -800       ApiPlatform\Hydra\Serializer\DocumentationNormalizer                                       
  api_platform.hydra.normalizer.entrypoint                                        -800       ApiPlatform\Hydra\Serializer\EntrypointNormalizer                                          
  api_platform.hydra.normalizer.error                                             -800       ApiPlatform\Hydra\Serializer\ErrorNormalizer                                               
  api_platform.serializer.normalizer.validation_exception                         -800       ApiPlatform\Symfony\Validator\Serializer\ValidationExceptionNormalizer                     
  api_platform.problem.normalizer.error                                           -810       ApiPlatform\Problem\Serializer\ErrorNormalizer                                             
  serializer.normalizer.flatten_exception                                         -880       Symfony\Component\Messenger\Transport\Serialization\Normalizer\FlattenExceptionNormalizer  
  serializer.normalizer.problem                                                   -890       Symfony\Component\Serializer\Normalizer\ProblemNormalizer                                  
  serializer.normalizer.uid                                                       -890       Symfony\Component\Serializer\Normalizer\UidNormalizer                                      
  serializer.normalizer.translatable                                              -890       Symfony\Component\Serializer\Normalizer\TranslatableNormalizer                             
  api_platform.graphql.normalizer.item                                            -890       ApiPlatform\GraphQl\Serializer\ItemNormalizer                                              
  RZ\EventsApiModelsBundle\Serializer\Normalizer\EventIncludedInEventNormalizer   -890       RZ\EventsApiModelsBundle\Serializer\Normalizer\EventIncludedInEventNormalizer              
  rz_events_api_models.serializer.normalizer.event_included_in_event.json         -895       RZ\EventsApiModelsBundle\Serializer\Normalizer\EventIncludedInEventNormalizer              
  serializer.normalizer.datetime                                                  -910       Symfony\Component\Serializer\Normalizer\DateTimeNormalizer                                 
  serializer.normalizer.constraint_violation_list                                 -915       Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer                  
  serializer.normalizer.mime_message                                              -915       Symfony\Component\Serializer\Normalizer\MimeMessageNormalizer                              
  serializer.normalizer.datetimezone                                              -915       Symfony\Component\Serializer\Normalizer\DateTimeZoneNormalizer                             
  serializer.normalizer.dateinterval                                              -915       Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer                             
  serializer.normalizer.form_error                                                -915       Symfony\Component\Serializer\Normalizer\FormErrorNormalizer                                
  serializer.normalizer.backed_enum                                               -915       Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer                               
  serializer.normalizer.data_uri                                                  -920       Symfony\Component\Serializer\Normalizer\DataUriNormalizer                                  
  serializer.normalizer.json_serializable                                         -950       Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer                         
  api_platform.hydra.normalizer.collection_filters                                -985       ApiPlatform\Hydra\Serializer\CollectionFiltersNormalizer                                   
  serializer.denormalizer.array                                                   -990       Symfony\Component\Serializer\Normalizer\ArrayDenormalizer                                  
  api_platform.jsonld.normalizer.object                                           -995       ApiPlatform\JsonLd\Serializer\ObjectNormalizer                                             
  api_platform.graphql.normalizer.object                                          -995       ApiPlatform\GraphQl\Serializer\ObjectNormalizer                                            
  serializer.normalizer.object                                                    -1000      Symfony\Component\Serializer\Normalizer\ObjectNormalizer                                   
 ------------------------------------------------------------------------------- ---------- ------------------------------------------------------------------------------------------- 

after update to 6.4.6:

Symfony Container Services Tagged with "serializer.normalizer" Tag
==================================================================

 ------------------------------------------------------------------------------- ---------- ------------------------------------------------------------------------------------------- 
  Service ID                                                                      priority   Class name                                                                                 
 ------------------------------------------------------------------------------- ---------- ------------------------------------------------------------------------------------------- 
  serializer.denormalizer.unwrapping                                              1000       Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer                             
  RZ\Roadiz\CoreBundle\Serializer\Normalizer\RealmSerializationGroupNormalizer    64         RZ\Roadiz\CoreBundle\Serializer\Normalizer\RealmSerializationGroupNormalizer               
   (same service as previous, another tag)                                                                                                                                              
  RZ\Roadiz\CoreBundle\Serializer\Normalizer\TranslationAwareNormalizer                      RZ\Roadiz\CoreBundle\Serializer\Normalizer\TranslationAwareNormalizer                      
  api_platform.serializer.uuid_denormalizer                                                  ApiPlatform\RamseyUuid\Serializer\UuidDenormalizer                                         
  api_platform.openapi.normalizer.legacy                                                     ApiPlatform\OpenApi\Serializer\LegacyOpenApiNormalizer                                     
  Limenius\Liform\Serializer\Normalizer\FormErrorNormalizer                       -10        Limenius\Liform\Serializer\Normalizer\FormErrorNormalizer                                  
  Limenius\Liform\Serializer\Normalizer\InitialValuesNormalizer                   -10        Limenius\Liform\Serializer\Normalizer\InitialValuesNormalizer                              
  api_platform.normalizer.constraint_violation_list                               -780       ApiPlatform\Serializer\ConstraintViolationListNormalizer                                   
  api_platform.hydra.normalizer.constraint_violation_list                         -780       ApiPlatform\Hydra\Serializer\ConstraintViolationListNormalizer                             
  api_platform.problem.normalizer.constraint_violation_list                       -780       ApiPlatform\Problem\Serializer\ConstraintViolationListNormalizer                           
  api_platform.graphql.normalizer.validation_exception                            -780       ApiPlatform\GraphQl\Serializer\Exception\ValidationExceptionNormalizer                     
  api_platform.graphql.normalizer.http_exception                                  -780       ApiPlatform\GraphQl\Serializer\Exception\HttpExceptionNormalizer                           
  api_platform.graphql.normalizer.runtime_exception                               -780       ApiPlatform\GraphQl\Serializer\Exception\RuntimeExceptionNormalizer                        
  api_platform.graphql.normalizer.error                                           -790       ApiPlatform\GraphQl\Serializer\Exception\ErrorNormalizer                                   
  api_platform.jsonld.normalizer.validation_exception                             -800       ApiPlatform\Symfony\Validator\Serializer\ValidationExceptionNormalizer                     
  api_platform.hydra.normalizer.documentation                                     -800       ApiPlatform\Hydra\Serializer\DocumentationNormalizer                                       
  api_platform.hydra.normalizer.entrypoint                                        -800       ApiPlatform\Hydra\Serializer\EntrypointNormalizer                                          
  api_platform.hydra.normalizer.error                                             -800       ApiPlatform\Hydra\Serializer\ErrorNormalizer                                               
  api_platform.serializer.normalizer.validation_exception                         -800       ApiPlatform\Symfony\Validator\Serializer\ValidationExceptionNormalizer                     
  api_platform.problem.normalizer.error                                           -810       ApiPlatform\Problem\Serializer\ErrorNormalizer                                             
  serializer.normalizer.flatten_exception                                         -880       Symfony\Component\Messenger\Transport\Serialization\Normalizer\FlattenExceptionNormalizer  
  serializer.normalizer.problem                                                   -890       Symfony\Component\Serializer\Normalizer\ProblemNormalizer                                  
  serializer.normalizer.uid                                                       -890       Symfony\Component\Serializer\Normalizer\UidNormalizer                                      
  serializer.normalizer.translatable                                              -890       Symfony\Component\Serializer\Normalizer\TranslatableNormalizer                             
  serializer.normalizer.backed_enum                                               -890       Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer                               
  api_platform.graphql.normalizer.item                                            -890       ApiPlatform\GraphQl\Serializer\ItemNormalizer                                              
  RZ\EventsApiModelsBundle\Serializer\Normalizer\EventIncludedInEventNormalizer   -890       RZ\EventsApiModelsBundle\Serializer\Normalizer\EventIncludedInEventNormalizer              
  rz_events_api_models.serializer.normalizer.event_included_in_event.json         -895       RZ\EventsApiModelsBundle\Serializer\Normalizer\EventIncludedInEventNormalizer              
  serializer.normalizer.datetime                                                  -910       Symfony\Component\Serializer\Normalizer\DateTimeNormalizer                                 
  serializer.normalizer.constraint_violation_list                                 -915       Symfony\Component\Serializer\Normalizer\ConstraintViolationListNormalizer                  
  serializer.normalizer.mime_message                                              -915       Symfony\Component\Serializer\Normalizer\MimeMessageNormalizer                              
  serializer.normalizer.datetimezone                                              -915       Symfony\Component\Serializer\Normalizer\DateTimeZoneNormalizer                             
  serializer.normalizer.dateinterval                                              -915       Symfony\Component\Serializer\Normalizer\DateIntervalNormalizer                             
  serializer.normalizer.form_error                                                -915       Symfony\Component\Serializer\Normalizer\FormErrorNormalizer                                
  serializer.normalizer.data_uri                                                  -920       Symfony\Component\Serializer\Normalizer\DataUriNormalizer                                  
  serializer.normalizer.json_serializable                                         -950       Symfony\Component\Serializer\Normalizer\JsonSerializableNormalizer                         
  api_platform.hydra.normalizer.collection_filters                                -985       ApiPlatform\Hydra\Serializer\CollectionFiltersNormalizer                                   
  serializer.denormalizer.array                                                   -990       Symfony\Component\Serializer\Normalizer\ArrayDenormalizer                                  
  api_platform.jsonld.normalizer.object                                           -995       ApiPlatform\JsonLd\Serializer\ObjectNormalizer                                             
  api_platform.graphql.normalizer.object                                          -995       ApiPlatform\GraphQl\Serializer\ObjectNormalizer                                            
  serializer.normalizer.object                                                    -1000      Symfony\Component\Serializer\Normalizer\ObjectNormalizer                                   
 ------------------------------------------------------------------------------- ---------- ------------------------------------------------------------------------------------------- 
ambroisemaupate commented 7 months ago
serializer.normalizer.backed_enum                                               -880       Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer                               
serializer.normalizer.flatten_exception                                         -880       Symfony\Component\Messenger\Transport\Serialization\Normalizer\FlattenExceptionNormalizer  
serializer.normalizer.problem                                                   -890       Symfony\Component\Serializer\Normalizer\ProblemNormalizer                                  
serializer.normalizer.uid                                                       -890       Symfony\Component\Serializer\Normalizer\UidNormalizer                                      
serializer.normalizer.translatable                                              -890       Symfony\Component\Serializer\Normalizer\TranslatableNormalizer                             
api_platform.graphql.normalizer.item                                            -890       ApiPlatform\GraphQl\Serializer\ItemNormalizer         

The problem here is that api_platform.graphql.normalizer.item has lower priority than serializer.normalizer.backed_enum

If I set serializer.normalizer.backed_enum to priority -891 problem is solved

soyuka commented 7 months ago

should we move up graphql?

ambroisemaupate commented 7 months ago

I think that related to all *.normalizer.item. Not only graphql.

If I set all *normalizer.item services to -878 and api_platform.serializer.normalizer.item to -879. That could fix this. But I don't know if that could break other priorities.

api_platform.serializer.normalizer.validation_exception                         -800       ApiPlatform\Symfony\Validator\Serializer\ValidationExceptionNormalizer                     
api_platform.problem.normalizer.error                                           -810       ApiPlatform\Problem\Serializer\ErrorNormalizer                                             
api_platform.graphql.normalizer.item                                            -878       ApiPlatform\GraphQl\Serializer\ItemNormalizer                                              
RZ\EventsApiModelsBundle\Serializer\Normalizer\EventIncludedInEventNormalizer   -878       RZ\EventsApiModelsBundle\Serializer\Normalizer\EventIncludedInEventNormalizer              
rz_events_api_models.serializer.normalizer.event_included_in_event.json         -879       RZ\EventsApiModelsBundle\Serializer\Normalizer\EventIncludedInEventNormalizer              
serializer.normalizer.backed_enum                                               -880       Symfony\Component\Serializer\Normalizer\BackedEnumNormalizer                               
serializer.normalizer.flatten_exception                                         -880       Symfony\Component\Messenger\Transport\Serialization\Normalizer\FlattenExceptionNormalizer  

I imagine all Api Platform item normalizer should be higher priority than Symfony normalizers

soyuka commented 7 months ago

thanks, let me try that

GwendolenLynch commented 7 months ago

I was having a look too, and here is a branch with a functional test for backed enum resources based on this https://github.com/GwendolenLynch/api-platform-core/tree/fix/issues/6264

Passes with the priority set at -915 and fails at -880 so hopefully an easy test case.

soyuka commented 7 months ago

I have an ongoing work to support enums with hydra it's a bit complex RDF-wise (https://github.com/soyuka/core/commit/48db6fa3e63f2207048069db99c022898860f5e1) for now I think that only graphql supports them.

GwendolenLynch commented 7 months ago

Yeah, I have added a personal TODO for JSON:API based on this, but just base testing on JSON works in a basic sense (hence the target of the tests above), but now breaks with the upstream change.

ambroisemaupate commented 7 months ago

Why closing this issue. Problem occurs for all item normalizers: JSON, JSONLD and GraphQL

soyuka commented 7 months ago

Because API Platform does not support enums as resources except for GraphQl, I see this out of my scope although it is planned for API Platform 3.4. Now https://github.com/api-platform/core/issues/6285#issuecomment-2035442991 is a valid solution until it's reverted inside Symfony. Or feel free to provide a fix but moving our normalizer priorities is hard.

ambroisemaupate commented 7 months ago

Because API Platform does not support enums as resources except for GraphQl, I see this out of my scope although it is planned for API Platform 3.4. Now #6285 (comment) is a valid solution until it's reverted inside Symfony. Or feel free to provide a fix but moving our normalizer priorities is hard.

Ok, I thought this was kind of official since a read this article https://les-tilleuls.coop/blog/exposez-vos-enums-avec-api-platform. My bad

soyuka commented 7 months ago

Mhh I completely missed this @ambroisemaupate my bad as I need enums on API P this month and found out it wasn't working properly...

Anyways, a fix has come to symfony regarding this issue, let's see what's wrong at #6298 as I think that something is definitely off regarding the support of enums.