jms/serializer
2017

Asmir Mustafic

PHP User Group Berlin - November 2017

Me

Asmir Mustafic

Bosnia, Italy, Germany

Software Developer

Open source

  • jms/serializer  (maintainer)
  • html5-php ( maintainer)
  • xsd2php (author)
  • twital (author)
  • and many other...

contributed/contributing to:

Serialization

Serialization is the process of translating data structures or object state
 

into a format that can be stored or transmitted

and reconstructed later in the same or another computer environment

Wikipedia

Serialize

class User
{
    protected $firstName;
    protected $lastName;

    public function __construct($firstName, $lastName)
    {
        $this->firstName = $firstName;
        $this->lastName = $lastName;
    }
}
$user = new User('Elvis', 'Presley'); // or find it somehow...
echo $serializer->serialize($user);
{
    "first_name": "Elvis",
    "last_name": "Presley"
}

serialization result

Deserialize

{
    "first_name": "Elvis",
    "last_name": "Presley"
}

Deserialize


$user = $deserializer->deserialize($serializedRepresentation, User::class);

// $user is instance of User class and contains valid data 

Why bother?

json_encode
json_decode

jms/serializer

PHP ^5.5 || ^7.0

Apache-2 License

Serialize: 

  • JSON
  • XML
  • YML

Deserialize:

  • JSON
  • XML

jms/serializer

opensource

  • Started in 2011 by Johannes Schmitt

    • JMSSerializerBundle (later splitted into two repo)

  • ~120 contributors

  • ~16M downloads on packagist (November 2017)

    • Not including the bundle and related sub-libraries

  • v1.9.1 currently

History

Many Changes

  • 1189 commits
  • 119 contributors
  • 28 releases
  • 111 issues
  • 17 pull requests
  • 655 stars
  • 461 forks
  • 41 watchers
  • 749 commits
  • 85 contributors
  • 8 releases
  • 200 issues
  • 119 pull requests
  • 441 stars
  • 364 forks
  • 35 watchers

April 4th 2016

 November 1st 2017

how to serialize/deserialize
with


jms/serializer?

Serialize/Deserialize

// create the serializer
$serializer = SerializerBuilder::create()->build();


// our example object
$user = new User('Elvis', 'Presley'); // or find it somehow...


// serialize to json
$json = $serializer->serialize($user, 'json'); // serialization


// deserialize from json into a User object
$user = $serializer->deserialize($json, User::class, 'json'); // deserialization

What if... 

Things are getting more complicated...

But we still want...

{
    "_id": "5919adb4720fc632f4e9f0dc",
    "index": 0,
    "guid": "c9c9a1ef-8a78-4acc-95ae-617a6d03db1d",
    "isActive": true,
    "balance": "$3,623.58",
    "picture": "http://placehold.it/32x32",
    "age": 23,
    "eyeColor": "blue",
    "name": {
        "first": "Malinda",
        "last": "Conley"
    },
    "email": "malinda.conley@flum.us",
    "address": "685 Gerry Street, Jenkinsville, Massachusetts, 3466",
    "registered": "Sunday, May 11, 2014 1:39 AM",
    "latitude": 29.082485,
    "longitude": 77.914309,
    "tags": ["dolor", "dolor", "magna",  "ipsum", "enim"],
    "range": [1,1,5,6,7,9,1,654,965,45,7],
    "friends": [
        { "id": 0, "name": "Kristi Swanson" },
        { "id": 1, "name": "Alisa Blair" },
        { "id": 2, "name": "Kelly Daugherty" }
    ],
    "greeting": "Hello, Malinda! You have 7 unread messages.",
    "favoriteFruit": "banana"
}

We need options, configurations, customizations...

But we do not want to get crazy...

jms/serializer

to resque...

Naming Strategies

Snake Case

class User
{
    protected $first_name;
    protected $lastName;
}
{
    "first_name": "Elvis",
    "last_name": "Presley"
}

(default)

Camel Case

class User
{
    protected $first_name;
    protected $lastName;
}
{
    "firstName": "Elvis",
    "lastName": "Presley"
}

Itentical

class User
{
    protected $first_name;
    protected $lastName;
}
{
    "first_name": "Elvis",
    "lastName": "Presley"
}

Configurable

class User
{
    protected $first_name;
    
    /**
     * @SerializedName("surname")
     */
    protected $lastName;
}
{
    "first_name": "Elvis",
    "surname": "Presley"
}

Custom

interface PropertyNamingStrategyInterface
{
    public function translateName(PropertyMetadata $property) : string;
}

