jms/serializer
2.0

Asmir Mustafic

Symfony User Group - Berlin - October 2018

You?

Me

Asmir Mustafic

Me

@goetas

Me

Software Developer

Berlin

Me

index.php ~2001

index.html ~1999

My first

Open source

  • jms/serializer (contributor/maintainer)
  • masterminds/html5 (contributor/maintainer)
  • hautelook/templated-uri-bundle (contributor/maintainer)
  • goetas-webservices/xsd2php (author)
  • goetas-webservices/xsd-reader (author)
  • goetas-webservices/soap-client (author)
  • goetas/twital (author)
  • doctrine/orm (occasional contributor)
  • nelmio/api-doc (occasional contributor)
  • many others...

Serializer?

jms/serializer?

class User
{
    private $name;
    private $email;

    public function __construct($name, $email)
    {
        $this->name = $name;
        $this->email = $email;
    }
}
$user = new User(
  'Elvis Presley', 
  'elvis@rocknroll.com'
); 
// or get it from DB

echo $serializer->serialize($user);
{
    "name": "Elvis Presley",
    "email": "elvis@rocknroll.com"
}

json_encode

json_decode

JsonSerializable
class User implements JsonSerializable
{
    // ...

    public function jsonSerialize()
    {
        return [
          "name" => $this->name,
          "email" => $this->email,
        ];
    }
}

echo json_encode($user);
{
    "name": "Elvis Presley",
    "email": "elvis@rocknroll.com"
}

Show the email
only to
logged-in users?

class User implements JsonSerializable
{
    // ...

    public function jsonSerialize()
    {
        if (Auth::getInstance()->isLoggedIn()) {
            return [
              "name" => $this->name,
              "email" => $this->email,
            ];
        } else {
           return [
              "name" => $this->name,
           ];
        }
    }
}

PHP

Serialize: 

  • JSON
  • XML

Deserialize:

  • JSON
  • XML

jms/serializer

opensource

First release 2011

Features?

Metadata:

  • XML
  • YAML
  • Annotations
  • Custom drivers

Serialize into:

  • XML
  • JSON

Deserialize from:

  • XML
  • JSON

Exclusion strategies:

  • per-class
  • groups
  • nested groups
  • max-depth
  • expression-language
  • versioning
  • custom

Customization:

  • custom events
  • custom handlers

Access strategies:

  • virtual-properties
  • reflection
  • getter/setter
  • closure bind
  • expression-language

More:

  • XML Namespaces
  • Custom access order
  • Read-Only properties
  • Inheritance Support
  • Doctrine Support

And More:

  • Fast
  • Battle tested

Naming:

  • multiple built-in strategies
  • custom strategies

Features

https://slides.com/goetas/jms-serializer-2017

jms/serializer
2.0

PHP ^5.5 || ^7.0

Apache-2 License

Serialize: 

  • JSON
  • XML
  • YAML

Deserialize:

  • JSON
  • XML

jms/serializer
1.x

opensource

PHP ^5.5 || ^7.0 ^5.5  ^7.2

Apache-2 License MIT

Serialize: 

  • JSON
  • XML
  • YAML

Deserialize:

  • JSON
  • XML

jms/serializer
2.0

opensource

jms/serializer

Why upgrade?

~20-50% faster

Less

Accumulated in 7 years of history

bugs
workarounds edge-cases

How to upgrade?

Backward compatible

Almost...

Same
metadata
format

YAML/XML/Annotations

Same
listeners

format


Same
handlers

format

Almost

~20-50% faster

Lets go back to...

Benchmarks

Benchmark your application

Developer productivity

 

Not just serialization performance!

Benchmarks

egeloen/ivory-serializer-benchmark

Vertical Complexity
 

Horizontal Complexity

Vertical Complexity

Horizontal Complexity

Vertical and Horizontal

Vertical and Horizontal x2

The initialization
takes more time

than the serialization

