支付宝扫一扫付款
微信扫一扫付款
(微信为保护隐私,不显示你的昵称)
Serializer组件意味着用于把对象转换成一种特殊格式(XML, JSON, YAML, ...)或者反其道而行之。
要实现这个,Serializer组件遵循下列简易框图。
如你在图中所见,有一个数组充当着中间人。这样一来,Encoder(编码器)只负责将特定的format(格式)转换成array(数组),或者反过来。相同方式下,Normalizers将负责把特定的Object转换成array,或者反过来。
序列化是一个复杂的话题,虽然本组件未必能满足全部使用场合,但在开发用于“序列化和反序列化你的对象”之工具的过程中,仍然有其作用。
你可以通过下述两种方式安装:
通过composer安装(Packagist上的symfony/serializer
)
通过官方Git宝库(https://github.com/symfony/serializer)
然后,包容vendor/autoload.php
文件,以开启Composer提供的自动加载机制。否则,你的程序将无法找到这个Symfony组件的类。
要使用 ObjectNormalizer
,必须安装 PropertyAccess组件。
使用Serializer十分简单。你只需设置 Serializer
来指定要使用哪个encoders和normalizer:
1 2 3 4 5 6 7 8 9 | use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\XmlEncoder;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
$encoders = array(new XmlEncoder(), new JsonEncoder());
$normalizers = array(new ObjectNormalizer());
$serializer = new Serializer($normalizers, $encoders); |
首选的normalizer是 ObjectNormalizer
,但也可以使用其他的normalizers。以下所有例程用的都是ObjectNormalizer
。
本例假定在你的项目中已经存在下面这个类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | namespace Acme;
class Person
{
private $age;
private $name;
private $sportsman;
// Getters
public function getName()
{
return $this->name;
}
public function getAge()
{
return $this->age;
}
// Issers
public function isSportsman()
{
return $this->sportsman;
}
// Setters
public function setName($name)
{
$this->name = $name;
}
public function setAge($age)
{
$this->age = $age;
}
public function setSportsman($sportsman)
{
$this->sportsman = $sportsman;
}
} |
现在,如果你希望序列化这个对象为JSON,只需使用之前创建的Serializer服务:
1 2 3 4 5 6 7 8 9 10 | $person = new Acme\Person();
$person->setName('foo');
$person->setAge(99);
$person->setSportsman(false);
$jsonContent = $serializer->serialize($person, 'json');
// $jsonContent contains {"name":"foo","age":99,"sportsman":false}
echo $jsonContent; // or return it in a Response |
serialize()
方法的第一个参数是“将要被序列化的对象”,第二个参数用来选择合适的encoder,在本例中是 JsonEncoder
。
现在来了解如何反向提取。这回,Person
类的信息,将从XML格式中反解出来:
1 2 3 4 5 6 7 8 9 | $data = <<<EOF
<person>
<name>foo</name>
<age>99</age>
<sportsman>false</sportsman>
</person>
EOF;
$person = $serializer->deserialize($data, 'Acme\Person', 'xml'); |
在本例,deserialize()
方法需要三个参数:
将要被反解的信息
将要容纳反解出来的信息的类的名字
用于把信息转换成数组的encoder
serializer也可以用于更新一个既存对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | $person = new Acme\Person();
$person->setName('bar');
$person->setAge(99);
$person->setSportsman(true);
$data = <<<EOF
<person>
<name>foo</name>
<age>69</age>
</person>
EOF;
$serializer->deserialize($data, 'Acme\Person', 'xml', array('object_to_populate' => $person));
// $person = Acme\Person(name: 'foo', age: '69', sportsman: true) |
当使用ORM时这是一个常规需求。
有时,你需要对你的多个entity中不同的属性组进行序列化。Groups是一个绝佳的方式来实现此种需求。
假设你有以下原生PHP对象:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | namespace Acme;
class MyObj
{
public $foo;
private $bar;
public function getBar()
{
return $this->bar;
}
public function setBar($bar)
{
return $this->bar = $bar;
}
} |
序列化在定义时可以指定使用annotations, XML 或 YAML。被normalizer所用到的 ClassMetadataFactory
对“将要使用哪一种格式”必须很清楚。
按如下代码对 ClassMetadataFactory
进行初始化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | use Symfony\Component\Serializer\Mapping\Factory\ClassMetadataFactory;
// For annotations / 对于annotation
use Doctrine\Common\Annotations\AnnotationReader;
use Symfony\Component\Serializer\Mapping\Loader\AnnotationLoader;
// For XML / 对于XML格式
// use Symfony\Component\Serializer\Mapping\Loader\XmlFileLoader;
// For YAML / 对于YAML格式
// use Symfony\Component\Serializer\Mapping\Loader\YamlFileLoader;
$classMetadataFactory = new ClassMetadataFactory(new AnnotationLoader(new AnnotationReader()));
// For XML / 对于XML格式
// $classMetadataFactory = new ClassMetadataFactory(new XmlFileLoader('/path/to/your/definition.xml'));
// For YAML / 对于YAML格式
// $classMetadataFactory = new ClassMetadataFactory(new YamlFileLoader('/path/to/your/definition.yml'));
|
然后,创建你的群组定义:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | namespace Acme;
use Symfony\Component\Serializer\Annotation\Groups;
class MyObj
{
/**
* @Groups({"group1", "group2"})
*/
public $foo;
/**
* @Groups({"group3"})
*/
public function getBar() // is* methods are also supported
// is* 方法也受到支持
{
return $this->bar;
}
// ...
} |
1 2 3 4 5 6 | Acme\MyObj:
attributes:
foo:
groups: ['group1', 'group2']
bar:
groups: ['group3'] |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | <?xml version="1.0" ?>
<serializer xmlns="http://symfony.com/schema/dic/serializer-mapping"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://symfony.com/schema/dic/serializer-mapping
http://symfony.com/schema/dic/serializer-mapping/serializer-mapping-1.0.xsd"
>
<class name="Acme\MyObj">
<attribute name="foo">
<group>group1</group>
<group>group2</group>
</attribute>
<attribute name="bar">
<group>group3</group>
</attribute>
</class>
</serializer> |
现在你就可以“仅对你需要的群组”之属性进行序列化了:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
$obj = new MyObj();
$obj->foo = 'foo';
$obj->setBar('bar');
$normalizer = new ObjectNormalizer($classMetadataFactory);
$serializer = new Serializer(array($normalizer));
$data = $serializer->normalize($obj, null, array('groups' => array('group1')));
// $data = array('foo' => 'foo');
$obj2 = $serializer->denormalize(
array('foo' => 'foo', 'bar' => 'bar'),
'MyObj',
null,
array('groups' => array('group1', 'group3'))
);
// $obj2 = MyObj(foo: 'foo', bar: 'bar') |
使用属性群组(attribute groups)而不是setIgnoredAttributes()
方法,是经过深思熟虑的最佳实践。
作为一个选择,有一种方式可以从原始对象中忽略属性。要移除那些属性,使用normalizer定义中的setIgnoredAttributes()
方法:
1 2 3 4 5 6 7 8 9 10 11 | use Symfony\Component\Serializer\Serializer;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
$normalizer = new ObjectNormalizer();
$normalizer->setIgnoredAttributes(array('age'));
$encoder = new JsonEncoder();
$serializer = new Serializer(array($normalizer), array($encoder));
$serializer->serialize($person, 'json'); // Output: {"name":"foo","sportsman":false}
|
有时,经过序列化的属性,必须与PHP类中的属性或/getter/setter方法在命名上不一样。
Serializer组件提供了一个很好的方式,来翻译(translate)或映射(map)PHP属性名称,令其成为“序列化命名”:即,命名转换系统(The Name Converter System)。
给你一个对象:
1 2 3 4 5 | class Company
{
public $name;
public $address;
} |
在序列化之后的(内容)形式中,对所有属性必须被施以下面这种org_
前缀:
1 | {"org_name": "Acme Inc.", "org_address": "123 Main Street, Big City"} |
一个自定义的命名转换器可以解决这种问题:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | use Symfony\Component\Serializer\NameConverter\NameConverterInterface;
class OrgPrefixNameConverter implements NameConverterInterface
{
public function normalize($propertyName)
{
return 'org_'.$propertyName;
}
public function denormalize($propertyName)
{
// remove org_ prefix
return 'org_' === substr($propertyName, 0, 4) ? substr($propertyName, 4) : $propertyName;
}
} |
自定义normalizer的第二个参数,可用来传递“命名转换器”,它可以是任何继承了 AbstractNormalizer
的类,包括 GetSetMethodNormalizer
和 PropertyNormalizer
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | use Symfony\Component\Serializer\Encoder\JsonEncoder
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
use Symfony\Component\Serializer\Serializer;
$nameConverter = new OrgPrefixNameConverter();
$normalizer = new ObjectNormalizer(null, $nameConverter);
$serializer = new Serializer(array($normalizer), array(new JsonEncoder()));
$obj = new Company();
$obj->name = 'Acme Inc.';
$obj->address = '123 Main Street, Big City';
$json = $serializer->serialize($obj);
// {"org_name": "Acme Inc.", "org_address": "123 Main Street, Big City"}
$objCopy = $serializer->deserialize($json);
// Same data as $obj |
在很多格式中,使用下划线来分隔单词是再普通不过的(也被称作snake_case)。但是,PSR-1指定了首选的PHP属性和方法之风格,却是CamelCase(驼峰)。
Symfony提供了内置的命名转换器,被设计为在序列化和反序列化进程中对snake_case和CamelCase两种风格进行转换:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | use Symfony\Component\Serializer\NameConverter\CamelCaseToSnakeCaseNameConverter;
use Symfony\Component\Serializer\Normalizer\ObjectNormalizer;
$normalizer = new ObjectNormalizer(null, new CamelCaseToSnakeCaseNameConverter());
class Person
{
private $firstName;
public function __construct($firstName)
{
$this->firstName = $firstName;
}
public function getFirstName()
{
return $this->firstName;
}
}
$kevin = new Person('Kévin');
$normalizer->normalize($kevin);
// ['first_name' => 'Kévin'];
$anne = $normalizer->denormalize(array('first_name' => 'Anne'), 'Person');
// Person object with firstName: 'Anne' |
如果你使用了isser方法 (就是以 is
作为前缀的方法,比如 Acme\Person::isSportsman()
),这时Serializer 组件将自动侦测并使用它来对相关属性进行序列化。
ObjectNormalizer
还会自动处理那些由 has
, add
和 remove
开头的方法。
在序列化时,你可以设置一个回调,来对特定对象的属性进行格式化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | use Acme\Person;
use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Serializer;
$encoder = new JsonEncoder();
$normalizer = new GetSetMethodNormalizer();
$callback = function ($dateTime) {
return $dateTime instanceof \DateTime
? $dateTime->format(\DateTime::ISO8601)
: '';
};
$normalizer->setCallbacks(array('createdAt' => $callback));
$serializer = new Serializer(array($normalizer), array($encoder));
$person = new Person();
$person->setName('cordoval');
$person->setAge(34);
$person->setCreatedAt(new \DateTime('now'));
$serializer->serialize($person, 'json');
// Output: {"name":"cordoval", "age": 34, "createdAt": "2014-03-22T09:43:12-0500"} |
下面是一些可用的normalizer类型:
ObjectNormalizer
这个normalizer利用了 PropertyAccess组件 来对对象进行读写。意味着它可以直接地或通过getters, setters, hassers, adders 以及 removers来访问到属性。它支持在denormalization进程中调用构造器。
对象经normalize而成为一个“属性名 - 属性值”的映射关系(方法名则被去除了"get"/"set"/"has"/"remove"前缀并被转换为小写)。
ObjectNormalizer
是最为强大的normalizer。当使用Symfony标准版并且开启了serializer时,它被默认配置好了。
GetSetMethodNormalizer
这个normalizer通过"getters"(以"get"开始的公有方法)来读取类的内容。它可以通过调用constructor和"setters" ("set"开始的公有方法)来对数据denormalize。
对象经normalize而成为一个“属性名 - 属性值”的映射关系(方法名则被去除了"get"/"set"/"has"/"remove"前缀并被转换为小写)。
PropertyNormalizer
这个normalizer可以直接对public属性和 private 以及 protected 属性进行读写。它支持在denormalization进程中调用构造器。
对象经normalize而成为一个“属性名 - 属性值”的映射关系。
JsonSerializableNormalizer
这个normalizer与实现了 JsonSerializable
的类一起工作。
它将调用 JsonSerializable::jsonSerialize()
方法,然后进一步对结果进行normalize。这意味着嵌套的 JsonSerializable
类同样会被normalize。
当你想要从一个使用了 json_encode
的既存代码库逐步迁移到 Symfony Serializer时,这个normalizer极为有用,它可令你把“哪个normalizer用于哪个类”给混合起来。
并不同于能够被解决的 json_encode
循环引用。
DateTimeNormalizer
DateTimeInterface
对象 (如 DateTime
和 DateTimeImmutable
) 转换成字符串。它默认使用 RFC3339 格式。DataUriNormalizer
SplFileInfo
对象转换成data URI字符串 (data:...
),这样该文件就可以被嵌入到序列化的数据中。3.1
JsonSerializableNormalizer
, DateTimeNormalizer
和 DataUriNormalizer
从Symfony 3.1开始被引入。
当处理entity之间的关系时,circular references(循环引用)十分常见:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 | class Organization
{
private $name;
private $members;
public function setName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
public function setMembers(array $members)
{
$this->members = $members;
}
public function getMembers()
{
return $this->members;
}
}
class Member
{
private $name;
private $organization;
public function setName($name)
{
$this->name = $name;
}
public function getName()
{
return $this->name;
}
public function setOrganization(Organization $organization)
{
$this->organization = $organization;
}
public function getOrganization()
{
return $this->organization;
}
} |
为了避免无限循环, GetSetMethodNormalizer
会在遇到下述情况时抛出一个 CircularReferenceException
异常:
1 2 3 4 5 6 7 8 9 10 11 |
这个normalizer 的setCircularReferenceLimit()
方法,设置的是“在认定一个循环引用之前,它要对同一对象进行序列化的时间”之数字,默认值是1
。
不同于抛出异常,循环引用还可以通过自定义回调(custom callables)来控制。当对“拥有unique id”的entity序列化时格外有用:
1 2 3 4 5 6 7 8 9 10 11 | $encoder = new JsonEncoder();
$normalizer = new ObjectNormalizer();
$normalizer->setCircularReferenceHandler(function ($object) {
return $object->getName();
});
$serializer = new Serializer(array($normalizer), array($encoder));
var_dump($serializer->serialize($org, 'json'));
// {"name":"Les-Tilleuls.coop","members":[{"name":"K\u00e9vin", organization: "Les-Tilleuls.coop"}]}
|
Serializer组件有能力对“对象数组”进行操作。序列化数组时,类似于对单一对象序列化:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | use Acme\Person;
$person1 = new Person();
$person1->setName('foo');
$person1->setAge(99);
$person1->setSportsman(false);
$person2 = new Person();
$person2->setName('bar');
$person2->setAge(33);
$person2->setSportsman(true);
$persons = array($person1, $person2);
$data = $serializer->serialize($persons, 'json');
// $data contains [{"name":"foo","age":99,"sportsman":false},{"name":"bar","age":33,"sportsman":true}]
|
如果你希望deserialize这样一个结构,你应该添加 ArrayDenormalizer
到normalizers的数组中。通过对
deserialize()
方法的type参数附着 []
,你就已经做出了“预期是一个数组,而不是单一对象”的暗示。
1 2 3 4 5 6 7 8 9 10 11 12 | use Symfony\Component\Serializer\Encoder\JsonEncoder;
use Symfony\Component\Serializer\Normalizer\ArrayDenormalizer;
use Symfony\Component\Serializer\Normalizer\GetSetMethodNormalizer;
use Symfony\Component\Serializer\Serializer;
$serializer = new Serializer(
array(new GetSetMethodNormalizer(), new ArrayDenormalizer()),
array(new JsonEncoder())
);
$data = ...; // The serialized data from the previous example
$persons = $serializer->deserialize($data, 'Acme\Person[]', 'json'); |
Symfony Serializer组件的一个“受到欢迎”的替代之选,是第三方类库,JMS serializer(基于Apache license发布,不兼容GPLv2项目)。
本文,包括例程代码在内,采用的是 Creative Commons BY-SA 3.0 创作共用授权。