Save 37% off PRO during our Black Friday Sale! »

Introduction to Serialization with Symfony2

E2ed7c278c8c49bb3e7fe0b7de039997?s=47 Hugo Hamon
November 23, 2012

Introduction to Serialization with Symfony2

Symfony comes with a Serializer component, which allows you to normalize and serialize your objects to multiple representations such as XML or JSON. This component becomes very helpful in certain circumstances like exposing data through a REST API. This talk will be split into three different parts. The first part will introduce the Serializer component underneath architecture and its basic usage. Then, you will learn how easy it is to extend the serializer in order to add custom serialization formats. Finally, you will discover how you can take benefit from the JMSSerializerBundle bundle that makes objects serialization easy, fun and intuitive with annotations.

E2ed7c278c8c49bb3e7fe0b7de039997?s=128

Hugo Hamon

November 23, 2012
Tweet

Transcript

  1. http://www.flickr.com/photos/netzer/38270856 Leveraging the Serializer Component

  2. Hugo HAMON SensioLabs

  3. 1. Introduction

  4. Serialization is the process of converting an object state into

    a format that can be stored and resurrected later Wikipedia  
  5. The Serializer serializes and unserializes objects.

  6. Normalizers Array Serializer Object XML JSON YAML … Denormalizers Encoders

    Decoders
  7. The Serializer Interface.

  8. namespace Symfony\Component\Serializer; interface SerializerInterface { /** * Serializes data in

    the appropriate format * * @param mixed $data any data * @param string $format format name * @return string */ public function serialize($data, $format); /** * Deserializes data into the given type. * * @param mixed $data * @param string $type * @param string $format */ public function deserialize($data, $type, $format); }
  9. The Normalizer Interface.

  10. namespace Symfony\Component\Serializer\Normalizer; interface NormalizerInterface { /** * Normalizes an object

    into a set of arrays/scalars * * @param object $object object to normalize * @param string $format format the normalization result will be encoded as * @return array|scalar */ public function normalize($object, $format = null); /** * Checks whether the given data are supported for normalization. * * @param mixed $data Data to normalize. * @param string $format The format being (de-)serialized from or into. * @return Boolean */ public function supportsNormalization($data, $format = null); }
  11. The Denormalizer Interface.

  12. interface DenormalizerInterface { /** * Denormalizes data back into an

    object of the given class. * * @param mixed $data data to restore * @param string $class the expected class to instantiate * @param string $format format the given data was extracted from * @return object */ public function denormalize($data, $class, $format = null); /** * Checks whether the given data are supported for denormalization. * * @param mixed $data Data to denormalize from. * @param string $type The class to which the data should be denormalized. * @param string $format The format being deserialized from. * @return Boolean */ public function supportsDenormalization($data, $type, $format = null); }
  13. The Encoder Interface.

  14. namespace Symfony\Component\Serializer\Encoder; interface EncoderInterface { /** * Encodes data into

    the given format * * @param mixed $data Data to encode * @param string $format Format name * * @return scalar */ public function encode($data, $format); /** * Checks whether the serializer can encode to given format * * @param string $format format name * @return Boolean */ public function supportsEncoding($format); }
  15. The Decoder Interface.

  16. namespace Symfony\Component\Serializer\Encoder; interface DecoderInterface { /** * Decodes a string

    into PHP data * * @param string $data Data to decode * @param string $format Format name * * @return mixed */ public function decode($data, $format); /** * Checks whether the serializer can decode from given format * * @param string $format format name * @return Boolean */ public function supportsDecoding($format); }
  17. 2. Basic Usage

  18. use Symfony\Component\Serializer\Serializer; use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer; use Symfony\Component\Serializer\Encoder\JsonEncoder; use Symfony\Component\Serializer\Encoder\XmlEncoder; // Register

    the normalizers $normalizers[] = new GetSetMethodNormalizer(); // Register the encoders $encoders[] = new JsonEncoder(); $encoders[] = new XmlEncoder(); // Create and initialize the serializer $serializer = new Serializer($normalizers, $encoders);
  19. Serializing an Object.

  20. $serializer ->serialize($object, 'xml') ;

  21. None
  22. Ignoring attributes from serialization.

  23. $ignore = array('id', 'customer'); $normalizer = new GetSetMethodNormalizer(); $normalizer->setIgnoredAttributes($ignore);

  24. No more ID and Customer name!

  25. Unserializing a string.

  26. $xml = '<response>...</response>'; // Convert an XML string to an

    array $data = $serializer->decode($xml, 'xml'); // Convert the array to an object $class = 'Acme\BookingBundle\Booking'; $booking = $serializer ->denormalize($data, $class, 'xml') ;
  27. 3. Extending the Serializer

  28. Adding support for YAML serialization.

  29. namespace Acme\SerializerBundle\Encoder; use Symfony\Component\Serializer\Encoder\EncoderInterface; use Symfony\Component\Yaml\Dumper; class YamlEncoder implements EncoderInterface

    { public function encode($data, $format) { $yaml = new Dumper(); return $yaml->dump($data, 2); } public function supportsEncoding($format) { return 'yaml' === $format; } }
  30. reference: SLT-123456 arrival: '2012-09-26' departure: '2012-09-29' designation: 'Deluxe Suite'

  31. Adding support for YAML unserialization.

  32. namespace Acme\SerializerBundle\Encoder; // ... use Symfony\Component\Serializer\Encoder\DecoderInterface; use Symfony\Component\Yaml\Parser; class YamlEncoder

    implements EncoderInterface, DecoderInterface { // ... public function decode($data, $format) { $yaml = new Parser(); return $yaml->parse($data); } public function supportsDecoding($format) { return 'yaml' === $format; } }
  33. $yaml = <<<EOY reference: SLT-123456 arrival: '2012-09-26' departure: '2012-09-29' designation:

    'Deluxe Suite' EOY; $class = 'Acme\BookingBundle\Booking'; $data = $serializer->decode($yaml, 'yaml'); $booking = $serializer->denormalize($data, $class, 'yaml'); echo $booking->getReference();
  34. 4. The JMSSerializer Bundle

  35. The Serializer service.

  36. $s = $container->get('serializer'); $json = $s->serialize($object, 'json'); $xml = $s->serialize($object,

    'xml'); $yml = $s->serialize($object, 'yml'); $type = 'Acme\BookingBundle\Booking'; $obj = $s->deserialize($xml, $type, 'xml'); $obj = $s->deserialize($yml, $type, 'yml');
  37. Twig helpers.

  38. {# uses JSON #} {{ booking | serialize }} {#

    Custom output format #} {{ booking | serialize('json') }} {{ booking | serialize('xml') }} {{ booking | serialize('yml') }}
  39. YAML Con guration

  40. # AcmeBookingBundle\Resources\config\serializer\Booking.yml Acme\BookingBundle\Booking: exclusion_policy: ALL xml_root_name: booking exclude: true access_type:

    public_method # ... properties: reference: expose: true type: string amount: expose: true type: double # ...
  41. XML Con guration

  42. # AcmeBookingBundle\Resources\config\serializer\Booking.xml <?xml version="1.0" encoding="UTF-8" ?> <serializer> <class name="Acme\BookingBundle\Booking" exclusion-policy="ALL"

    xml-root-name="booking" access-type="public_method" > <property name="reference" expose="true" type="string"/> <property name="amount" expose="true" type="double"/> </class> </serializer>
  43. Annotations Con guration

  44. namespace Acme\BookingBundle; use JMS\SerializerBundle\Annotation as Serialize; /** * @Serialize\XmlRoot("booking") */

    class Booking { /** * @Serialize\XmlAttribute * @Serialize\Type("string") */ private $reference; // ... }
  45. Customizing the XML root node.

  46. namespace Acme\BookingBundle; use JMS\SerializerBundle\Annotation as Serialize; /** * @Serialize\XmlRoot("booking") */

    class Booking { // ... }
  47. <?xml version="1.0" encoding="UTF-8"?> <booking> <reference>SLT-123456</reference> <arrival>2012-09-26</arrival> <departure>2012-09-29</departure> <designation>Deluxe Suite</designation> </booking>

  48. Serializing a property to a XML attribute.

  49. namespace Acme\BookingBundle; use JMS\SerializerBundle\Annotation as Serialize; /** * @Serialize\XmlRoot("booking") */

    class Booking { /** @Serialize\XmlAttribute */ private $reference; // ... }
  50. <?xml version="1.0" encoding="UTF-8"?> <booking reference="SLT-123456"> <arrival>2012-09-26</arrival> <departure>2012-09-29</departure> <designation>Deluxe Suite</designation> </booking>

  51. Serializing an object to a single XML node

  52. /** * @Serialize\XmlRoot("booking") * */ class Booking { /** *

    @var Price */ private $price; }
  53. class Price { /** @Serialize\XmlValue */ private $amount; /** @Serialize\XmlAttribute

    */ private $vat; /** @Serialize\XmlAttribute */ private $currency; }
  54. $booking = new Booking(); $booking->setReference('SLT-123456'); // ... $booking->setPrice(new Price(120, 12,

    'EUR')); <?xml version="1.0" encoding="UTF-8"?> <booking> <!-- ... --> <departure>2012-09-29</departure> <price vat="12" currency="EUR">120</price> </booking>
  55. Excluding or Exposing properties.

  56. /** * @Serialize\ExclusionPolicy("all") * @Serialize\XmlRoot("booking") */ class Booking { /**

    * @Serialize\Expose */ private $reference; }
  57. <booking> <reference>SLT-123456</reference> </booking>

  58. /** * @Serialize\ExclusionPolicy("none") * @Serialize\XmlRoot("booking") */ class Booking { /**

    * @Serialize\Exclude */ private $price; }
  59. <?xml version="1.0" encoding="UTF-8"?> <booking> <reference>SLT-123456</reference> <arrival>2012-09-26</arrival> <departure>2012-09-29</departure> <designation>Deluxe Suite</designation> </booking>

  60. Managing objects properties versioning

  61. // ... class Booking { /** * @Serialize\Since("1.8.4") * @Serialize\SerializedName("loyalty")

    */ private $loyaltyNumber; // ... }
  62. // ... class Booking { /** * @Serialize\Until("1.8.3") * @Serialize\SerializedName("loyalty")

    */ private $membershipNumber; // ... }
  63. $booking->setMembershipNumber('3942945839'); $booking->setLoyaltyNumber('0973525332'); $serializer->setVersion('1.8.2'); <?xml version="1.0" encoding="UTF-8"?> <booking> <!-- ... -->

    <loyalty>3942945839</loyalty> </booking>
  64. $booking->setMembershipNumber('3942945839'); $booking->setLoyaltyNumber('0973525332'); $serializer->setVersion('1.9'); <?xml version="1.0" encoding="UTF-8"?> <booking> <!-- ... -->

    <loyalty>0973525332</loyalty> </booking>
  65. Detecting the version & format automatically

  66. namespace Acme\BookingBundle\Listener; use Symfony\Component\HttpKernel\Event\KernelEvent; use JMS\SerializerBundle\Serializer\SerializerInterface; class ApiVersionListener { public

    function __construct(SerializerInterface $serializer) { $this->serializer = $serializer; } public function onKernelRequest(KernelEvent $event) { $request = $event->getRequest(); $accept = $request->headers->get('Accept'); // look for: application/vnd.hhamon-v{version}+{format} if ($accept && preg_match('/([0-9\.]+)?\+(xml|json)$/', $accept, $matches)) { $request->setRequestFormat($matches[2]); $this->serializer->setVersion($matches[1]); } } }
  67. Offering several views of a serialized object.

  68. // ... class Booking { /** @Serialize\Groups({ "list" }) */

    private $id; /** @Serialize\Groups({ "list", "details" }) */ private $reference; /** @Serialize\Groups({ "details" }) */ private $designation; }
  69. <booking> <reference>SLT-123456</reference> <designation>Deluxe Suite</designation> </booking> $serializer = $this->get('serializer'); $serializer->setGroups(array('details')); $serializer->serialize($booking,

    'xml');
  70. Forcing the serializer to call getters & setters.

  71. class Booking { /** @Serialize\AccessType("public_method") */ private $arrival; public function

    setArrival($arrival) { $this->arrival = new \DateTime($arrival); } public function getArrival() { return $this->arrival->format('m/d/Y'); } }
  72. <?xml version="1.0" encoding="UTF-8"?> <booking> <reference>SLT-123456</reference> <arrival>09/26/2012</arrival> <departure>2012-09-29</departure> <!-- ... -->

    </booking>
  73. Mapping a property to another getter or setter method.

  74. class Booking { /** * @Serialize\Accessor( * setter = "setShortDesignation",

    * getter = "getShortDesignation" * ) * */ private $designation; }
  75. Customizing the order in which properties are serialized.

  76. /** * @Serialize\AccessOrder("alphabetical") * */ class Booking { // ...

    }
  77. /** * @Serialize\AccessOrder(custom = { * "reference", * "arrival", *

    "departure" * }) * */ class Booking { // ... }
  78. <?xml version="1.0" encoding="UTF-8"?> <booking> <reference>SLT-123456</reference> <arrival>2012-09-26</arrival> <departure>2012-09-29</departure> <designation>Deluxe Suite</designation> <loyalty>3942945839</loyalty>

    <price currency="EUR" vat="19.6">119.6</price> </booking>
  79. Typing properties for a complete unserialization.

  80. class Booking { /** @Serialize\Type("string") */ private $reference; /** @Serialize\Type("DateTime")

    */ private $arrival; /** @Serialize\Type("Acme\BookingBundle\Price") */ private $price; }
  81. 5. Doctrine Support

  82. Serializing a Doctrine entity

  83. class BookingController extends Controller { /** * @Route( * "/booking/{reference}.{_format}",

    * defaults = { "_format" = "xml" } * ) */ public function showAction(Booking $booking) { $serializer = $this->container->get('serializer'); $xml = $serializer->serialize($booking, 'xml'); return new Response($xml); } }
  84. <?xml version="1.0" encoding="UTF-8"?> <booking reference="GH45IZ8"> <id>1</id> <arrival><![CDATA[12/23/2012]]></arrival> <departure><![CDATA[12/25/2012]]></departure> <amount>440</amount> <vat>86.24</vat>

    <currency><![CDATA[EUR]]></currency> <customer username="hhamon"/> <rooms> <entry> <id>1</id> <number><![CDATA[420]]></number> <type><![CDATA[double]]></type> <price>130</price> </entry> <entry> <id>5</id> <number><![CDATA[518]]></number> <type><![CDATA[single]]></type> <price>90</price> </entry> </rooms> </booking>
  85. Serializing a Doctrine Collection

  86. class BookingController extends Controller { public function indexAction() { $em

    = $this->get('doctrine')->getManager(); $bookings = $em ->getRepository('AcmeBookingBundle:Booking') ->findAll() ; $serializer = $this->container->get('serializer'); $xml = $serializer->serialize($bookings, 'xml'); return new Response($xml); } }
  87. None
  88. Unserializing a Doctrine entity

  89. $json = '{"id":1,"reference":"GH45IZ8","arrival":"12\/ 23\/2012","departure":"12\/25\/2012","amount": 440,"vat":86.24,"currency":"EUR","customer":{"id": 1, "username":"hhamon"},"rooms":[{"id": 1,"number":"420","type":"double","price":130},{"id": 5,"number":"518","type":"single","price":90}]}'; $serializer

    = $this->container->get('serializer'); $booking = $serializer->deserialize( $json, 'Acme\BookingBundle\Entity\Booking', 'json' );
  90. https://joind.in/talk/view/7568