With small datasets

v2.0 Changes

109 issues closed
246 commits
533 files changed

26 months

MIT

license

211 contributor
approvals

Technical Improvements

Huge Internal refactoring

no impact on user API

Simpler context

$context = SerializationContext::create();


// v1.x
$context->attributes->set("foo", "bar"); 

$holder = $context->attributes->get("foo"); 
if ($holder instanceof Some) {
     // get a value
    $value = $holder->get();
}

Simpler context

$context = SerializationContext::create();


// v2.0
$context->setAttribute("foo", "bar"); 

if ($context->hasAttribute("foo")) {
    // get a value
    $value = $context->getAttribute("foo");
} 

Max Depth

class User
{
    private $name;

    /** 
     * @MaxDepth(1)
     * @var Collection|Song[]
     */
    private $songs;
}
{
    "name": "Elvis Presley",
    "songs": {
      {},
      {}
    }
}

Max Depth v 1.x

Max Depth v 1.x

class User
{
    private $name;

    /** 
     * @MaxDepth(2)
     * @var Collection|Song[]
     */
    private $songs;
}
{
    "name": "Elvis Presley",
    "songs": {
      {
        "id: 9
        "title: "Jailhouse Rock"
      },
      {
        "id: 10
        "title: "Love me tender"
      }
    }
}

Max Depth

Max Depth v 2.0

class User
{
    private $name;

    /** 
     * @MaxDepth(1)
     * @var Collection|Song[]
     */
    private $songs;
}
{
    "name": "Elvis Presley",
    "songs": {
      {
        "id: 9
        "title: "Jailhouse Rock"
      },
      {
        "id: 10
        "title: "Love me tender"
      }
    }
}

Max Depth

Type Handlers

function ($visitor, $data)
{
    // do something
    return $result;
}

It's just a callback!

function ($visitor, $data, $type, $context)
{
    // do something
    return $result;
}

Type Handlers v 1.x

function (
    JsonSerializationVisitor $visitor, 
    User $user
)
{
    $data = [
        'username' => $user->getUsername()
    ];

    // extra root check
    if ($visitor->getRoot() === null) {
        $visitor->setRoot($data);
    }
    return $data;
}

Type Handlers v 2.0

function (
    JsonSerializationVisitor $visitor, 
    User $user
)
{
    return [
        'username' => $user->getUsername()
    ];
}

Recursive Calls

class CustomUserHandler
{
    function __construct(Serializer $serializer)
    {
        $this->serializer = $serializer;
    }

    function serializeUserToJson(
        JsonSerializationVisitor $visitor, 
        User $user
    )
    {
        return [
           'raw' => $this->serializer->serialize($user, 'json')
        ];
    }
}

Extensibility

Most of the classes now are declared

final

use composition
 

inheritance is evil

https://ocramius.github.io/blog/when-to-declare-classes-final/

Error Handling

JMS\Serializer\Exception\InvalidMetadataException

All metadata errors throw a single exception type

Symfony Support?

JMSSerializerBundle

JMSSerializerBundle
v3.0

jms_serializer:
    visitors:
        json_serialization:
            options: 0 # json_encode options bitmask
            depth: 512
        json_deserialization:
            options: 0 # json_dencode options bitmask
jms_serializer:
    visitors:
        xml_serialization:
            format_output: false
            version: "1.0"
            encoding: "UTF-8"
            default_root_name: "result"
            default_root_ns: null

        xml_deserialization:
            external_entities: false
            doctype_whitelist:
                - '<!DOCTYPE authorized ...'

Now is your turn!

{
    "require" : {
        "jms/serializer" : "^2.0"
    },

    "minimum-stability": "RC"
}
{
    "require" : {
        "jms/serializer-bundle" : "^3.0"
    },


    "minimum-stability": "RC"
}

Thank you!

Please leave a feedback

Twitter: @goetas_asmir

Comment on: sfugberlin