Slide 1

Slide 1 text

@adrienbrault Going further with the JMSSerializer 1

Slide 2

Slide 2 text

About me • Adrien Brault • Student at SUPINFO Paris • Got this awesome badge • @AdrienBrault everywhere 2

Slide 3

Slide 3 text

JMSSerializer “Easily serialize, and deserialize data of any complexity (supports XML, JSON, YAML)” 3

Slide 4

Slide 4 text

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

Slide 5

Slide 5 text

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(); 1 echo $serializer->serialize($user, 'xml'); 4

Slide 6

Slide 6 text

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" } 1 echo $serializer->serialize($user, 'xml'); echo $serializer->serialize($user, 'json'); 4

Slide 7

Slide 7 text

EASILY 5

Slide 8

Slide 8 text

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

Slide 9

Slide 9 text

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

Slide 10

Slide 10 text

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

Slide 11

Slide 11 text

$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 { "identifier": 4, "username": "AdrienBrault" } 7

Slide 12

Slide 12 text

PRETTY COOL 8

Slide 13

Slide 13 text

So, What’s new ? 9

Slide 14

Slide 14 text

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

Slide 15

Slide 15 text

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

Slide 16

Slide 16 text

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

Slide 17

Slide 17 text

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

Slide 18

Slide 18 text

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

Slide 19

Slide 19 text

EventDispatcher Since 0.10 We’ll see it in detail later 13

Slide 20

Slide 20 text

Before going further, PRO TIP 14

Slide 21

Slide 21 text

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

Slide 22

Slide 22 text

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

Slide 23

Slide 23 text

Serializer internals UML class diagram 17

Slide 24

Slide 24 text

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

Slide 25

Slide 25 text

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

Slide 26

Slide 26 text

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

Slide 27

Slide 27 text

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

Slide 28

Slide 28 text

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

Slide 29

Slide 29 text

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

Slide 30

Slide 30 text

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

Slide 31

Slide 31 text

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

Slide 32

Slide 32 text

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

Slide 33

Slide 33 text

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

Slide 34

Slide 34 text

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

Slide 35

Slide 35 text

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

Slide 36

Slide 36 text

Handlers “Handlers allow you to change the serialization, or deserialization process for a single type/format combination.” 19

Slide 37

Slide 37 text

$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

Slide 38

Slide 38 text

{ "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

Slide 39

Slide 39 text

{ "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

Slide 40

Slide 40 text

{ "page": 1, "pages": 2, "total": 3, "results": [ { "identifier": 1, "username": "AdrienBrault" }, { "identifier": 20, "username": "John" } ] } This is what we’d like to get 21

Slide 41

Slide 41 text

GraphNavigator::DIRECTION_SERIALIZATION, 'format' => 'json', 'type' => 'Pagerfanta\Pagerfanta', 'method' => 'serializeToJson', ] ]; } // ... } Writing our own PagerfantaHandler 22

Slide 42

Slide 42 text

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

Slide 43

Slide 43 text

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

Slide 44

Slide 44 text

$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

Slide 45

Slide 45 text

$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

Slide 46

Slide 46 text

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

Slide 47

Slide 47 text

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

Slide 48

Slide 48 text

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

Slide 49

Slide 49 text

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

Slide 50

Slide 50 text

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" ] How to serialize that ? { "username": "AdrienBrault", "avatar_url": "http://static.site.com/profile/xazb.jpg" } 27

Slide 51

Slide 51 text

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

Slide 52

Slide 52 text

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

Slide 53

Slide 53 text

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

Slide 54

Slide 54 text

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

Slide 55

Slide 55 text

What you should do • Use an event subscriber to add the data 29

Slide 56

Slide 56 text

Slide 57

Slide 57 text

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

Slide 58

Slide 58 text

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

Slide 59

Slide 59 text

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

Slide 60

Slide 60 text

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 http://static.site.com/profile/xazb.jpg 33

Slide 61

Slide 61 text

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 http://static.site.com/profile/xazb.jpg { "identifier": 4, "username": "AdrienBrault", "avatar_url": "http://static.site.com/profile/xazb.jpg" } 33

Slide 62

Slide 62 text

FSCHateoasBundle • EventSubscriber to add 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

Slide 63

Slide 63 text

Questions ? 35 Thanks ! https://speakerdeck.com/adrienbrault/going-further-with-the-jmsserializer