Slide 1

Slide 1 text

2022-09-24 @suzuki Symfony Serializer Deep Dive PHP Conference Japan 2022

Slide 2

Slide 2 text

This slide and sample codes • Speaker Deck • https://speakerdeck.com/suzuki/symfony-serializer-deep-dive • Sample codes on GitHub • https://github.com/suzuki/symfony-serializer-samples Slide Codes

Slide 3

Slide 3 text

About me

Slide 4

Slide 4 text

@suzuki https://twitter.com/suzuki https://github.com/suzuki https://speakerdeck.com/suzuki

Slide 5

Slide 5 text

Currently worked at RABO, Inc https://rabo.cat

Slide 6

Slide 6 text

What is Symfony Serializer

Slide 7

Slide 7 text

This talks based on the image of official docs

Slide 8

Slide 8 text

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

Slide 13

Slide 13 text

Serializer • Serializer = Normalizer + Encoder Object Format (JSON, XML, CSV) Array serialize deserialize normalize denormalize encode decode

Slide 14

Slide 14 text

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

Slide 29

Slide 29 text

JSON API for Mobile App DB API Mobile App JSON

Slide 30

Slide 30 text

Detail view GET /cats/{id} { "id": 1, "name": "Mugi", "image_url": "IMAGE_URL",
 "gender": "Female", "weight": 1.8 }

Slide 31

Slide 31 text

List view 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"} ]

Slide 32

Slide 32 text

000: json_encode style

Slide 33

Slide 33 text

json_encode

Slide 34

Slide 34 text

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

Slide 47

Slide 47 text

JsonEncoder • Convert between Array and JSON

Slide 48

Slide 48 text

102: ListView via Serializer $repo = new CatRepository(Cat::class); $mugi = $repo->find(1); $sora = $repo->find(2); $normalizers = [new ObjectNormalizer()]; $encoders = [new JsonEncoder()]; $serializer = new Serializer($normalizers, $encoders); $data = [$mugi, $sora]; $output = $serializer->serialize($data, 'json'); echo $output; // output // [{"id":1,"name":"Mugi","imageUrl":"mugi.jpg","gender":"Female","weight":1.8}, {"id":2,"name":"Sora","imageUrl":"sora.jpg","gender":"Male","weight":2.4}] app/100/102_serialize_list.php

Slide 49

Slide 49 text

Setup annotation reader

Slide 50

Slide 50 text

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

Slide 58

Slide 58 text

104: camelCase to snake_case use Symfony\Component\Serializer\NameConverter\MetadataAwareNameConverter; $repo = new CatRepository(Cat::class); $mugi = $repo->find(1); $sora = $repo->find(2); $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory); $normalizers = [new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter)]; $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","image_url":"mugi.jpg"},{"id":2,"name":"Sora","image_url":"sora.jpg"}] app/100/104_serialize_serialized_name.php snake_case

Slide 59

Slide 59 text

[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

Slide 61

Slide 61 text

105: Method Annotation/Attribute $repo = new CatRepository(Cat::class); $mugi = $repo->find(1); $sora = $repo->find(2); $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory); $normalizers = [new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter)]; $encoders = [new JsonEncoder()]; $serializer = new Serializer($normalizers, $encoders); $data = [$mugi, $sora]; $output = $serializer->serialize($data, 'json', [ AbstractNormalizer::GROUPS => ['list'], ]); echo $output; // output // [{"id":2,"name":"Sora","image_url":"sora.jpg","lower_name":"sora"},{"id":1,"name":"Mugi","image_url":"mugi.jpg","lower_name":"mugi"}] app/100/105_serialize_annotation_method.php

Slide 62

Slide 62 text

200: Deserialize

Slide 63

Slide 63 text

Deserialize Data Model JSON deserialize()

Slide 64

Slide 64 text

