Symfony Serializer is like "Converter"
• Convert between Object and Format Object
Format (JSON, XML, CSV)
convert
Slide 9
Slide 9 text
Serializer has serialize / deserialize
• Object to Format: serialize
• Format to Object: deserialize
Object
Format (JSON, XML, CSV)
serialize
deserialize
Slide 10
Slide 10 text
Serializer uses Array
• Use Array as intermediate data Object
Format (JSON, XML, CSV)
serialize
deserialize Array
Slide 11
Slide 11 text
Normalizer is Converter between Object and Array
• Object to Array: normalize
• Array to Object: denormalize
Object
Format (JSON, XML, CSV)
serialize
deserialize Array
normalize
denormalize
Normalizer
Slide 12
Slide 12 text
Encoder is Converter between Array And Format
• Array to Format: endode
• Format to Array: decode
Object
Format (JSON, XML, CSV)
serialize
deserialize Array
normalize
denormalize
encode
decode
Encoder
So far, you understand about 80%
of the Symfony Serializer, now
Slide 15
Slide 15 text
Next,
Slide 16
Slide 16 text
Read the docs
https://symfony.com/doc/current/components/serializer.html
Slide 17
Slide 17 text
Read the docs
Read the docs
Read the docs
Read the docs
...
Slide 18
Slide 18 text
Congratulations
You will master Symfony Serializer
Slide 19
Slide 19 text
That's all
Slide 20
Slide 20 text
But, today,
please keep listening to this talk!
Slide 21
Slide 21 text
[TIPS] Two types of Documents on Symfony.com
• There are two types of documents on the Symfony.com
1. The Symfony Component
2. How to use the Symfony Component in the Symfony Framework
• If you use another framework, you should read the above 1 instead of 2
Slide 22
Slide 22 text
How to use Symfony Serializer
Slide 23
Slide 23 text
Install
$ composer require symfony/serializer
Slide 24
Slide 24 text
Setup
$serializer = new Serializer($normalizers, $encoders);
Slide 25
Slide 25 text
Use it
$data = $serializer->serialize($object, $format, $context);
$object = $serializer->deserialize($data, $type, $format, $context);
Slide 26
Slide 26 text
Situation 1: Mobile App
Slide 27
Slide 27 text
Mobile App
Detail view List view
Slide 28
Slide 28 text
Data Model
final class Cat
{
private int $id;
private string $name;
private string $imageUrl;
private string $gender;
private ?float $weight = null;
// snip
}
[note] getters/setters are omitted in this slide
json_encode
Data Model View Model JSON
json_encode()
create View
Slide 35
Slide 35 text
001: DetailView Model
namespace App\ViewModel;
use App\Model\v000\Cat;
final class DetailView implements \JsonSerializable
{
public function __construct(private Cat $cat)
{
}
public function jsonSerialize(): mixed
{
return [
'id' => $this->cat->getId(),
'name' => $this->cat->getName(),
'image_url' => $this->cat->getImageUrl(),
'gender' => $this->cat->getGender(),
'weight' => $this->cat->getWeight(),
];
}
}
src/ViewModel/DetailView.php
Slide 36
Slide 36 text
[TIPS] You can find codes on my GitHub repo
src/ViewModel/DetailView.php
https://github.com/suzuki/symfony-serializer-samples/
Slide 37
Slide 37 text
001: json_encode DetailView
use App\Model\v000\Cat;
use App\Repository\CatRepository;
use App\ViewModel\DetailView;
require_once __DIR__ . '/../../vendor/autoload.php';
$repo = new CatRepository(Cat::class);
$mugi = $repo->find(1);
$view = new DetailView($mugi);
$output = json_encode($view);
echo $output;
// output
// {"id":1,"name":"Mugi","image_url":"IMAGE_URL","gender":"Female","weight":1.8}
app/000/001_json_encode.php
Slide 38
Slide 38 text
002: ListView Model
namespace App\ViewModel;
use App\Model\v000\Cat;
final class ListView implements \JsonSerializable
{
public function __construct(private Cat $cat)
{
}
public function jsonSerialize(): mixed
{
return [
'id' => $this->cat->getId(),
'name' => $this->cat->getName(),
'image_url' => $this->cat->getImageUrl(),
];
}
}
src/ViewModel/ListView.php
Slide 39
Slide 39 text
002: json_encode ListView
use App\Model\v000\Cat;
use App\Repository\CatRepository;
use App\ViewModel\ListView;
require_once __DIR__ . '/../../vendor/autoload.php';
$repo = new CatRepository(Cat::class);
$mugi = $repo->find(1);
$sora = $repo->find(2);
$data = [
new ListView($mugi),
new ListView($sora),
];
$output = json_encode($data);
echo $output;
// output
// [{"id":1,"name":"Mugi","image_url":"mugi.jpg"},{"id":2,"name":"Sora","image_url":"sora.jpg"}]
app/000/002_json_encode_list.php
Slide 40
Slide 40 text
One data model, Two view models
• Using two View Model, json_encode can flexible output
• It's a good way to implement these APIs
Slide 41
Slide 41 text
100: Symfony Serializer style
Slide 42
Slide 42 text
json_encode
Data Model View Model JSON
json_encode()
create View
Slide 43
Slide 43 text
Symfony Serializer
Data Model JSON
serialize()
Slide 44
Slide 44 text
Data Model
final class Cat
{
private int $id;
private string $name;
private string $imageUrl;
private string $gender;
private ?float $weight = null;
// snip
}
[note] getters/setters are omitted in this slide
Slide 45
Slide 45 text
101: DetailView via Serializer
use App\Model\v101\Cat;
use App\Repository\CatRepository;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
require_once __DIR__ . '/../../vendor/autoload.php';
$repo = new CatRepository(Cat::class);
$mugi = $repo->find(1);
$normalizers = [new ObjectNormalizer()];
$encoders = [new JsonEncoder()];
$serializer = new Serializer($normalizers, $encoders);
$output = $serializer->serialize($mugi, 'json');
echo $output;
// output
// {"id":1,"name":"Mugi","imageUrl":"mugi.jpg","gender":"Female","weight":1.8}
Build Serializer
Use Serializer
app/100/101_serialize.php
Slide 46
Slide 46 text
ObjectNormalizer
• Default Normalizer on Symfony Framework
• Most powerful normalizer
• Convert between Object and Array
• Property name and value converts to key and value of hash
Install doctrine/annotaions
$ composer require doctrine/annotations
[note] You can use YAML/XML configurations instead of annotations
Slide 51
Slide 51 text
Doctrine Annotations
• The Reader utility of Annotation and Attribute
class Sample
{
/**
* @SampleAnnotation
*/
public function Foo(): void
{
}
}
class Sample
{
#[SampleAttribute]
public function Foo(): void
{
}
}
Slide 52
Slide 52 text
103: Add Groups attribute to Data Model
namespace App\Model\v103;
use Symfony\Component\Serializer\Annotation\Groups;
final class Cat
{
#[Groups(['list'])]
private int $id;
#[Groups(['list'])]
private string $name;
#[Groups(['list'])]
private string $imageUrl;
private string $gender;
private ?float $weight = null;
// snip
src/Model/v103/Cat.php
Slide 53
Slide 53 text
103: ListView with Groups
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
$repo = new CatRepository(Cat::class);
$mugi = $repo->find(1);
$sora = $repo->find(2);
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$normalizers = [new ObjectNormalizer($classMetadataFactory)];
$encoders = [new JsonEncoder()];
$serializer = new Serializer($normalizers, $encoders);
$data = [$mugi, $sora];
$output = $serializer->serialize($data, 'json', [
AbstractNormalizer::GROUPS => ['list'],
]);
echo $output;
// output
// [{"id":1,"name":"Mugi","imageUrl":"mugi.jpg"},{"id":2,"name":"Sora","imageUrl":"sora.jpg"}]
Setup Annotation Reader
Use Groups in Context
app/100/103_serialize_list_groups.php
Slide 54
Slide 54 text
[TIPS] Groups can set multiple key
namespace App\Model\v103;
use Symfony\Component\Serializer\Annotation\Groups;
final class Cat
{
#[Groups(['list', 'OTHER_KEY'])]
private int $id;
#[Groups(['list', 'OTHER_KEY'])]
private string $name;
#[Groups(['list'])]
private string $imageUrl;
#[Groups(['OTHER_KEY'])]
private string $gender;
private ?float $weight = null;
// snip
$output = $serializer->serialize($data, 'json', [
AbstractNormalizer::GROUPS => ['list', 'OTHER_KEY'],
]);
Slide 55
Slide 55 text
Did you notice this spec?
GET /cats
[
{"id": 1, "name": "Mugi", "image_url": "IMAGE_URL"},
{"id": 2, "name": "Sora", "image_url": "IMAGE_URL"},
{"id": 3, "name": "Leo" , "image_url": "IMAGE_URL"},
{"id": 4, "name": "Coco", "image_url": "IMAGE_URL"}
]
snake_case
Slide 56
Slide 56 text
Output of 103
[
{"id":1,"name":"Mugi","imageUrl":"mugi.jpg"},
{"id":2,"name":"Sora","imageUrl":"sora.jpg"}
]
camelCase
Slide 57
Slide 57 text
104: Add SerializedName to Data Model
namespace App\Model\v104;
use Symfony\Component\Serializer\Annotation\SerializedName;
final class Cat
{
#[Groups('list')]
private int $id;
#[Groups('list')]
private string $name;
#[Groups('list')]
#[SerializedName('image_url')]
private string $imageUrl;
// snip
src/Model/v104/Cat.php
[TIPS] NameConverter can be used independently
use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
$converter = new CamelCaseToSnakeCaseNameConverter();
$snake_case = $converter->normalize('ImageUrl');
echo $snake_case . "\n";
$camelCase = $converter->denormalize('image_url');
echo $camelCase . "\n";
// output
// image_url
// imageUrl
app/100/190_camel_case_only.php
Slide 60
Slide 60 text
105: Add attribute to method
namespace App\Model\v105;
use Symfony\Component\Serializer\Annotation\Groups;
use Symfony\Component\Serializer\Annotation\SerializedName;
final class Cat
{
// snip
#[Groups('list')]
#[SerializedName('lower_name')]
public function getLowerCaseName(): string
{
return mb_strtolower($this->getName());
}
// snip
src/Model/v105/Cat.php
ArrayDenormalizer
• Deserialize JSON array to Object
array, use ArrayDenormalizer
• "Denormalizer" only effects to the
process of denormalize
Object
Format (JSON, XML, CSV)
Array serialize
deserialize
normalize
denormalize
encode
decode
EncoderInterface, DecoderInterface
namespace Symfony\Component\Serializer\Encoder;
interface EncoderInterface
{
public function encode(mixed $data, string $format, array $context = []): string;
public function supportsEncoding(string $format /*, array $context = [] */): bool;
}
namespace Symfony\Component\Serializer\Encoder;
interface DecoderInterface
{
public function decode(string $data, string $format, array $context = []);
public function supportsDecoding(string $format /*, array $context = [] */);
}
Slide 87
Slide 87 text
TOML
https://toml.io/en/
Slide 88
Slide 88 text
TinyTomlEncoder
namespace App\Encoder;
use App\Encoder\TinyTomlEncoder\Dumper;
use App\Encoder\TinyTomlEncoder\Parser;
use Symfony\Component\Serializer\Encoder\DecoderInterface;
use Symfony\Component\Serializer\Encoder\EncoderInterface;
class TinyTomlEncoder implements EncoderInterface, DecoderInterface
{
public const FORMAT = 'toml';
private Dumper $dumper;
private Parser $parser;
public function __construct(Dumper $dumper = null, Parser $parser = null)
{
$this->dumper = $dumper ?? new Dumper();
$this->parser = $parser ?? new Parser();
}
// snip
src/Encoder/TinyTomlEncoder.php
Slide 89
Slide 89 text
TinyTomlEncoder: Encoder
use App\Encoder\TinyTomlEncoder\Dumper;
class TinyTomlEncoder implements EncoderInterface, DecoderInterface
{
public const FORMAT = 'toml';
private Dumper $dumper;
// snip
/**
* {@inheritdoc}
*/
public function encode(mixed $data, string $format, array $context = []): string
{
return $this->dumper->dump($data);
}
/**
* {@inheritdoc}
*/
public function supportsEncoding(string $format, array $context = []): bool
{
return self::FORMAT === $format;
}
// snip
src/Encoder/TinyTomlEncoder.php
Slide 90
Slide 90 text
TinyTomlEncoder: Decoder
use App\Encoder\TinyTomlEncoder\Parser;
class TinyTomlEncoder implements EncoderInterface, DecoderInterface
{
public const FORMAT = 'toml';
private Parser $parser;
// snip
/**
* {@inheritdoc}
*/
public function decode(string $data, string $format, array $context = [])
{
return $this->parser->parse($data);
}
/**
* {@inheritdoc}
*/
public function supportsDecoding(string $format, array $context = [])
{
return self::FORMAT === $format;
}
}
src/Encoder/TinyTomlEncoder.php
Slide 91
Slide 91 text
501: Serialize to TOML
use App\Encoder\TinyTomlEncoder;
$repo = new CatRepository(Cat::class);
$mugi = $repo->find(1);
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory);
$normalizers = [new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter)];
$encoders = [new TinyTomlEncoder()];
$serializer = new Serializer($normalizers, $encoders);
$toml = $serializer->serialize($mugi, 'toml');
echo $toml;
// output
// id = 1
// name = "Mugi"
// image_url = "mugi.jpg"
// gender = "Female"
// weight = 1.8
app/500/501_toml_serialize.php
Slide 92
Slide 92 text
502: Deserialize from TOML
use App\Encoder\TinyTomlEncoder;
$repo = new CatRepository(Cat::class);
$mugi = $repo->find(1);
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
$metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory);
$normalizers = [new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter)];
$encoders = [new TinyTomlEncoder()];
$serializer = new Serializer($normalizers, $encoders);
$toml = <<deserialize($toml, Cat::class, 'toml');
print_r($object);
app/500/502_toml_deserialize.php
[TIPS] Other Encoders implemented by API Platform
• The API Platform uses the Symfony Serializer
• They implement some useful custom Encoders for API:
• JSON-LD, GraphQL, OpenAPI, HAL, JSON:API, and so on
• See also: https://api-platform.com/
Slide 95
Slide 95 text
[TIP] Custom Normalizer
• You can create Custom Normalizer like Custom Encoder
• Implement the NormalizerInterface and DenormalizerInterface
ChainEncoder implements these:
final public function encode(mixed $data, string $format, array $context = []): string
public function supportsEncoding(string $format, array $context = []): bool
public function needsNormalization(string $format, array $context = []): bool
Slide 99
Slide 99 text
ChainDecorder implements these:
final public function decode(string $data, string $format, array $context = []): mixed
public function supportsDecoding(string $format, array $context = []): bool
Slide 100
Slide 100 text
Serialize flow
final public function serialize(mixed $data, string $format, array $context = []): string
return $this->encoder->encode($data, $format, $context);
if ($this->encoder->supportsEncoding($format, $context))
if ($this->encoder->needsNormalization($format, $context)) {
$data = $this->normalize($data, $format, $context);
}
throw new Exception
No
$normalizers
[Note] These codes are not real, it's image
Slide 101
Slide 101 text
Deserialize flow
final public function deserialize(mixed $data, string $type, string $format, array $context = []): mixed
return $this->denormalize($data, $type, $format, $context);
if ($this->encoder->supportsDecoding($format, $context))
$this->decoder->decode($data, $format, $context);
throw new Exception
No
$normalizers
[Note] These codes are not real, it's image
Slide 102
Slide 102 text
Summary of flow: It's simple
• Each Encoder
• It knows its own supported format
• It receives its own supported context
• Each Normalizer
• It knows its own supported format and data
• It receives its own supported context
• Serializer
• It choices Encoder by format
• It choices Normalizer by format and data
Slide 103
Slide 103 text
Conclusion
Slide 104
Slide 104 text
Serializer = Normalizer + Encoder
• Normalizer transforms
keys and values
• Encoder transforms
format
Object
Format (JSON, XML, CSV)
Array serialize
deserialize
normalize
denormalize
encode
decode
Slide 105
Slide 105 text
Multiple formats, Same interface
• Serializer is supporting multiple formats as the same interface
• Custom Encoders and Normalizers can be easily created
• They can also be used in the same interface
Slide 106
Slide 106 text
Good code design
• Serializer implementation is simple
• It only controls Normalizers and Encoders
• Splitting Normalizer and Encoder is a good idea
• Splitting NormalizeInterface and DenormalizeInterface, too
• Splitting EncoderInterface and DecorderInterface, too
Slide 107
Slide 107 text
Read the docs
• This talk is not enough to all about SymfonySerializer
• You can find many topics on the official docs:
• https://symfony.com/doc/current/components/serializer.html
Slide 108
Slide 108 text
Read the codes
• Yes, you are a developer, read the codes on GitHub
• https://github.com/symfony/serializer
Slide 109
Slide 109 text
That's all, thank you!
Slide 110
Slide 110 text
Appendix
Slide 111
Slide 111 text
Official Document Links
• Symfony Serializer Component
• https://symfony.com/doc/current/components/serializer.html
• Doctrine Annotations
• https://www.doctrine-project.org/projects/doctrine-annotations/en/
current/index.html
• API Platform
• https://api-platform.com/
Slide 112
Slide 112 text
Cat names and images
• Names of Cat are taken from:
• https://www.anicom-sompo.co.jp/special/name_cat/cat_2022/
• Photos of Cat (CC BY 2.0)
• https://flickr.com/photos/tboard/6949552141/
• https://flickr.com/photos/mandywonder/33002985171
• https://flickr.com/photos/cuatrok77/8436333647
• https://flickr.com/photos/stone65/7795933612
Slide 113
Slide 113 text
Other References
• Mastering the Symfony Serializer (PHP Tour slides)
• https://medium.com/@dunglas/mastering-the-symfony-serializer-
php-tour-slides-20a117708893
• JMS Serializer
• https://jmsyst.com/libs/serializer