Access Order

Definition

class User
{
    protected $first_name;
    protected $lastName;
    protected $email;
}
{
    "first_name": "Elvis",
    "last_name": "Presley",
    "email": "king@rocknroll.com"
}

(default)

Alphabetical

/**
 * @AccessorOrder("alphabetical")
 */
class User
{
    protected $first_name;
    protected $lastName;
    protected $email;
}
{
    "first_name": "Elvis",
    "email": "king@rocknroll.com",
    "last_name": "Presley"
}

Custom

/**
 * @AccessorOrder("custom", custom = {"email", "first_name", "lastName"})
 */
class User
{
    protected $first_name;
    protected $lastName;
    protected $email;
}
{
    "email": "king@rocknroll.com",
    "first_name": "Elvis",
    "last_name": "Presley"
}

Access Policy

Reflection

by default

Public Method

/** 
 * @AccessType("public_method") 
 */
class User
{
    private $name;

    public function getName()
    {
        return $this->name;
    }

    public function setName($name)
    {
        $this->name = trim($name);
    }
}

Custom Accessor 

class User
{
    private $id;

    /** 
     * @Accessor(getter="getTrimmedName",setter="setName")
     */
    private $name;

    // ...
    public function getTrimmedName()
    {
        return trim($this->name);
    }

    public function setName($name)
    {
        $this->name = $name;
    }

Read-only

class User
{
    private $id;

    /** 
     * @ReadOnly
     */
    private $name;
}
/** 
 * @ReadOnly
 */
class User
{
    private $id;

    private $name;
}

Exclusion Strategies

Exclude

class User
{
    protected $id;

    /**
     * @Exclude
     */
    protected $name;
}

Expose


/**
 * @ExclusionPolicy("ALL")
 */
class User
{
    protected $id;

    /**
     * @Expose
     */
    protected $name;
}

Groups

class User
{
    protected $id;
    /**
     * @Groups({"registered", "me"})
     */
    protected $name;
    /**
     * @Groups({"me"})
     */
    protected $password;
}

$context = new SerializationContext();
$context->setGroups(["Default", "registered"]);

$serializer->serialize($user, 'json', $context);
{
    "id": 9874,
    "name": "Elvis Presley"
}

Property Groups

class User
{
    protected $name;

    /**
     * @Groups({"private"})
     */
    protected $email;
    
    /**
     * @var User
     */
    protected $bestFriend;
}

$context = new SerializationContext();
$context->setGroups([
  "Default",
  "bestFriend" =>  ["Default", "private"]
]);

$serializer->serialize($user, 'json', $context);
{
    "name": "Elvis Presley",
    "best_friend": {
        "name": "Sonny West",
        "email": "sonny@west.com"
    }
}


# note "best_friend" circular ref

new in v1.4

Version

class User
{
    /**
     * @Until("1.0.x")
     */
    private $name;

    /**
     * @Since("1.1")
     * @SerializedName("name")
     */
    private $name2;
}

$context = new SerializationContext();
$context->setVersion("1.5.0");

$serializer->serialize($user, 'json', $context);
{
    "name": "Elvis Presley" 
}

    // name is taken from field $name2

Expression Language

class User
{
    /**
     * @Expose(if="service('firends_manager').isAllowed(object)")
     */
    private $email;
}

$serializer->serialize($user, 'json');

new in v1.5

Max Depth

class User
{
    private $name;
    /** 
     * @MaxDepth(1)
     * @var User[]
     */
    private $friends;

    /** 
     * @MaxDepth(2)
     * @var Post[]
     */
    private $likedPosts;
}

class Post
{
    private $title;
    /** 
     * @var User[]
     */
    private $author;
}
{
    "name": "Elvis Presley",
    "friends": [
      {"name": "Sonny West"}
    ],
    "liked_posts": [
      {
       "title": "Elvis will disappear", 
       "author": {
         "name": "Allan Weiss"
       }
      }
    ],
}
$context = new SerializationContext();
$context->enableMaxDepthChecks();

$ser->serialize($user, 'json', $context);

Virtual Properties

Get methods

class User
{
    protected $firstName;
    protected $lastName;

    /**
     * @VirtualProperty()
     */
    public function getFullName()
    {
        return $this->firstName . ' '. $this->lastName;
    }
}
{
    "first_name": "Elvis",
    "last_name": "Presley",
    "full_name": "Elvis Presley"
}

Expression Language


/**
 * @VirtualProperty("fans", 
 *   exp="service('fan_counter').count(object)"
 * )
 */
class User
{
    protected $firstName;
    protected $lastName;
}
{
    "first_name": "Elvis",
    "last_name": "Presley",
    "fans": 9774565
}

new in v1.6

Types

Basics

class User
{
    protected $id;

