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

Going further with the JMSSerializer

Going further with the JMSSerializer

Avatar for Adrien Brault

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