Going further with the JMSSerializer

Going further with the JMSSerializer

B9f66200b0c0fc3e26b87af802b99c9c?s=128

Adrien Brault

April 05, 2013
Tweet

Transcript

  1. @adrienbrault Going further with the JMSSerializer 1

  2. About me • Adrien Brault • Student at SUPINFO Paris

    • Got this awesome badge • @AdrienBrault everywhere 2
  3. JMSSerializer “Easily serialize, and deserialize data of any complexity (supports

    XML, JSON, YAML)” 3
  4. Easy serialization class User { private $id; private $username; private

    $password; // Getters & Setters } $user = new User(); $user->setId(1); $user->setUsername('AdrienBrault'); $user->setPassword('ZOMGCLEARPASSWORD'); $serializer = JMS\Serializer\SerializerBuilder::create()->build(); 4
  5. Easy serialization class User { private $id; private $username; private

    $password; // Getters & Setters } $user = new User(); $user->setId(1); $user->setUsername('AdrienBrault'); $user->setPassword('ZOMGCLEARPASSWORD'); $serializer = JMS\Serializer\SerializerBuilder::create()->build(); <?xml version="1.0" encoding="UTF-8"?> <result> <id>1</id> <username><![CDATA[AdrienBrault]]></username> <password><![CDATA[ZOMGCLEARPASSWORD]]></password> </result> echo $serializer->serialize($user, 'xml'); 4
  6. Easy serialization class User { private $id; private $username; private

    $password; // Getters & Setters } $user = new User(); $user->setId(1); $user->setUsername('AdrienBrault'); $user->setPassword('ZOMGCLEARPASSWORD'); $serializer = JMS\Serializer\SerializerBuilder::create()->build(); { "id": 1, "username": "AdrienBrault", "password": "ZOMGCLEARPASSWORD" } <?xml version="1.0" encoding="UTF-8"?> <result> <id>1</id> <username><![CDATA[AdrienBrault]]></username> <password><![CDATA[ZOMGCLEARPASSWORD]]></password> </result> echo $serializer->serialize($user, 'xml'); echo $serializer->serialize($user, 'json'); 4
  7. EASILY 5

  8. use JMS\Serializer\Annotation as Serializer; /** * @Serializer\XmlRoot("user") */ class User

    { /** * @Serializer\XmlAttribute * @Serializer\SerializedName("identifier") */ private $id; private $username; /** * @Serializer\Exclude */ private $password; // Getters & Setters } Let’s tune the serialization 6
  9. $serializer = ...; $user = new User(); $user->setId(4); $user->setUsername('AdrienBrault'); $user->setPassword('ZOMGCLEARPASSWORD');

    Let’s tune the serialization 7
  10. $serializer = ...; $user = new User(); $user->setId(4); $user->setUsername('AdrienBrault'); $user->setPassword('ZOMGCLEARPASSWORD');

    echo $serializer->serialize($user, 'xml'); Let’s tune the serialization <?xml version="1.0" encoding="UTF-8"?> <user identifier="4"> <username><![CDATA[AdrienBrault]]></username> </user> 7
  11. $serializer = ...; $user = new User(); $user->setId(4); $user->setUsername('AdrienBrault'); $user->setPassword('ZOMGCLEARPASSWORD');

    echo $serializer->serialize($user, 'json'); echo $serializer->serialize($user, 'xml'); Let’s tune the serialization <?xml version="1.0" encoding="UTF-8"?> <user identifier="4"> <username><![CDATA[AdrienBrault]]></username> </user> { "identifier": 4, "username": "AdrienBrault" } 7
  12. PRETTY COOL 8

  13. So, What’s new ? 9

  14. https://github.com/schmittjoh/serializer http://jmsyst.com/libs/serializer $serializer = JMS\Serializer\SerializerBuilder::create()->build(); $serializer->serialize($data, 'json'); Standalone library Used

    by the bundle since 0.11 10
  15. Context stateless serializer $serializer->setVersion(2); $serializer->setGroups(['list', 'details']); $serializer->setSerializeNull(true); $serializer->serialize($data, 'json'); Before

    0.12 11
  16. Context stateless serializer $serializer->setVersion(2); $serializer->setGroups(['list', 'details']); $serializer->setSerializeNull(true); $serializer->serialize($data, 'json'); Since

    0.12 $context = JMS\Serializer\SerializationContext::create() ->setVersion(2) ->setGroups(['list', 'detail']) ->setSerializeNull(true) ; $serializer->serialize($data, 'json', $context); Before 0.12 11
  17. DisjunctExclusionStrategy multiple exclusion strategies at the same time Before 0.12

    $serializer->setExclusionStrategy( new GroupsExclusionStrategy(['list', 'detail']) ); $serializer->serialize($data, 'json'); 12
  18. DisjunctExclusionStrategy multiple exclusion strategies at the same time Before 0.12

    $serializer->setExclusionStrategy( new GroupsExclusionStrategy(['list', 'detail']) ); $serializer->serialize($data, 'json'); Since 0.12 $context = SerializationContext::create() ->addExclusionStrategy(new GroupsExclusionStrategy(['list', 'detail'])) ->addExclusionStrategy(new VersionExclusionStrategy(2)) ; $serializer->serialize($data, 'json', $context); 12
  19. EventDispatcher Since 0.10 We’ll see it in detail later 13

  20. Before going further, PRO TIP 14

  21. Separate APIBundle jms_serializer: metadata: directories: api: namespace_prefix: 'Acme\WebBundle' path: '@ApiBundle/Resources/config/serializer'

    15
  22. Let’s see how the serializer ... serializes! 16

  23. Serializer internals UML class diagram 17

  24. object(Post)[1] public 'title' => string 'Hey guys!' (length=9) public 'tags'

    => array (size=2) 0 => object(Tag)[2] public 'name' => string 'news' (length=4) 1 => object(Tag)[3] public 'name' => string 'short' (length=5) 18
  25. object(Post)[1] public 'title' => string 'Hey guys!' (length=9) public 'tags'

    => array (size=2) 0 => object(Tag)[2] public 'name' => string 'news' (length=4) 1 => object(Tag)[3] public 'name' => string 'short' (length=5) { 18
  26. object(Post)[1] public 'title' => string 'Hey guys!' (length=9) public 'tags'

    => array (size=2) 0 => object(Tag)[2] public 'name' => string 'news' (length=4) 1 => object(Tag)[3] public 'name' => string 'short' (length=5) { “title”: “Hey guys” 18
  27. object(Post)[1] public 'title' => string 'Hey guys!' (length=9) public 'tags'

    => array (size=2) 0 => object(Tag)[2] public 'name' => string 'news' (length=4) 1 => object(Tag)[3] public 'name' => string 'short' (length=5) { “title”: “Hey guys” ,“tags”: [ 18
  28. object(Post)[1] public 'title' => string 'Hey guys!' (length=9) public 'tags'

    => array (size=2) 0 => object(Tag)[2] public 'name' => string 'news' (length=4) 1 => object(Tag)[3] public 'name' => string 'short' (length=5) { “title”: “Hey guys” ,“tags”: [ { 18
  29. object(Post)[1] public 'title' => string 'Hey guys!' (length=9) public 'tags'

    => array (size=2) 0 => object(Tag)[2] public 'name' => string 'news' (length=4) 1 => object(Tag)[3] public 'name' => string 'short' (length=5) { “title”: “Hey guys” ,“tags”: [ { “name”: “news” 18
  30. object(Post)[1] public 'title' => string 'Hey guys!' (length=9) public 'tags'

    => array (size=2) 0 => object(Tag)[2] public 'name' => string 'news' (length=4) 1 => object(Tag)[3] public 'name' => string 'short' (length=5) { “title”: “Hey guys” ,“tags”: [ { “name”: “news” 18
  31. object(Post)[1] public 'title' => string 'Hey guys!' (length=9) public 'tags'

    => array (size=2) 0 => object(Tag)[2] public 'name' => string 'news' (length=4) 1 => object(Tag)[3] public 'name' => string 'short' (length=5) { “title”: “Hey guys” ,“tags”: [ { “name”: “news” } 18
  32. object(Post)[1] public 'title' => string 'Hey guys!' (length=9) public 'tags'

    => array (size=2) 0 => object(Tag)[2] public 'name' => string 'news' (length=4) 1 => object(Tag)[3] public 'name' => string 'short' (length=5) { “title”: “Hey guys” ,“tags”: [ { “name”: “news” } ,{ “name”: “short” } 18
  33. object(Post)[1] public 'title' => string 'Hey guys!' (length=9) public 'tags'

    => array (size=2) 0 => object(Tag)[2] public 'name' => string 'news' (length=4) 1 => object(Tag)[3] public 'name' => string 'short' (length=5) { “title”: “Hey guys” ,“tags”: [ { “name”: “news” } ,{ “name”: “short” } 18
  34. object(Post)[1] public 'title' => string 'Hey guys!' (length=9) public 'tags'

    => array (size=2) 0 => object(Tag)[2] public 'name' => string 'news' (length=4) 1 => object(Tag)[3] public 'name' => string 'short' (length=5) { “title”: “Hey guys” ,“tags”: [ { “name”: “news” } ,{ “name”: “short” } ] 18
  35. object(Post)[1] public 'title' => string 'Hey guys!' (length=9) public 'tags'

    => array (size=2) 0 => object(Tag)[2] public 'name' => string 'news' (length=4) 1 => object(Tag)[3] public 'name' => string 'short' (length=5) { “title”: “Hey guys” ,“tags”: [ { “name”: “news” } ,{ “name”: “short” } ] } 18
  36. Handlers “Handlers allow you to change the serialization, or deserialization

    process for a single type/format combination.” 19
  37. $users = [ new User(1, 'AdrienBrault'), new User(20, 'John'), new

    User(50, 'Smith') ]; $pager = new Pagerfanta\Pagerfanta(new Pagerfanta\Adapter\ArrayAdapter($users)); $pager->setMaxPerPage(2); echo $serializer->serialize($pager, 'json'); Serialization of a Pagerfanta instance 20
  38. { "adapter": { "array": [ { "identifier": 1, "username": "AdrienBrault"

    }, { "identifier": 20, "username": "John" }, { "identifier": 50, "username": "Smith" } ] }, "allow_out_of_range_pages": false, "normalize_out_of_range_pages": false, "max_per_page": 2, "current_page": 1 } $users = [ new User(1, 'AdrienBrault'), new User(20, 'John'), new User(50, 'Smith') ]; $pager = new Pagerfanta\Pagerfanta(new Pagerfanta\Adapter\ArrayAdapter($users)); $pager->setMaxPerPage(2); echo $serializer->serialize($pager, 'json'); Serialization of a Pagerfanta instance 20
  39. { "adapter": { "array": [ { "identifier": 1, "username": "AdrienBrault"

    }, { "identifier": 20, "username": "John" }, { "identifier": 50, "username": "Smith" } ] }, "allow_out_of_range_pages": false, "normalize_out_of_range_pages": false, "max_per_page": 2, "current_page": 1 } $users = [ new User(1, 'AdrienBrault'), new User(20, 'John'), new User(50, 'Smith') ]; $pager = new Pagerfanta\Pagerfanta(new Pagerfanta\Adapter\ArrayAdapter($users)); $pager->setMaxPerPage(2); echo $serializer->serialize($pager, 'json'); Well ... Serialization of a Pagerfanta instance 20
  40. { "page": 1, "pages": 2, "total": 3, "results": [ {

    "identifier": 1, "username": "AdrienBrault" }, { "identifier": 20, "username": "John" } ] } This is what we’d like to get 21
  41. <?php use JMS\Serializer\GraphNavigator; use JMS\Serializer\Handler\SubscribingHandlerInterface; class PagerfantaHandler implements SubscribingHandlerInterface {

    public static function getSubscribingMethods() { return [ [ 'direction' => GraphNavigator::DIRECTION_SERIALIZATION, 'format' => 'json', 'type' => 'Pagerfanta\Pagerfanta', 'method' => 'serializeToJson', ] ]; } // ... } Writing our own PagerfantaHandler 22
  42. public function serializeToJson( JMS\Serializer\JsonSerializationVisitor $visitor, Pagerfanta\Pagerfanta $pager, array $type =

    null, JMS\Serializer\Context $context ) { $data = [ 'page' => $pager->getCurrentPage(), 'pages' => $pager->getNbPages(), 'total' => $pager->getNbResults(), 'results' => $pager->getCurrentPageResults(), ]; return $visitor->getNavigator()->accept( $data, ['name' => 'array'], $context ); } Writing our own PagerfantaHandler 23
  43. public function serializeToJson( JMS\Serializer\JsonSerializationVisitor $visitor, Pagerfanta\Pagerfanta $pager, array $type =

    null, JMS\Serializer\Context $context ) { $isRoot = null === $visitor->getRoot(); $data = [ 'page' => $pager->getCurrentPage(), 'pages' => $pager->getNbPages(), 'total' => $pager->getNbResults(), 'results' => $visitor->visitArray( $pager->getCurrentPageResults(), [], $context ), ]; if ($isRoot) { $visitor->setRoot($data); } return $data; } Writing our own PagerfantaHandler or another possible implementation 24
  44. $serializer = JMS\Serializer\SerializerBuilder::create() ->addDefaultHandlers() ->configureHandlers(function (HandlerRegistryInterface $registry) { $registry->registerSubscribingHandler(new PagerfantaHandler());

    }) ->build(); $users = [ new User(1, 'AdrienBrault'), new User(20, 'John'), new User(50, 'Smith') ]; $pager = new Pagerfanta\Pagerfanta(new ArrayAdapter($users)); $pager->setMaxPerPage(2); Writing our own PagerfantaHandler 25
  45. $serializer = JMS\Serializer\SerializerBuilder::create() ->addDefaultHandlers() ->configureHandlers(function (HandlerRegistryInterface $registry) { $registry->registerSubscribingHandler(new PagerfantaHandler());

    }) ->build(); $users = [ new User(1, 'AdrienBrault'), new User(20, 'John'), new User(50, 'Smith') ]; $pager = new Pagerfanta\Pagerfanta(new ArrayAdapter($users)); $pager->setMaxPerPage(2); echo $serializer->serialize($pager, 'json'); Tada! Writing our own PagerfantaHandler { "page": 1, "pages": 2, "total": 3, "results": [ { "identifier": 1, "username": "AdrienBrault" }, { "identifier": 20, "username": "John" } ] } 25
  46. Events “The serializer dispatches different events during the serialization, and

    deserialization process which you can use to hook in and alter the default behavior.” • serializer.pre_serialize • serializer.post_serialize • serializer.pre_deserialize • serializer.post_deserialize 26
  47. Avatar urls use case in a website Let’s say our

    users have awesome avatars, stored on an awesome CDN. 27
  48. Avatar urls use case in a website Let’s say our

    users have awesome avatars, stored on an awesome CDN. framework: templating: assets_base_urls: http: [ "http://static.site.com" ] 27
  49. Avatar urls use case in a website Let’s say our

    users have awesome avatars, stored on an awesome CDN. <img src="{{ asset(user.imagePath) }}"/> framework: templating: assets_base_urls: http: [ "http://static.site.com" ] 27
  50. Avatar urls use case in a website Let’s say our

    users have awesome avatars, stored on an awesome CDN. <img src="{{ asset(user.imagePath) }}"/> framework: templating: assets_base_urls: http: [ "http://static.site.com" ] How to serialize that ? { "username": "AdrienBrault", "avatar_url": "http://static.site.com/profile/xazb.jpg" } 27
  51. class User { // ... private $avatarUrl; // ... }

    What you could do ... 28
  52. class User { // ... private $avatarUrl; // ... }

    public function getUserAction(User $user) { $package = $this->get('templating.helper.assets')->getPackage(); $user->setAvatarUrl($package->getUrl($user->getImagePath())); $this->get('serializer')->serialize($user, 'json'); // ... } What you could do ... 28
  53. class User { // ... private $avatarUrl; // ... }

    public function getUserAction(User $user) { $package = $this->get('templating.helper.assets')->getPackage(); $user->setAvatarUrl($package->getUrl($user->getImagePath())); $this->get('serializer')->serialize($user, 'json'); // ... } public function getUserListAction() { foreach () { // ... well ... doable } } What you could do ... 28
  54. class User { // ... private $avatarUrl; // ... }

    public function getUserAction(User $user) { $package = $this->get('templating.helper.assets')->getPackage(); $user->setAvatarUrl($package->getUrl($user->getImagePath())); $this->get('serializer')->serialize($user, 'json'); // ... } public function getUserListAction() { foreach () { // ... well ... doable } } public function getPostsWithAuthorsAction() { // Whhaaattttt } What you could do ... 28
  55. What you should do • Use an event subscriber to

    add the data 29
  56. <?php use JMS\Serializer\Annotation as Serializer; class User { // previous

    properties /** * @Serializer\Exclude */ private $imagePath; // Getters & Setters } Writing an AvatarUrlSubscriber 30
  57. use JMS\Serializer\EventDispatcher\Events; use JMS\Serializer\EventDispatcher\EventSubscriberInterface; class AvatarUrlSubscriber implements EventSubscriberInterface { public

    static function getSubscribedEvents() { return [ [ 'event' => Events::POST_SERIALIZE, 'method' => 'onPostSerializeUserJson', 'class' => 'User', 'format' => 'json', ], [ 'event' => Events::POST_SERIALIZE, 'method' => 'onPostSerializeUserXml', 'class' => 'User', 'format' => 'xml', ], ]; } // ... } Writing an AvatarUrlSubscriber 31
  58. use JMS\Serializer\EventDispatcher\Event; use JMS\Serializer\EventDispatcher\EventSubscriberInterface; use Symfony\Component\Templating\Asset\PackageInterface; class AvatarUrlSubscriber implements EventSubscriberInterface

    { // ... private $package; public function __construct(PackageInterface $package) { $this->package = $package; } public function onPostSerializeUserJson(Event $event) { $event->getVisitor()->addData( 'avatar_url', $this->package->getUrl($event->getObject()->getImagePath()) ); } public function onPostSerializeUserXml(Event $event) { $avatarUrlNode = $event->getVisitor()->getDocument()->createElement( 'avatarUrl', $this->package->getUrl($event->getObject()->getImagePath()) ); $event->getVisitor()->getCurrentNode()->appendChild($avatarUrlNode); } } Writing an AvatarUrlSubscriber 32
  59. use JMS\Serializer\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Templating\Asset\UrlPackage; $serializer = JMS\Serializer\SerializerBuilder::create() ->configureListeners(function (EventDispatcherInterface $eventDispatcher)

    { $eventDispatcher->addSubscriber( new AvatarUrlSubscriber(new UrlPackage('http://static.site.com/')) ); }) ->build(); $user = new User(4, 'AdrienBrault'); $user->setImagePath('/profile/xazb.jpg'); Writing an AvatarUrlSubscriber 33
  60. use JMS\Serializer\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Templating\Asset\UrlPackage; $serializer = JMS\Serializer\SerializerBuilder::create() ->configureListeners(function (EventDispatcherInterface $eventDispatcher)

    { $eventDispatcher->addSubscriber( new AvatarUrlSubscriber(new UrlPackage('http://static.site.com/')) ); }) ->build(); $user = new User(4, 'AdrienBrault'); $user->setImagePath('/profile/xazb.jpg'); echo $serializer->serialize($user, 'xml'); Writing an AvatarUrlSubscriber <?xml version="1.0" encoding="UTF-8"?> <user identifier="4"> <username><![CDATA[AdrienBrault]]></username> <avatarUrl>http://static.site.com/profile/xazb.jpg</avatarUrl> </user> 33
  61. use JMS\Serializer\EventDispatcher\EventDispatcherInterface; use Symfony\Component\Templating\Asset\UrlPackage; $serializer = JMS\Serializer\SerializerBuilder::create() ->configureListeners(function (EventDispatcherInterface $eventDispatcher)

    { $eventDispatcher->addSubscriber( new AvatarUrlSubscriber(new UrlPackage('http://static.site.com/')) ); }) ->build(); $user = new User(4, 'AdrienBrault'); $user->setImagePath('/profile/xazb.jpg'); echo $serializer->serialize($user, 'json'); echo $serializer->serialize($user, 'xml'); Writing an AvatarUrlSubscriber <?xml version="1.0" encoding="UTF-8"?> <user identifier="4"> <username><![CDATA[AdrienBrault]]></username> <avatarUrl>http://static.site.com/profile/xazb.jpg</avatarUrl> </user> { "identifier": 4, "username": "AdrienBrault", "avatar_url": "http://static.site.com/profile/xazb.jpg" } 33
  62. FSCHateoasBundle • EventSubscriber to add <link/> s • EventSubscriber to

    embed data from services (forms, pagerfanta, etc) • Handler for Pagerfanta • Handler for Form instances https://github.com/TheFootballSocialClub/FSCHateoasBundle A bundle I’ve created using many of the serializer features. Have a look at the code to learn more :) 34
  63. Questions ? 35 Thanks ! https://speakerdeck.com/adrienbrault/going-further-with-the-jmsserializer