Symfony Serializer Component

0b8df4309d2e40efce1208242c35a058?s=47 jucabrera
November 22, 2019

Symfony Serializer Component

Slides presented in SymfonyCon Amsterdam

0b8df4309d2e40efce1208242c35a058?s=128

jucabrera

November 22, 2019
Tweet

Transcript

  1. Symfony Serializer There and back again 1

  2. Juciellen Cabrera @jucycabrera Brazilian Mother Developer over 10 years Software

    Engineer | Smartbox in Dublin Coorganizer | PHPWomen Brazil 2
  3. Motivation 3 • json_encode() doesn’t do everything we need •

    unserialize() is not safe • Different views for the same object • Recursive encoding/decoding
  4. Serializer Component 4 Turns objects into a specific format (XML,

    JSON, Yaml, ...) and the other way around.
  5. 5

  6. Main Projects Using Serializer Over 800 projects Drupal 6

  7. Serialization is The process of converting an object into a

    stream of bytes to store the object or transmit it to memory, a database, or a file. Its main purpose is to save the state of an object in order to be able to recreate it when needed. https://docs.microsoft.com/en-us/dotnet/csharp/programming-guide/concepts/serialization/ 7
  8. 8 We are talking about API

  9. 9 Normalization

  10. Converting object into array • ObjectNormalizer • DateTimeZoneNormalizer • PropertyNormalizer

    • GetSetMethodNormalizer • DataUriNormalizer • ConstraintViolationListNormalizer • DateIntervalNormalizer • JsonSerializableNormalizer • CustomNormalizer • ArrayDenormalizer Normalizers 10
  11. GetSetMethod Normalizer • Normalization => getters • Denormalization => setters

    and constructor
  12. PropertyNormalizer • Directly reads and writes properties • Even being

    private or protected properties • It supports calling the constructor during the denormalization process.
  13. ObjectNormalizer The most powerful normalizer It is configured by default

    in Symfony applications with the Serializer component enabled. • Can access properties directly • Or getters, setters, hassers, adders and removers • Supports calling the constructor during the denormalization process. *PropertyAccess Component required 13
  14. DateTimeNormalizer • Converts DateTimeInterface objects into strings Y-m-d\TH:i:sP 14

  15. 15 Encoding

  16. • Csv • Json • Yaml • Xml Encoders /

    Decoders 16
  17. None
  18. Serializer class /** * @param (NormalizerInterface|DenormalizerInterface)[] $normalizers * @param (EncoderInterface|DecoderInterface)[]

    $encoders */ public function __construct(array $normalizers = [], array $encoders = []) 18
  19. You can define all normalizers and encoders that you want

    to be available $normalizers = [$datetimeNormalizer, propertyNormalizer]; $encoders = [$jsonEncoder, $csvEncoder, $xmlEncoder]; $serializer = new Serializer( $normalizers, $encoders ); Serializer class 19
  20. Serializer class - some methods • public function serialize($data, $format,

    array $context = []); • public function deserialize($data, $type, $format, array $context = []); • public function normalize($data, $format = null, array $context = []) • public function denormalize($data, $type, $format = null, array $context = []) • final public function encode($data, $format, array $context = []) • final public function decode($data, $format, array $context = []) 20
  21. class Pokemon { private $id; private $name; private $cp; private

    $caughtAt; private $weight; private $height; private $hp; private $battles; } 21
  22. $pokemon = new Pokemon(); $pokemon->setId(25); $pokemon->setName('Pikachu'); $pokemon->setHeight(0.4); $pokemon->setWeight(6); $pokemon->setCaughtAt(new DateTime());

    $pokemon->setHp(66); $pokemon->setCp(450); 22
  23. To use the Serializer component, set up the Serializer specifying

    which encoders and normalizer are going to be available. echo $serializer->serialize($pokemon,JsonEncoder::FORMAT); { "id": 25, "name": "Pikachu", "cp": 450, "caughtAt": "2019-11-17T21:24:03+00:00", "weight": 6, "height": 0.4, "hp": 66, "battles": null } Serializing to JSON 23
  24. Selecting attributtes $context = [ ObjectNormalizer::ATTRIBUTES => [ 'id','name', 'cp'

    ] ]; echo $serializer->serialize($pokemon, 'json', $context); { "id": 25, "name": "Pikachu", "cp": 450 } 24
  25. Ignoring Attributes $context = [ AbstractObjectNormalizer::IGNORED_ATTRIBUTES => [ 'battles', 'caughtAt']

    ]; echo $serializer->serialize($player, 'json', $context); { "id": 25, "name": "Pikachu", "cp": 450, "weight": 6, "height": 0.4, "hp": 66 } 25
  26. Skipping null values $pokemon = new Pokemon(); $pokemon->setId(25); $pokemon->setName('Pikachu'); $pokemon->setHp(527);

    $context = [AbstractObjectNormalizer::SKIP_NULL_VALUES => true]; echo $serializer->serialize($pokemon,'json', $context); { "pokemonId": 25, "name": "Pikachu", "hp": 527 } 26
  27. Serializing Arrays Serialization $pokemonCollection = [$pokemon1, $pokemon2]; echo $serializer->serialize($pokemonCollection, JsonEncoder::FORMAT);

    [{ "name": "Porygon" }, { "name": "Ditto" }] 27
  28. Boolean Attributes Methods prefixed by is, the Serializer component will

    automatically detect and use it to serialize related attributes. class Pokemon { public function isAlive() { return (boolean) $this->hp; } } { "id": 25, "name": "Pikachu", "cp": 450, "caughtAt": "2019-11-18T23:03:38+00:00", "weight": 6, "height": 0.4, "hp": 66, "battles": null, "alive": true } 28
  29. SerializedName class Pokemon { /** * @var int * @SerializedName("pokemonId")

    */ public $id; } 29
  30. SerializedName echo $serializer->serialize($pokemon,'json', $context); { "pokemonId": 25, "name": "Pikachu", "cp":

    450, "caughtAt": "2019-11-18T23:03:38+00:00", "weight": 6, "height": 0.4, "hp": 66, "battles": null } 30
  31. Groups XML, YAML and PHP supported Symfony\Component\Serializer\Annotation\Group s; class Pokemon

    { /** * @var int * @Groups("list") */ public $id; /** * @var string * @Groups({"list","catch"}) */ private $name; /** * @var integer * @Groups({"list","catch"}) */ private $cp; /** * @var \DateTime * @Groups({list") */ private $caughtAt; /** * @var float * @Groups("list") */ private $weight; /** * @var float * @Groups("list") */ private $height; 31
  32. Groups $context = [ObjectNormalizer::GROUPS=>'catch']; echo $serializer->serialize($pokemon, 'json', $context); Catch screen

    { "name": "Pikachu", "cp": 450 } 32
  33. Groups $context = [ObjectNormalizer::GROUPS=>[list]]; echo $serializer->serialize($pokemon, 'json', $context); { "id":

    25, "name": "Pikachu", "cp": 450, "caughtAt": "2019-11-18T23:26:13+00:00", "weight": 6, "height": 0.4 } 33
  34. class Pokemon { /** * @var Battle [] */ private

    $battles; } class Battle { /** * @var Pokemon [] */ protected $participants; } Circular Reference Circular references are common when dealing with entity relations: 34
  35. To avoid infinite loops, GetSetMethodNormalizer or ObjectNormalizer throw a CircularReferenceException

    when such a case is encountered: echo $serializer->serialize($battle, 'json'); // A circular reference has been detected when serializing the object of class "Battle" (configured limit: 1) Circular Reference 35
  36. Instead of throwing an exception, circular references can also be

    handled by custom callables $defaultContext = [ AbstractNormalizer::CIRCULAR_REFERENCE_HANDLER => function ($object, $format, $context) { return $object->getDate()->format(DateTime::RFC3339); }, ]; $objectNormalizer = new ObjectNormalizer(null, null, null, null, null, null, $defaultContext); Circular Reference 36
  37. echo $serializer->serialize($battle, 'json'); { "participants": [{ "name": "Ursaring", "battles": ["2019-11-11T22:19:38+00:00"]

    }, { "name": "Muk", "battles": ["2019-11-11T22:19:38+00:00"] }], "date": "2019-11-11T22:19:38+00:00" } Circular Reference 37
  38. Deserialization 38

  39. Handling Arrays Deserialization $collection = $serializer->deserialize($serializedCollection, 'Pokemon[]', JsonEncoder::FORMAT ); var_dump($collection);

    array(2) { [0]=> object(Pokemon)#37 (8) { ["name":"Pokemon":private]=> string(7) "Porygon" } [1]=> object(Pokemon)#36 (8) { ["name":"Pokemon":private]=> string(5) "Ditto" } } 39
  40. Type Safety * PropertyInfo Component class Pokemon { /** *

    @var int */ private $id; /** * @var string */ private $name; /** * @var float */ private $weight; } 40
  41. Type Safety * PropertyInfo Component $json = '{"id":”52”,"name":"Meowth","weight":”1.5”}'; var_dump($serializer->deserialize($json, Pokemon::class,

    'json')); //The type of the "id" attribute for class "Pokemon" must be one of "int" ("string" given). 41
  42. Type Safety * PropertyInfo Component $json = '{"id":52,"name":"Meowth","weight":1.5}'; var_dump($serializer->deserialize($json, Pokemon::class,

    'json')); object(Pokemon)#13 (8) { ["id"]=> int(52) ["name":"Pokemon":private]=> string(6) "Meowth" ["weight":"Pokemon":private]=> float(1.5) } 42
  43. Recursivity $json = ' { "id": 52, "name": "Meowth", "weight":

    1.5, "caughtAt": "2019-10-23 11:35:00" }'; var_dump($serializer->deserialize($json, Pokemon::class, 'json')); 43
  44. Recursivity object(Pokemon)#14 (8) { ["id":"Pokemon":private]=> int(52) ["name":"Pokemon":private]=> string(6) "Meowth" ["cp":"Pokemon":private]=>

    NULL ["caughtAt":"Pokemon":private]=> object(DateTime)#15 (3) { ["date"]=> string(26) "2019-10-23 11:35:00.000000" ["timezone_type"]=> int(3) ["timezone"]=> string(3) "UTC" } ["weight":"Pokemon":private]=> float(1.5) ["height":"Pokemon":private]=> NULL ["hp":"Pokemon":private]=> NULL ["battles":"Pokemon":private]=> NULL } 44
  45. More encoders 45

  46. CsvEncoder $collection = [$bulbasaur,$vaporeon]; echo $serializer->serialize($collection, CsvEncoder::FORMAT); id,name,cp,caughtAt,weight,height,xp,hp,alive 1,Bulbasaur,250,2019-11-14T00:02:21+00:00,0.4,2.5,527,527,1 134,Vaporeon,751,2019-11-14T00:02:21+00:00,3.09,0.84,112,112,1

    46
  47. XmlEncoder echo $serializer->serialize($collection, XmlEncoder::FORMAT); <?xml version="1.0" encoding="UTF-8"?> <response> <item key="0">

    <id>1</id> <name>Bulbasaur</name> <cp>250</cp> <caughtAt>2019-11-14T00:04:44+00:00</caughtAt> <weight>0.4</weight> <height>2.5</height> <xp>527</xp> <hp>527</hp> <alive>1</alive> </item> <item key="1"> <id>134</id> <name>Vaporeon</name> <cp>751</cp> <caughtAt>2019-11-14T00:04:44+00:00</caughtAt> <weight>3.09</weight> <height>0.84</height> <xp>112</xp> <hp>112</hp> <alive>1</alive> </item> </response> 47
  48. What's more? • Custom Normalizers • Handling Constructor Arguments •

    Serializing Interfaces and Abstract Classes • Depth limit 48
  49. Installation composer require symfony/serializer-pack 49

  50. References https://symfony.com/doc/current/components/serializer.html https://symfony.com/doc/current/serializer.html https://github.com/symfony/serializer https://symfony.com/components/Serializer https://packagist.org/packages/symfony/serializer/dependents 50

  51. Thank you :-) Feedback are welcome @jucycabrera 51