DataModel namespace App\Model\v200; final class Cat { private int $id; private string $name; private string $imageUrl; private string $gender; private ?float $weight = null; // snip src/Model/v200/Cat.php

Slide 65

Slide 65 text

201: Deserialize $normalizers = [new ObjectNormalizer()]; $encoders = [new JsonEncoder()]; $serializer = new Serializer($normalizers, $encoders); $json = '{"id":1,"name":"Mugi","imageUrl":"mugi.jpg","gender":"Female","weight":1.8}'; $object = $serializer->deserialize($json, Cat::class, 'json'); print_r($object); // output // App\Model\v200\Cat Object // ( // [id:App\Model\v200\Cat:private] => 1 // [name:App\Model\v200\Cat:private] => Mugi // [imageUrl:App\Model\v200\Cat:private] => mugi.jpg // [gender:App\Model\v200\Cat:private] => Female // [weight:App\Model\v200\Cat:private] => 1.8 // ) app/200/201_deserialize.php Target class name

Slide 66

Slide 66 text

202: Deserialize from JSON array use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer; $normalizers = [new ObjectNormalizer(), new ArrayDenormalizer()]; $encoders = [new JsonEncoder()]; $serializer = new Serializer($normalizers, $encoders); $json = '[ {"id":1,"name":"Mugi","imageUrl":"mugi.jpg","gender":"Female","weight":1.8}, {"id":2,"name":"Sora","imageUrl":"sora.jpg","gender":"Male","weight":2.4} ]'; $list = $serializer->deserialize($json, Cat::class.'[]', 'json'); print_r($list); app/200/202_deserialize_json_array.php

Slide 67

Slide 67 text

202: Output $json = '[ {"id":1,"name":"Mugi","imageUrl":"mugi.jpg","gender":"Female","weight":1.8}, {"id":2,"name":"Sora","imageUrl":"sora.jpg","gender":"Male","weight":2.4} ]'; // output // Array // ( // [0] => App\Model\v200\Cat Object // ( // [id:App\Model\v200\Cat:private] => 1 // [name:App\Model\v200\Cat:private] => Mugi // [imageUrl:App\Model\v200\Cat:private] => mugi.jpg // [gender:App\Model\v200\Cat:private] => Female // [weight:App\Model\v200\Cat:private] => 1.8 // ) // // [1] => App\Model\v200\Cat Object // ( // [id:App\Model\v200\Cat:private] => 2 // [name:App\Model\v200\Cat:private] => Sora // [imageUrl:App\Model\v200\Cat:private] => sora.jpg // [gender:App\Model\v200\Cat:private] => Male // [weight:App\Model\v200\Cat:private] => 2.4 // ) app/200/202_deserialize_json_array.php

Slide 68

Slide 68 text

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

Slide 69

Slide 69 text

Situation 2: Admin App

Slide 70

Slide 70 text

Admin App

Slide 71

Slide 71 text

300: Serialize to CSV

Slide 72

Slide 72 text

CSV Download id, name, image_url, gender, weight 1, "Mugi", "mugi.jpg", "Female", 1.8 2, "Sora", "sora.jpg", "Male", 2.4 3, "Leo", "leo.jpg", "Male", 2.2 4, "Coco", "coco.jpg", "Female", 3.1

Slide 73

Slide 73 text

Yes! PHP has fputcsv, But forget today!

Slide 74

Slide 74 text

301: Serialize to CSV use Symfony\Component\Serializer\Encoder\CsvEncoder; $repo = new CatRepository(Cat::class); $mugi = $repo->find(1); $sora = $repo->find(2); $normalizers = [new ObjectNormalizer()]; $encoders = [new CsvEncoder()]; $serializer = new Serializer($normalizers, $encoders); $data = [$mugi, $sora]; $output = $serializer->serialize($data, 'csv'); echo $output; // output // id,name,imageUrl,gender,weight // 1,Mugi,mugi.jpg,Female,1.8 // 2,Sora,sora.jpg,Male,2.4 app/300/301_serialize_csv.php

Slide 75

Slide 75 text

302: Use Groups and SerializedName same as JSON $repo = new CatRepository(Cat::class); $mugi = $repo->find(1); $sora = $repo->find(2); $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory); $normalizers = [new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter)]; $encoders = [new CsvEncoder()]; $serializer = new Serializer($normalizers, $encoders); $data = [$mugi, $sora]; $output = $serializer->serialize($data, 'csv', [ AbstractNormalizer::GROUPS => ['list'], ]); echo $output; // output // id,name,image_url // 1,Mugi,mugi.jpg // 2,Sora,sora.jpg app/300/302_serialize_csv_group_camel_case.php

Slide 76

Slide 76 text

Selected columns id, image_url, gender 1, "mugi.jpg", "Female" 2, "sora.jpg", "Male" 3, "leo.jpg", "Male" 4, "coco.jpg", "Female"

Slide 77

Slide 77 text

303: Specific Attributes $repo = new CatRepository(Cat::class); $mugi = $repo->find(1); $sora = $repo->find(2); $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory); $normalizers = [new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter)]; $encoders = [new CsvEncoder()]; $serializer = new Serializer($normalizers, $encoders); $data = [$mugi, $sora]; $output = $serializer->serialize($data, 'csv', [ AbstractNormalizer::ATTRIBUTES => ['id', 'imageUrl', 'gender'], ]); echo $output; // output // id,image_url,gender // 1,mugi.jpg,Female // 2,sora.jpg,Male app/300/303_serialize_csv_attributes.php

Slide 78

