jms/serializer
2017
Asmir Mustafic
PHP User Group Berlin - November 2017
Me
Asmir Mustafic
- Twitter: @goetas_asmir
- Github: @goetas
- LinkedIn: @goetas
- WWW: goetas.com
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!
jms/serializer 2017
By Asmir Mustafic
jms/serializer 2017
- 2,464