Skip to content

[Serializer] Dynamic DiscriminatorMap and a custom loader registration #50390

Closed as not planned
@ludekbenedik

Description

@ludekbenedik

Description

Symfony does not currently support an easy way to register more mapping types on DiscriminatorMap. This is an important feature for modularity.

Currently a programmer must:

  1. Create a compiler pass to replace first argument of LoaderChain and SerializerCacheWarmer services.
  2. Replace whole ClassDiscriminatorMapping object.

There is a simple change to solve the current not friendly way:

  1. Add serializer.loader tag to register custom loaders.
  2. Add addType(string $type, string $class) method to ClassDiscriminatorMapping class. This point is not important as the first.
  3. Remove internal annotation from ClassMetadataInterface. Is there a reason to mark this class as internal?

For example, Doctrine allows you to dynamically register more types via loadClassMetadata event and ClassMetadataInfo::addDiscriminatorMapClass method.

Example

Before

class SerializerLoadersPass implements CompilerPassInterface
{
    public function process(ContainerBuilder $container): void
    {
        $chainLoader = $container->getDefinition('serializer.mapping.chain_loader');

        $serializerLoaders = $chainLoader->getArgument(0);
        $serializerLoaders[] = new Definition(DiscriminatorMappingLoader::class, [
            $container->getParameter('module_entity_mapping'), // Define app mapping in configuration
        ]);

        $chainLoader->replaceArgument(0, $serializerLoaders);
        $container->getDefinition('serializer.mapping.cache_warmer')->replaceArgument(0, $serializerLoaders);
    }
}
class DiscriminatorMappingLoader implements LoaderInterface
{
    /**
     * @param array<string, class-string> $appMapping Mapping from configuration
     */
    public function __construct(private array $appMapping)
    {
    }


    public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
    {
        if (ModuleEntity::class === $classMetadata->getName()) {
            $mapping = $classMetadata->getClassDiscriminatorMapping();

            $classMetadata->setClassDiscriminatorMapping(
                new ClassDiscriminatorMapping(
                    $mapping->getTypeProperty(),
                    array_merge($this->appMapping, $mapping->getTypesMapping())
                )
            );

            return true;
        }

        return false;
    }
}

After

return function(ContainerConfigurator $container) {
    $container->services()->set(DiscriminatorMappingLoader::class)
        ->arg(0, abstract_arg('From configuration'))
        ->tag('serializer.loader', ['priority' => 4096])
    ;
};
class DiscriminatorMappingLoader implements LoaderInterface
{
    /**
     * @param array<string, class-string> $appMapping Mapping from configuration
     */
    public function __construct(private array $appMapping)
    {
    }


    public function loadClassMetadata(ClassMetadataInterface $classMetadata): bool
    {
        if (ModuleEntity::class === $classMetadata->getName()) {
            $classMetadata->getClassDiscriminatorMapping()->addTypes($this->appMapping);
            // or iterate over appMapping and call addType($type, $class)
            
            return true;
        }

        return false;
    }
}

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