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

Going further with the JMSSerializer

Going further with the JMSSerializer

Adrien Brault

April 05, 2013
Tweet

More Decks by Adrien Brault

Other Decks in Programming

Transcript

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

    • Got this awesome badge • @AdrienBrault everywhere 2
  2. 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
  3. 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
  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(); { "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
  5. 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
  6. $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
  7. $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
  8. 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
  9. DisjunctExclusionStrategy multiple exclusion strategies at the same time Before 0.12

    $serializer->setExclusionStrategy( new GroupsExclusionStrategy(['list', 'detail']) ); $serializer->serialize($data, 'json'); 12
  10. 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
  11. 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
  12. 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
  13. 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
  14. 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
  15. 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
  16. 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
  17. 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
  18. 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
  19. 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
  20. 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
  21. 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
  22. 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
  23. Handlers “Handlers allow you to change the serialization, or deserialization

    process for a single type/format combination.” 19
  24. $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
  25. { "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
  26. { "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
  27. { "page": 1, "pages": 2, "total": 3, "results": [ {

    "identifier": 1, "username": "AdrienBrault" }, { "identifier": 20, "username": "John" } ] } This is what we’d like to get 21
  28. <?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
  29. 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
  30. 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
  31. $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
  32. $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
  33. 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
  34. Avatar urls use case in a website Let’s say our

    users have awesome avatars, stored on an awesome CDN. 27
  35. 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
  36. 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
  37. 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
  38. 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
  39. 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
  40. 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
  41. <?php use JMS\Serializer\Annotation as Serializer; class User { // previous

    properties /** * @Serializer\Exclude */ private $imagePath; // Getters & Setters } Writing an AvatarUrlSubscriber 30
  42. 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
  43. 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
  44. 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
  45. 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
  46. 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
  47. 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