Skip to content

Navigation Menu

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

[Serializer][Feature] Automating the denormalization process for objects with dependencies #59074

Copy link
Copy link
Open
@jjpshorg

Description

@jjpshorg
Issue body actions

> Context:

ObjectNormalizer from Symfony's Serializer component.

> Goal:

Automating the deserialization of objects that depend on services from the "Service Container" or parameters from the "Parameter Bag," avoiding the need to create a specific denormalizer for each class or object.

> Use Case:

Enable deserialization/denormalization into domain objects commonly used in a DDD (Domain-Driven Design) architecture. These domain objects include properties as well as functionalities implemented via services.
Domain objects are instantiated by factories that ensure the injection of dependencies.

> Functional Proposal:

Facilitate the denormalization process for objects with service dependencies by leveraging their factories.
Factories should be identified and made callable by the ObjectNormalizer.

> Technical Proposal:

1) Characterizing Factories:

  • Factories are identified as tagged services.
  • Tag:
    • Attribute name (e.g., serializer.denormalizer.factory)
    • Attribute type = the type of the object to instantiate (FQCN).
  • Creation of an interface (e.g., FactoryInterface) that declares a method (e.g., getMethodName) to return the name of the method used to instantiate the object.

2) Accessing Factories from the Normalizer:

Two Strategies :

- Strategy 1 : Injection via constructor of ObjectNormalizer

  • Add a $factories property to the constructor of the ObjectNormalizer class.

  • Use the #[AutowireIterator] attribute to automatically inject all tagged factory services.

    #[AutowireIterator(tag: 'serializer.denormalizer.factory', indexAttribute: 'type')]
  • The $factories property traverses the inheritance tree to reach the abstract class AbstractNormalizer, where the instantiateObject method is modified to invoke the factories if needed (according to the state of the denormalization).

  • Advantage: Automatic injection.

  • Disadvantage: Adds complexity to the constructor with an additional parameter.

- Strategy 2 : Passing via Context

  • Creation of a context option DENORMALIZER_FACTORIES as an associative array:

    object type => service factory instance
  • In the abstract class AbstractNormalizer: modify the instantiateObject method to invoke the factories if needed (according to the state of the denormalization).

  • Advantage: Does not add complexity to the constructor with an additional parameter. The definition of factories to use benefits from the flexibility of the context.

  • Disadvantage: Injection is not automatic. A default context must be created, which requires creating a decorator for the ObjectNormalizer to add a context based on a factory collection service, which also needs to be implemented.

Example

Interface :
interface FactoryInterface
{
public function getMethodName(): string;
}

In the method Symfony\Component\Serializer\Normalizer\AbstractNormalizer:instantiateObject(...) :

  • BEFORE :

     if ($missingConstructorArguments) {
              throw new MissingConstructorArgumentsException(\sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires the following parameters to be present : "$%s".', $class, implode('", "$', $missingConstructorArguments)), 0, null, $missingConstructorArguments, $class);
          }
    
  • AFTER :

          if ($missingConstructorArguments) {
              if ($constructor->isConstructor() && null !== $factory = $this->getFactory($reflectionClass)) {
                  $factoryMethodName = $factory->getMethodName();
                  $orderedParams = $this->reorderArguments($params, get_class($factory), $factoryMethodName);
    
                  return $factory->$factoryMethodName(...$orderedParams);
              }
    
            throw new MissingConstructorArgumentsException(\sprintf('Cannot create an instance of "%s" from serialized data because its constructor requires the following parameters to be present : "$%s".', $class, implode('", "$', $missingConstructorArguments)), 0, null, $missingConstructorArguments, $class);
          }
    

NOTE :

  • The method $this->reorderArguments() ensures that the arguments are passed in the correct order to the factory method.
  • The method $this->getFactory() retrieves the factory corresponding to the type $reflectionClass->name. Its implementation will depend on the chosen strategy ("Injection via the constructor of ObjectNormalizer" or "Passing via Context").

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

      Morty Proxy This is a proxified and sanitized view of the page, visit original site.