    /**
     * @Type("string")
     */
    protected $name;
}

Casting

use Ramsey\Uuid\UuidInterface;

class User
{
    /**
     * @Type("string")
     * @var UuidInterface
     */
    protected $id;
}

Obejcts

class User
{
    /**
     * @Type("xxx\namespace\xxx\User")
     * @var User
     */
    protected $friend;
}

Arrays

class User
{
    /**
     * @Type("array")
     * @var User[]
     */
    protected $friends = array();
}

Arrays

class User
{
    protected $name;

    /**
     * @Type("array<xxx\namespace\xxx\User>")
     * @var User[]
     */
    protected $friends = array();
}
{
    "name": "Elvis Presley",
    "friends": [
       {"name":"Sonny West", ...},
       {"name":"Sonny West", ...},
       ...
    ]
}

new in v1.7

Maps/Hashes

class User
{
    protected $name;

    /**
     * @Type("array<string,xxx\namespace\xxx\User>")
     * @var User[]
     */
    protected $friends = array();
}
{
    "name": "Elvis Presley",
    "friends": {
       "friend1": {"name":"Sonny West", ...},
       "friend2": {"name":"Sonny West", ...},
       ...
    }
}

new in v1.7

Date

class User
{
    /**
     * @Type("DateTime<'d-m-Y'>")
     * @var \DateTime
     */
    protected $updated;

    /**
     * @Type("DateTimeImmutable<'d-m-Y', 'UTC'>")
     * @var \DateTimeImmutable
     */
    protected $created;
}

Doctrine Collections

use  Doctrine\Common\Collections\Collection;

class User
{
    /**
     * @Type("ArrayCollection<string,xxx\namespace\xxx\User>")
     * @var Collection
     */
    protected $friends;
}

Virtual Types (1/1)

class User
{
    /**
     * @Type("MyType")
     * @var ???
     */
    protected $image;
}

$image can be any type you want

 

(resources too)

Virtual Types (2/2)

(handler)

class MyHandler implements SubscribingHandlerInterface
{
    public static function getSubscribingMethods()
    {
        return [
            [
                'direction' => GraphNavigator::DIRECTION_SERIALIZATION,
                'format' => 'json',
                'type' => 'MyType',
                'method' => 'imageToJson',
            ],
        ];
    }

    public function imageToJson(JsonSerializationVisitor $v, $img, array $type, Context $ctx)
    {
        return base64_encode(something_cool($image));
    }
}

Events

Pre/Post
Serialize/Deserialize

Listeners

class MyEventSubscriber implements EventSubscriberInterface
{
    public static function getSubscribedEvents()
    {
        return [
            ['event' => 'serializer.pre_serialize', 
                'method' => 'preSerialize'],
            ['event' => 'serializer.post_deserialize', 
                'method' => 'postDeserialize', 'format' => 'json'],
            ['event' => 'serializer.post_deserialize', 
                'method' => 'postDeserialize', 'class' => 'Foo')]
        ];
    }

    public function preSerialize(JMS\Serializer\EventDispatcher\PreSerializeEvent $event)
    {
        // do something
    }

    public function postDeserialize(JMS\Serializer\EventDispatcher\PreSerializeEvent $event)
    {
        // do something else
    }
}

Metadata

  • Annotations

  • XML

  • YAML

Formats

# User.yml

User:
    properties:
        name:
            exclude: true
        email:
            type: string
 
# User.xml

<serializer>
    <class name="User">
        <property name="name" exclude="true" />
        <property name="email" type="string" />
    </class>
</serializer>

Examples

Metadata are per-class!

class BaseUser
{
    protected $name;
}

class AdvancedUser extends BaseUser
{
    protected $email;
}
# AdvancedUser.yml

AdvancedUser:
    properties:
        name:
            exclude: true
        email:
            type: string
 
class BaseUser
{
    protected $name;
}

class AdvancedUser extends BaseUser
{
    protected $email;
}
# BaseUser.yml

BaseUser:
    properties:
        name:
            exclude: true
 
# AdvancedUser.yml

AdvancedUser:
    properties:
        email:
            type: string
 

Serialization
Deserialization
XML

(and YAML)

To much staff
for a single talk

Try it your self!

Thank you!

Made with Slides.com