stopsopa / jms-serializer-lite
一个简单的库,用于将对象/数组从数据库中导出/序列化,以便通过RESTful API公开 - 单向操作
Requires
- php: ^5.3 || ^7.0
Requires (Dev)
- satooshi/php-coveralls: v0.7.1
README
已弃用
创建于2016年 - 现在相当老了,并且不再维护。
当时这实际上非常有用。但现在请不要使用它。
为什么?
通常,Symfony 2/3中用于将数据库中的数据导出以提供任何RESTful数据流的库的首选是jms/serializer。此工具旨在将数据序列化和反序列化为xml、json或yml,并返回初始数据结构。但通常只需要将数据提供到单一方向 - 到json数据流。此外,通常需要以不同的方式序列化相同的对象。在jms/serializer和类似的复杂工具中,通常可以使用“分组”,但不幸的是,这个解决方案不足以处理现实生活中的情况。
那么这个库特别做什么呢?
这个库允许您将任何嵌套的数据结构(通常是ORM对象)序列化为任何数组结构,以最简单的方式json_encode,而不会失去灵活性,也不会失去继承,从而通过更改(覆盖)旧格式来提供新的序列化格式。这个库也是框架无关的。
README
安装
composer require stopsopa/jms-serializer-lite
文档
当您有像...的ORM实体时
Article:
id
title
content
comments # <one-to-many with Comment entity>
Comment
id
article # <many-to-one with Article entity>
user # <many-to-one with User entity>
content
User
id
login
name
surname
comments # <one-to-many with Comment entity>
...并且需要将Article序列化为RESTful数据流
{ "id": 1, "name": "First article", "body": "Content of first article" }
使用此库开始此操作的最简单方法是创建一个简单的类(例如)NewDumper,它扩展了类 Stopsopa\LiteSerializer\Dumper ...
<?php namespace MyProject; use Stopsopa\LiteSerializer\Dumper; class NewDumper extends Dumper { }
...然后使用它获取一个可以json_encode的数组...
$article = $man->find(...); $array = NewDumper::getInstance()->dump($article); echo json_encode($array, JSON_PRETTY_PRINT);
执行此操作后,您将遇到错误...
...这意味着您需要实现方法 dumpMyProject_Article 来“解释”新类如何将实体转换为平面数组...
namespace MyProject; class NewDumper extends Dumper { public function dumpMyProject_Article($entity) { return array( 'id' => $entity->getId(), 'name' => $entity->getTitle(), 'body' => $entity->getContent() ); } }
...现在再次运行此代码,您将得到您需要的结果。
嵌套实体
要序列化具有所有其评论的Article...
{ "id": 1, "name": "First article", "body": "Content of first article", "comments": [ { "id": 2, "body": "Content of comment 1" }, { "id": 1, "body": "Content of comment 2" } ] }
...只需将方法 dumpMyProject_Article 更改为...
namespace MyProject; class NewDumper extends Dumper { public function dumpMyProject_Article($entity) { $data = array( 'id' => $entity->getId(), 'name' => $entity->getTitle(), 'body' => $entity->getContent(), ); $data['comments'] = $this->innerDump($entity->getComments()); return $data; } }
...现在当您尝试执行它时,您将看到 NewDump 需要方法 dumpMyProject_Comment ...
namespace MyProject; class NewDumper extends Dumper { ... public function dumpMyProject_Comment($entity) { return array( 'id' => $entity->getId(), 'body' => $entity->getContent() ); } }
...就是这样。
从不同角度序列化
值得一提的是,上面准备的类已经准备好序列化用于不同用例的Article和Comment类...
$dumper = NewDumper::getInstance(); # to serialize single Article entity $array = $dumper->dump($article); # result: {"id":1, ... , "comments":[...]} # to serialize array/collection of Article entities $array = $dumper->dump(array($article1, $article2, ...)); # result: [ {"id":1, ... , "comments":[...] }, {"id":2, ... , "comments":[...] } ] # to serialize single Comment entity $array = $dumper->dump($comment); # result: {"id":1, ...} # to serialize array/collection of Comment entities $array = $dumper->dump(array($comment1, $comment2, ...)); # result: [ {"id":1, ... }, {"id":2, ... } ]
更短的语法和助手
所有上述逻辑都可以使用helper 'toArray'更简洁地实现
class NewDumper extends Dumper { public function dumpMyProject_Article($entity) { return $this->toArray($entity, array( 'id' => 'id', 'name' => 'title', 'body' => 'content', 'comments' => 'comments' )); } public function dumpMyProject_Comment($entity) { return $this->toArray($entity, array( 'id' => 'id', 'body' => 'content' )); } }
...让我们暂停一下这段代码,并尝试理解这里发生了什么。
因此,数组的“键侧”(带有'id', 'name', 'body', 'comments'的左侧)是我们声明结果数组的键的地方 - 这是显而易见的。
右侧(具有 'id', 'title', 'content', 'comments' 的数组值一侧)稍微复杂一些。这些值传递给 AbstractEntity->get() 方法,这些是 "路径",描述了如何获取这些键的值。更多关于 AbstractEntity 的信息请参阅其他页面。
注意::
在继续阅读此文档之前,阅读关于 AbstractEntity 的章节是个好主意。
默认值
您还可以指定默认值以防止在路径错误时抛出 AbstractEntityException(错误指的是如果导致无路可走)。但要做到这一点,在右侧您需要使用扩展版本的选择……
public function dumpMyProject_Article($entity) { return $this->toArray($entity, array( 'id' => 'id', 'name' => array( 'path' => 'title', 'default' => 'defaultname' ), )); }
... 因此,从现在开始,如果对象 Article 字段 'title' 缺失,您将看不到异常,方法将返回(就像什么都没发生一样)值 'defaultname'。
当我提到 "缺失" 时,它的意思是
-
如果 $article 是一个数组 或者 如果对象实现了接口 ArrayAccess
- 如果 'title' 是有效的键,则返回值
-
如果 $article 是一个对象
-
如果 路径有后缀 '()',则显式查找公共方法 'title()' 并尝试执行它并返回值
-
否则 抛出 AbstractEntityException
-
如果 存在公共方法 'getTitle',则执行它并返回值
-
如果 存在公共方法 'isTitle',则执行它并返回值
-
如果 存在公共方法 'hasTitle',则执行它并返回值
-
如果 对象具有(公共或私有)属性 'title',则返回此属性的值。
-
-
由于路径错误,导致无路可走,因此抛出 AbstractEntityException
... 如果上述所有操作都无法获取值,那么 "路径错误",值 缺失,因为在此路径下没有值。
注意:
空字符串或 null 值仍然是有效的值,这并不意味着路径错误。
如果需要将 false 值替换为其他值,则应按如下方式操作
class NewDumper extends Dumper { public function dumpMyProject_Article($entity) { $data = $this->toArray($entity, array( 'id' => 'id', 'name' => array('title', null), # if path 'title' is wrong then # return null instead of throw AbstractEntityException 'body' => 'content', 'comments' => 'comments' )); if (!$data['name']) { $data['name'] = 'default value if here is something false'; } return $data; } }
排除/省略实体
有时我们不希望在源中包含某些特定的实体。要跳过它们,只需抛出 DumperContinueException
use Stopsopa\LiteSerializer\Exceptions\DumperContinueException; class NewDumper extends Dumper { public function dumpMyProject_Comment($entity) { if (!$entity->isModerated()) { throw new DumperContinueException(); } return $this->toArray($entity, array( 'id' => 'id', 'body' => 'content' )); } }
保存键
默认情况下,dumper 在迭代过程中不维护键关联
namespace MyProject; use Stopsopa\LiteSerializer\Dumper; use Stopsopa\LiteSerializer\Exceptions\DumperContinueException; class Group { protected $id; protected $name; public static function getInstance() { return new self(); } public function getId() { return $this->id; } public function setId($id) { $this->id = $id; return $this; } public function getName() { return $this->name; } public function setName($name) { $this->name = $name; return $this; } } # extend just to make this example shorter # the case is that we can build one-to-many relation using this classes class User extends Group { protected $groups = array(); public function getGroups() { return $this->groups; } public function setGroups($groups) { $this->groups = $groups; return $this; } } $user = User::getInstance()->setId(50)->setName('user')->setGroups(array( 'group-1' => Group::getInstance()->setId(10)->setName('gr 1'), 'group-2' => Group::getInstance()->setId(11)->setName('gr 2'), 'group-3' => Group::getInstance()->setId(12)->setName('gr 3'), )); class NewDumper extends Dumper { public function dumpMyProject_User($entity) { return $this->toArray($entity, array( 'id' => 'id', 'name' => 'name', 'groups' => 'groups' )); } public function dumpMyProject_Group($entity) { if ($entity->getName() === 'gr 2') { throw new DumperContinueException(); } return $this->toArray($entity, array( 'id' => 'id', 'name' => 'name' )); } } echo json_encode(NewDumper::getInstance()->dump($user), JSON_PRETTY_PRINT);
... 返回 ...
{ "id": 50, "name": "user", "groups": [ { "id": 10, "name": "gr 1" }, { "id": 12, "name": "gr 3" } ] }
但当你改变 ...
... public function dumpMyProject_User($entity) { return $this->toArray($entity, array( 'id' => 'id', 'name' => 'name', 'groups' => array( 'path' => 'groups', 'savekeys' => true ) )); } ...
... 键将会被保留 ...
{ "id": 50, "name": "user", "groups": { "group-1": { "id": 10, "name": "gr 1" }, "group-3": { "id": 12, "name": "gr 3" } } }
DumperInterface
存在一个特殊接口 Stopsopa\LiteSerializer\DumpToArrayInterface。实现此接口以在 实体本身 中声明如何转储实体。
默认类型序列化(整数、字符串、浮点数等)
其他任何既不是数组也不是对象的值都将通过 dumpPrimitives 方法进行序列化。您可以轻松地通过重写此方法来更改甚至这些值的序列化方式。
强制模式
正如您可能已经注意到的,所有可迭代的值都将被迭代,并且 "集合" 的每个元素都将单独序列化。但是,有一种情况这种行为 "可能"(不一定)是错误的。当实体实现了接口 Traversable 并且在 JSON 源中需要通过此类上的 'foreach' 无法访问的字段时。在这种情况下,有一个机制可以手动决定是否以正常模式序列化此类或让 dumper 迭代。您可以这样做:
public function dumpMyProject_User($entity) { return $this->toArray($entity, array( 'id' => 'id', 'name' => 'name', 'order' => array( 'path' => 'order', 'mode' => Dumper::MODE_ENTITY # or Dumper::MODE_COLLECTION # default is Dumper::MODE_AUTO ) )); }
... 或者您可以手动转储这些实体(不是在 'toArray' 方法中):
public function dumpMyProject_User($entity) { $data = $this->toArray($entity, array( 'id' => 'id', 'name' => 'name', )); $tmp = array(); foreach ($entity->getOrder() as $o) { $tmp[] = array( 'id' => 'id', 'prise' => 'price', ... ); } $data['order'] = $tmp; return $data; }
... 哎呀,太长了。
范围和堆栈
正如我们在文档中看到的那样(链接),可以使用一个输出类从不同的角度输出多个实体。然而,这样做的时候,有时你可能想要改变个别输出方法的操作方式。例如,如果你想要输出文章,在提供文章信息的同时,提供创建此文章的用户信息可能是个好主意,但在这个用例中,你不需要所有关于用户的信息。但是,如果你将输出用户及其所有文章,提供所有关于用户的信息,而文章的信息较少会更好。为了在方法内部区分这些用例,你可以使用所有方法中都可用的两个私有属性 - 范围和堆栈。
使用 '堆栈' 的示例
... public function dumpMyProject_User($entity) { $map = array( 'id' => 'id', 'name' => 'name' ); if (count($this->stack) < 1) { # dump groups of user only if this is feed where # user is on higher level of hierarchy $map['groups'] = 'groups'; } # stack is array and contain more information about where we # are in execution stack, inspect this later if You need. return $this->toArray($entity, $map); } ...
使用 '范围' 的示例
class NewDumper extends Dumper { ... public function dumpMyProject_User($entity) { $map = array( 'id' => 'id', 'name' => 'name' ); if ($this->scope === 'dumpalsogroups') { $map['groups'] = 'groups'; } return $this->toArray($entity, $map); } } NewDumper::getInstance()->dumpScope($user, 'dumpalsogroups');