Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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

    View Slide

  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

    View Slide

  3. About me

    View Slide

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

    View Slide

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

    View Slide

  6. What is Symfony Serializer

    View Slide

  7. This talks based on the image of official docs

    View Slide

  8. Symfony Serializer is like "Converter"
    • Convert between Object and Format Object
    Format (JSON, XML, CSV)
    convert

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  14. So far, you understand about 80%
    of the Symfony Serializer, now

    View Slide

  15. Next,

    View Slide

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

    View Slide

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

    View Slide

  18. Congratulations
    You will master Symfony Serializer

    View Slide

  19. That's all

    View Slide

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

    View Slide

  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

    View Slide

  22. How to use Symfony Serializer

    View Slide

  23. Install
    $ composer require symfony/serializer

    View Slide

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

    View Slide

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

    View Slide

  26. Situation 1: Mobile App

    View Slide

  27. Mobile App
    Detail view List view

    View Slide

  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

    View Slide

  29. JSON API for Mobile App
    DB API Mobile App
    JSON

    View Slide

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

    "gender": "Female",
    "weight": 1.8
    }

    View Slide

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

    View Slide

  32. 000: json_encode style

    View Slide

  33. json_encode

    View Slide

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

    View Slide

  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

    View Slide

  36. [TIPS] You can find codes on my GitHub repo
    src/ViewModel/DetailView.php
    https://github.com/suzuki/symfony-serializer-samples/

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  41. 100: Symfony Serializer style

    View Slide

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

    View Slide

  43. Symfony Serializer
    Data Model JSON
    serialize()

    View Slide

  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

    View 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

    View Slide

  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

    View Slide

  47. JsonEncoder
    • Convert between Array and JSON

    View Slide

  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

    View Slide

  49. Setup annotation reader

    View Slide

  50. Install doctrine/annotaions
    $ composer require doctrine/annotations
    [note] You can use YAML/XML configurations instead of annotations

    View Slide

  51. Doctrine Annotations
    • The Reader utility of Annotation and Attribute
    class Sample
    {
    /**
    * @SampleAnnotation
    */
    public function Foo(): void
    {
    }
    }
    class Sample
    {
    #[SampleAttribute]
    public function Foo(): void
    {
    }
    }

    View Slide

  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

    View Slide

  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

    View Slide

  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'],
    ]);

    View Slide

  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

    View Slide

  56. Output of 103
    [
    {"id":1,"name":"Mugi","imageUrl":"mugi.jpg"},
    {"id":2,"name":"Sora","imageUrl":"sora.jpg"}
    ]
    camelCase

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  62. 200: Deserialize

    View Slide

  63. Deserialize
    Data Model JSON
    deserialize()

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  69. Situation 2: Admin App

    View Slide

  70. Admin App

    View Slide

  71. 300: Serialize to CSV

    View Slide

  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

    View Slide

  73. Yes! PHP has fputcsv,
    But forget today!

    View Slide

  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

    View Slide

  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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  81. 400: Deserialize from CSV

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  85. 500: Custom Encoder

    View Slide

  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 = [] */);
    }

    View Slide

  87. TOML
    https://toml.io/en/

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  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 = <<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

    View Slide

  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

    View Slide

  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/

    View Slide

  95. [TIP] Custom Normalizer
    • You can create Custom Normalizer like Custom Encoder
    • Implement the NormalizerInterface and DenormalizerInterface

    View Slide

  96. Inside Serializer

    View Slide

  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;

    View Slide

  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

    View Slide

  99. ChainDecorder implements these:
    final public function decode(string $data, string $format, array $context = []): mixed
    public function supportsDecoding(string $format, array $context = []): bool

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  103. Conclusion

    View Slide

  104. Serializer = Normalizer + Encoder
    • Normalizer transforms
    keys and values
    • Encoder transforms
    format
    Object
    Format (JSON, XML, CSV)
    Array serialize
    deserialize
    normalize
    denormalize
    encode
    decode

    View Slide

  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

    View Slide

  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

    View Slide

  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

    View Slide

  108. Read the codes
    • Yes, you are a developer, read the codes on GitHub
    • https://github.com/symfony/serializer

    View Slide

  109. That's all, thank you!

    View Slide

  110. Appendix

    View Slide

  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/

    View Slide

  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

    View Slide

  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

    View Slide