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. 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
  2. Symfony Serializer is like "Converter" • Convert between Object and

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

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

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

    XML, CSV) Array serialize deserialize normalize denormalize encode decode
  8. [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
  9. 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
  10. Detail view GET /cats/{id} { "id": 1, "name": "Mugi", "image_url":

    "IMAGE_URL",
 "gender": "Female", "weight": 1.8 }
  11. 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"} ]
  12. 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
  13. [TIPS] You can find codes on my GitHub repo src/ViewModel/DetailView.php

    https://github.com/suzuki/symfony-serializer-samples/
  14. 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
  15. 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
  16. 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
  17. One data model, Two view models • Using two View

    Model, json_encode can flexible output • It's a good way to implement these APIs
  18. 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
  19. 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
  20. 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
  21. 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
  22. Doctrine Annotations • The Reader utility of Annotation and Attribute

    class Sample { /** * @SampleAnnotation */ public function Foo(): void { } } class Sample { #[SampleAttribute] public function Foo(): void { } }
  23. 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
  24. 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
  25. [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'], ]);
  26. 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
  27. 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
  28. 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
  29. [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
  30. 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
  31. 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
  32. 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
  33. 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
  34. 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
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. Selected columns id, image_url, gender 1, "mugi.jpg", "Female" 2, "sora.jpg",

    "Male" 3, "leo.jpg", "Male" 4, "coco.jpg", "Female"
  41. 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
  42. 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
  43. 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
  44. [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
  45. 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
  46. 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
  47. 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
  48. 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 = [] */); }
  49. 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
  50. 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
  51. 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
  52. 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
  53. 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
  54. 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
  55. [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/
  56. [TIP] Custom Normalizer • You can create Custom Normalizer like

    Custom Encoder • Implement the NormalizerInterface and DenormalizerInterface
  57. 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;
  58. 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
  59. ChainDecorder implements these: final public function decode(string $data, string $format,

    array $context = []): mixed public function supportsDecoding(string $format, array $context = []): bool
  60. 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
  61. 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
  62. 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
  63. Serializer = Normalizer + Encoder • Normalizer transforms keys and

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

    the codes on GitHub • https://github.com/symfony/serializer
  68. 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/
  69. 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
  70. 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