Slide 78 text

304: Delimiter $repo = new CatRepository(Cat::class); $mugi = $repo->find(1); $sora = $repo->find(2); $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory); $normalizers = [new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter)]; $encoders = [new CsvEncoder()]; $serializer = new Serializer($normalizers, $encoders); $data = [$mugi, $sora]; $output = $serializer->serialize($data, 'csv', [ CsvEncoder::DELIMITER_KEY => "\t", ]); echo $output; // output // idname image_url gender weight // 1 Mugi mugi.jpg Female 1.8 // 2 Sora sora.jpg Male 2.4 app/300/304_serialize_csv_delimiter.php

Slide 79

Slide 79 text

CsvEncoder options Options Default csv_delimiter ' csv_enclosure " csv_end_of_line \n csv_escape_char empty string csv_key_separator . csv_headers [], inferred from input data's keys csv_escape_formulas false as_collection true no_headers false output_utf8_bom false

Slide 80

Slide 80 text

[TIPS] Multiple Encoders can be set $repo = new CatRepository(Cat::class); $mugi = $repo->find(1); $sora = $repo->find(2); $classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader())); $metadataAwareNameConverter = new MetadataAwareNameConverter($classMetadataFactory); $normalizers = [new ObjectNormalizer($classMetadataFactory, $metadataAwareNameConverter)]; $encoders = [new CsvEncoder(), new JsonEncode()]; $serializer = new Serializer($normalizers, $encoders); $data = [$mugi, $sora]; $csv = $serializer->serialize($mugi, 'csv'); $json = $serializer->serialize($mugi, 'json'); echo $csv; echo "---\n"; echo $json; // output // id,name,image_url,gender,weight // 1,Mugi,mugi.jpg,Female,1.8 // --- // {"id":1,"name":"Mugi","image_url":"mugi.jpg","gender":"Female","weight":1.8} app/300/390_serialize_csv_json.php

Slide 81

Slide 81 text

400: Deserialize from CSV

Slide 82

Slide 82 text

CSV Upload id, name, imageUrl, gender, weight 1, Mugi, mugi.jpg, Female, 1.8 2, Sora, sora.jpg, Male, 2.4 3, Leo, leo.jpg, Male, 2.2 4, Coco, coco.jpg, Female, 3.1

Slide 83

Slide 83 text

401: Deserialize from CSV $normalizers = [new ObjectNormalizer(), new ArrayDenormalizer()]; $encoders = [new CsvEncoder()]; $serializer = new Serializer($normalizers, $encoders); $csv = 'id,name,imageUrl,gender,weight 1,Mugi,mugi.jpg,Female,1.8 2,Sora,sora.jpg,Male,2.4'; $list = $serializer->deserialize($csv, Cat::class.'[]', 'csv'); print_r($list); app/400/401_deserialize_csv.php

Slide 84

Slide 84 text

401: Output // output // Array // ( // [0] => App\Model\v400\Cat Object // ( // [id:App\Model\v400\Cat:private] => 1 // [name:App\Model\v400\Cat:private] => Mugi // [imageUrl:App\Model\v400\Cat:private] => mugi.jpg // [gender:App\Model\v400\Cat:private] => Female // [weight:App\Model\v400\Cat:private] => 1.8 // ) // // [1] => App\Model\v400\Cat Object // ( // [id:App\Model\v400\Cat:private] => 2 // [name:App\Model\v400\Cat:private] => Sora // [imageUrl:App\Model\v400\Cat:private] => sora.jpg // [gender:App\Model\v400\Cat:private] => Male // [weight:App\Model\v400\Cat:private] => 2.4 // ) // // ) app/400/401_deserialize_csv.php

Slide 85

Slide 85 text

500: Custom Encoder

Slide 86

Slide 86 text

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

Slide 93

Slide 93 text

502: Output // output // App\Model\v500\Cat Object // ( // [id:App\Model\v500\Cat:private] => 1 // [name:App\Model\v500\Cat:private] => Mugi // [imageUrl:App\Model\v500\Cat:private] => mugi.jpg // [gender:App\Model\v500\Cat:private] => Female // [weight:App\Model\v500\Cat:private] => 1.8 // ) app/500/502_toml_deserialize.php

Slide 94

Slide 94 text

[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

Slide 96

Slide 96 text

Inside Serializer

Slide 97

Slide 97 text

Constructor of Serializer public function __construct(array $normalizers = [], array $encoders = []) /** * @var NormalizerInterface|DenormalizerInterface */ private $normalizers = []; /** * @var Encoder\ChainEncoder */ protected $encoder; /** * @var Encoder\ChainDecoder */ protected $decoder;

Slide 98

Slide 98 text

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