$30 off During Our Annual Pro Sale. View Details »

Symfony Serializer Deep Dive

Norio Suzuki
September 24, 2022

Symfony Serializer Deep Dive

Symfony Serializer Deep Dive

Talked at PHP Conference Japan 2022
- https://fortee.jp/phpcon-2022/proposal/f517eec8-48e9-4d24-a6e9-e6823895e505

Sample codes are available here:
- https://github.com/suzuki/symfony-serializer-samples

Norio Suzuki

September 24, 2022
Tweet

More Decks by Norio Suzuki

Other Decks in Technology

Transcript

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

  2. 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
  3. About me

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

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

  6. What is Symfony Serializer

  7. This talks based on the image of official docs

  8. Symfony Serializer is like "Converter" • Convert between Object and

    Format Object Format (JSON, XML, CSV) convert
  9. Serializer has serialize / deserialize • Object to Format: serialize

    • Format to Object: deserialize Object Format (JSON, XML, CSV) serialize deserialize
  10. Serializer uses Array • Use Array as intermediate data Object

    Format (JSON, XML, CSV) serialize deserialize Array
  11. 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
  12. 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
  13. Serializer • Serializer = Normalizer + Encoder Object Format (JSON,

    XML, CSV) Array serialize deserialize normalize denormalize encode decode
  14. So far, you understand about 80% of the Symfony Serializer,

    now
  15. Next,

  16. Read the docs https://symfony.com/doc/current/components/serializer.html

  17. Read the docs Read the docs Read the docs Read

    the docs ...
  18. Congratulations You will master Symfony Serializer

  19. That's all

  20. But, today, please keep listening to this talk!

  21. [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
  22. How to use Symfony Serializer

  23. Install $ composer require symfony/serializer

  24. Setup $serializer = new Serializer($normalizers, $encoders);

  25. Use it $data = $serializer->serialize($object, $format, $context); $object = $serializer->deserialize($data,

    $type, $format, $context);
  26. Situation 1: Mobile App

  27. Mobile App Detail view List view

  28. 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
  29. JSON API for Mobile App DB API Mobile App JSON

  30. Detail view GET /cats/{id} { "id": 1, "name": "Mugi", "image_url":

    "IMAGE_URL",
 "gender": "Female", "weight": 1.8 }
  31. 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"} ]
  32. 000: json_encode style

  33. json_encode

  34. json_encode Data Model View Model JSON json_encode() create View

  35. 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
  36. [TIPS] You can find codes on my GitHub repo src/ViewModel/DetailView.php

    https://github.com/suzuki/symfony-serializer-samples/
  37. 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
  38. 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
  39. 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
  40. One data model, Two view models • Using two View

    Model, json_encode can flexible output • It's a good way to implement these APIs
  41. 100: Symfony Serializer style

  42. json_encode Data Model View Model JSON json_encode() create View

  43. Symfony Serializer Data Model JSON serialize()

  44. 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
  45. 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
  46. 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
  47. JsonEncoder • Convert between Array and JSON

  48. 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
  49. Setup annotation reader

  50. Install doctrine/annotaions $ composer require doctrine/annotations [note] You can use

    YAML/XML configurations instead of annotations
  51. Doctrine Annotations • The Reader utility of Annotation and Attribute

    class Sample { /** * @SampleAnnotation */ public function Foo(): void { } } class Sample { #[SampleAttribute] public function Foo(): void { } }
  52. 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
  53. 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
  54. [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'], ]);
  55. 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
  56. Output of 103 [ {"id":1,"name":"Mugi","imageUrl":"mugi.jpg"}, {"id":2,"name":"Sora","imageUrl":"sora.jpg"} ] camelCase

  57. 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
  58. 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
  59. [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
  60. 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
  61. 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
  62. 200: Deserialize

  63. Deserialize Data Model JSON deserialize()

  64. 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
  65. 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
  66. 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
  67. 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
  68. 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
  69. Situation 2: Admin App

  70. Admin App

  71. 300: Serialize to CSV

  72. 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
  73. Yes! PHP has fputcsv, But forget today!

  74. 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
  75. 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
  76. Selected columns id, image_url, gender 1, "mugi.jpg", "Female" 2, "sora.jpg",

    "Male" 3, "leo.jpg", "Male" 4, "coco.jpg", "Female"
  77. 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
  78. 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
  79. 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
  80. [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
  81. 400: Deserialize from CSV

  82. 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
  83. 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
  84. 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
  85. 500: Custom Encoder

  86. 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 = [] */); }
  87. TOML https://toml.io/en/

  88. 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
  89. 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
  90. 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
  91. 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
  92. 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 = <<<TOML id = 1 name = "Mugi" image_url = "mugi.jpg" gender = "Female" weight = 1.8' TOML; $object = $serializer->deserialize($toml, Cat::class, 'toml'); print_r($object); app/500/502_toml_deserialize.php
  93. 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
  94. [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/
  95. [TIP] Custom Normalizer • You can create Custom Normalizer like

    Custom Encoder • Implement the NormalizerInterface and DenormalizerInterface
  96. Inside Serializer

  97. 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;
  98. 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
  99. ChainDecorder implements these: final public function decode(string $data, string $format,

    array $context = []): mixed public function supportsDecoding(string $format, array $context = []): bool
  100. 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
  101. 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
  102. 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
  103. Conclusion

  104. Serializer = Normalizer + Encoder • Normalizer transforms keys and

    values • Encoder transforms format Object Format (JSON, XML, CSV) Array serialize deserialize normalize denormalize encode decode
  105. 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
  106. 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
  107. 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
  108. Read the codes • Yes, you are a developer, read

    the codes on GitHub • https://github.com/symfony/serializer
  109. That's all, thank you!

  110. Appendix

  111. 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/
  112. 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
  113. 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