stopsopa/jms-serializer-lite

一个简单的库,用于将对象/数组从数据库中导出/序列化,以便通过RESTful API公开 - 单向操作

v1.3.0 2016-10-09 20:37 UTC

This package is auto-updated.

Last update: 2024-08-31 00:33:14 UTC


README

Build Status Coverage Status Latest Stable Version

已弃用

创建于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);

执行此操作后,您将遇到错误...

Article exception screen

...这意味着您需要实现方法 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 ...

Comment exception screen

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');