Samuel Roze
Contributed by Samuel Roze in #24375

When working with objects that are pretty similar or share lots of properties, is common to use interfaces or abstract classes. The problem with classes that extend from other abstract classes is that the Serializer component doesn't know how to serialize/deserialize them correctly.

In Symfony 4.1, we improved the Serializer component to support this feature using a "discriminator class mapping". Consider an application that defines an abstract CodeRepository class extended by GitHubCodeRepository and BitBucketCodeRepository classes. This example shows how to serialize and deserialize those objects when using the stand-alone Serializer component:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
// ...

$discriminator = new ClassDiscriminatorResolver();
// $type is the property used to differentiate between classes
$discriminator->addClassMapping(
    CodeRepository::class,
    new ClassDiscriminatorMapping('type', [
        'github' => GitHubCodeRepository::class,
        'bitbucket' => BitBucketCodeRepository::class,
    ])
);

// pass the discriminator as the last argument of the normalizer
$serializer = new Serializer(
    array(new ObjectNormalizer(null, null, null, null, $discriminator)),
    array('json' => new JsonEncoder())
);

// when serializing, the discriminator knows that the value of the "type"
// property for GitHubCodeRepository classes must be set to {"type": "github"}
$serialized = $serializer->serialize(new GitHubCodeRepository());

// when deserializing into the abstract CodeRepository class, the discriminator
// uses the "type" value to know that $repository must be an instance of GitHubCodeRepository
$repository = $serializer->unserialize($serialized, CodeRepository::class, 'json');

When using this feature in a Symfony application, you can simply configure all this in a YAML or XML file and even using PHP annotations:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
namespace App\Repository;

use Symfony\Component\Serializer\Annotation\DiscriminatorMap;

/**
 * @DiscriminatorMap(typeProperty="type", mapping={
 *    "github"="App\Repository\GitHubCodeRepository",
 *    "bitbucket"="App\Repository\BitBucketCodeRepository"
 * })
 */
interface CodeRepository
{
    // ...
}

You can read more about this new feature in the Serializing Interfaces and Abstract Classes section of the Serializer Component documentation.

Published in #Living on the edge