Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Serializer] Introduce named serializers #56823

Open
wants to merge 2 commits into
base: 7.2
Choose a base branch
from

Conversation

HypeMC
Copy link
Contributor

@HypeMC HypeMC commented May 20, 2024

Q A
Branch? 7.2
Bug fix? no
New feature? yes
Deprecations? no
Issues -
License MIT

The idea behind this PR is to allow configuring multiple serializer instances with different default contexts, name converters, and sets of normalizers and encoders. This is useful when your application is communicating with multiple APIs, each with different rules. Similar ideas have been mentioned before.

serializer:
  named_serializers:
    api1:
      name_converter: 'serializer.name_converter.camel_case_to_snake_case'
      default_context:
        enable_max_depth: true
    api2:
      default_context:
        enable_max_depth: false

The different serializers can be injected using named aliases:

#[AsController]
class HomeController
{
    #[Route('/', name: 'app_home')]
    public function index(
        SerializerInterface $serializer,     // Default serializer
        SerializerInterface $api1Serializer, // api1 serializer
        #[Target('api2.serializer')]         // api2 serializer
        SerializerInterface $someName,
    ) {
        // ...
    }
}

Multiple normalizer/encoder instances with different arguments are created as child services of the default ones. I also ensured that the same normalizer/encoder instances are reused between different serializers that have the same default context and name converter to minimize the number of child services created.

Custom normalizers/encoders can target specific serializers using the serializer tag attribute:

get_set_method_normalizer:
    class: Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer
    autoconfigure: false # This is needed so that it's not included in the default serializer
    tags:
        # single serializer
        - serializer.normalizer: { serializer: 'api1' }
        # or multiple ones
        - serializer.normalizer: { serializer: [ 'api1', 'api2' ] }
        # use * to include the service in all serializers including the default one
        - serializer.normalizer: { serializer: '*' }

For BC reasons, not setting the serializer tag attribute is the same as setting it to the default one:

    tags:
        - serializer.normalizer
        # same as
        - serializer.normalizer: { serializer: 'default' }

The profiler has been updated to support multiple serializer instances:

image

All normalizers/encoders are tagged with additional named serializer specific tags to help with debugging. To get the priority of normalizers/encoders for a certain serializer, use the serializer.normalizer.<name> or serializer.encoder.<name> tags:

$ bin/console debug:container --tag serializer.normalizer.default
Symfony Container Services Tagged with "serializer.normalizer.default" Tag
==========================================================================

 ------------------------------------------------- ---------- ------------------------------------------------------------------------------------------- 
  Service ID                                        priority   Class name                                                                                 
 ------------------------------------------------- ---------- ------------------------------------------------------------------------------------------- 
  serializer.denormalizer.unwrapping                1000       Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer                             
  serializer.normalizer.flatten_exception           -880       Symfony\Component\Messenger\Transport\Serialization\Normalizer\FlattenExceptionNormalizer  
  ...
  serializer.denormalizer.array                     -990       Symfony\Component\Serializer\Normalizer\ArrayDenormalizer                                  
  serializer.normalizer.object                      -1000      Symfony\Component\Serializer\Normalizer\ObjectNormalizer                                   
 ------------------------------------------------- ---------- ------------------------------------------------------------------------------------------- 

$ bin/console debug:container --tag serializer.normalizer.api1

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

 ------------------------------------------------- ---------- ------------------------------------------------------------------------------------------- 
  Service ID                                        priority   Class name                                                                                 
 ------------------------------------------------- ---------- ------------------------------------------------------------------------------------------- 
  serializer.denormalizer.unwrapping                1000       Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer                             
  get_set_method_normalizer                                    Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer                             
  serializer.normalizer.flatten_exception           -880       Symfony\Component\Messenger\Transport\Serialization\Normalizer\FlattenExceptionNormalizer  
  ...
  serializer.denormalizer.array                     -990       Symfony\Component\Serializer\Normalizer\ArrayDenormalizer                                  
  serializer.normalizer.object                      -1000      Symfony\Component\Serializer\Normalizer\ObjectNormalizer                                   
 ------------------------------------------------- ---------- ------------------------------------------------------------------------------------------- 

$ bin/console debug:container --tag serializer.normalizer

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

 ------------------------------------------------- ---------- ---------- ----------------- ------------------------------------------------------------------------------------------- 
  Service ID                                        standard   priority   serializer        Class name                                                                                 
 ------------------------------------------------- ---------- ---------- ----------------- ------------------------------------------------------------------------------------------- 
  serializer.denormalizer.unwrapping                1          1000                         Symfony\Component\Serializer\Normalizer\UnwrappingDenormalizer                             
  get_set_method_normalizer                                               ["api1","api2"]   Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer                             
  serializer.normalizer.flatten_exception           1          -880                         Symfony\Component\Messenger\Transport\Serialization\Normalizer\FlattenExceptionNormalizer  
  ...
  serializer.denormalizer.array                     1          -990                         Symfony\Component\Serializer\Normalizer\ArrayDenormalizer                                  
  serializer.normalizer.object                      1          -1000                        Symfony\Component\Serializer\Normalizer\ObjectNormalizer                                   
 ------------------------------------------------- ---------- ---------- ----------------- ------------------------------------------------------------------------------------------- 

Since Symfony comes with some pre-registered normalizers and encoders, I added options to exclude those in case someone wants to use only custom ones:

serializer:
  named_serializers:
    api1:
      include_standard_normalizers: false
      include_standard_encoders: true

      name_converter: 'serializer.name_converter.camel_case_to_snake_case'
      default_context:
        enable_max_depth: true

TBH, I have doubts about the usefulness of this, please let me know your thoughts.

I've split the PR into two commits to ease reviewing:

  • the first commit only rearranges the SerializerPass without adding any features
  • the second commit implements the feature

@carsonbot

This comment has been minimized.

@carsonbot carsonbot added this to the 7.1 milestone May 20, 2024
@HypeMC HypeMC force-pushed the named-serializers branch 2 times, most recently from ecd355d to 63409d5 Compare May 20, 2024 11:52
@OskarStark OskarStark modified the milestones: 7.1, 7.2 May 20, 2024
@alamirault alamirault mentioned this pull request May 20, 2024
fabpot added a commit that referenced this pull request May 21, 2024
This PR was merged into the 7.1 branch.

Discussion
----------

Fix singular phpdoc

| Q             | A
| ------------- | ---
| Branch?       | 7.1
| Bug fix?      | no
| New feature?  | no <!-- please update src/**/CHANGELOG.md files -->
| Deprecations? | no <!-- please update UPGRADE-*.md and src/**/CHANGELOG.md files -->
| Issues        | Fix #... <!-- prefix each issue number with "Fix #", no need to create an issue if none exists, explain below instead -->
| License       | MIT

(Found while reviewing #56823)

Commits
-------

e0f6258 Fix singular phpdoc
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

3 participants