nuad/graph-objects

一个数组到对象映射的PHP库。在将传入的JSON或数据库结果映射时很有用

v0.4.0 2021-04-14 06:51 UTC

This package is not auto-updated.

Last update: 2024-09-25 22:53:18 UTC


README

图对象是php5的注入器,使用类中定义的元数据将关联数组映射到类实例,当与数据库结果集或可以轻松格式化为关联数组的格式(如JSON)一起工作时非常有用。

该库没有额外要求(除了用于测试的PHPUnit)。它不要求自定义注解语法或使用文档注释来限制反射使用,以保持尽可能简单和轻量。

安装

使用Composer安装 nuad/graph-objects

$ composer require nuad/graph-objects

用法

所有项目文件都在Nuad/Graph命名空间下。所有图对象必须实现的接口是Graphable接口,所有功能都在GraphAdapter特质中。

库使用的两个主要方法是create()和graph()方法。第一个方法将仅输出一个干净的对象实例,第二个方法将提供将关联数组映射到对象的元数据。尽管GraphAdapter提供了一个create()方法的版本(使用反射创建实例),但可能需要一个具有默认值的对象实例。

假设有一个名为Person的DTO、entity等类,该类可能是一个User对象的基类。Person类的简单定义可能是

class Person
{
    public $id;
    public $name;
    public $gender;

    public function __construct($id, $name, $gender)
    {
        $this->id = $id;
        $this->name = $name;
        $this->gender = $gender;
    }
}

现在要使用该库,上述类变为

use Nuad\Graph\Entity;
use Nuad\Graph\Graphable;
use Nuad\Graph\GraphAdapter;
use Nuad\Graph\Type;

class Person implements Graphable
{
    use GraphAdapter;

    public $id;
    public $name;
    public $gender;

    public function __construct($id, $name, $gender)
    {
        $this->id = $id;
        $this->name = $name;
        $this->gender = $gender;
    }

	public static function create()
    {
        return new self(0,'','');
    }

    public function graph()
    {
        return Entity::graph(
            ['Person']
        )
        ->properties(
            [
                'id'        => Type::Integer(),
                'name'      => Type::String(),
                'gender'    => Type::String()
            ]
        );
    }
}

注意:可以忽略create方法,因为我们可以使用特质的create()方法,但这样可以在不使用反射的情况下创建实例。

现在要使用该类将$data关联数组映射,假设是从JSON格式中获取

//person.json
{
     "id": 1,
     "name": "Jewell Lester",
     "gender": "female"
}
$data = json_decode(file_get_contents('person.json'),true);
$person = Person::map($data);

现在$person变量应该是一个包含从JSON文件中获取的数据的新Person实例。

语法

graph方法必须返回一个Entity对象。Entity的构造函数需要一个包含名称的数组,这些名称将标识类。Entity包含一个属性列表(使用properties()方法定义),它是一个关联数组,键是主类的属性名称,值是每个属性的类型。

类型

属性的类型分为三个主要类别

  • 平面类型(整数、双精度、布尔值、平面数组)
  • 对象(指另一个图对象。构造函数需要一个该对象的干净实例)
  • 集合(指一组图对象。构造函数也需要该对象的干净实例)

继承和更复杂类型

使用上面的示例,我们将定义一个User类,它将扩展Person并包含一个Location对象,该对象将包含一个Point对象。

额外的Location和Point类

class Point implements Graphable
{
    use GraphAdapter;
    /**
     * @var float
     */
    public $lat;
    /**
     * @var float
     */
    public $lng;

    public function __construct($lat=0.0, $lng=0.0)
    {
        $this->lat = $lat;
        $this->lng = $lng;
    }

    public function graph()
    {
        return Entity::graph(
            ['Point']
        )
        ->properties(
            [
                'lat'   => Type::Double()
                ->expected(array('latitude')),
                'lng'   => Type::Double()
                ->expected(array('longitude'))
            ]
        );
    }
}

class Location implements Graphable
{
    use GraphAdapter;
    /**
     * @var Point
     */
    public $point;
    /**
     * @var string
     */
    public $city;
    /**
     * @var string
     */
    public $country;
    /**
     * @var string
     */
    public $address;

    public function __construct(Point $point=null, $city='', $country='', $address='')
    {
        $this->point = $point;
        $this->city = $city;
        $this->country = $country;
        $this->address = $address;
    }

    public function graph()
    {
        return Entity::graph(
            ['Location']
        )
        ->properties(
            [
                'point'     => Type::Object(Point::create()),
                'city'      => Type::String(),
                'country'   => Type::String(),
                'address'   => Type::String()
            ]
        );
    }
}

注意:Location包含一个名为point的对象,其类型为Point,并在graph()方法中如此定义。Point图定义了其两个属性预期的数组。在expected属性中,您可以放置可能遇到的数据中属性名称的变体。

现在来定义User子类

class User extends Person
{
    use GraphAdapter;

    /**
     * @var int
     */
    public $age;
    /**
     * @var string
     */
    public $email;
    /**
     * @var boolean
     */
    public $loggedIn;
    /**
     * @var Location
     */
    public $location;
    /**
     * @var Person[]
     */
    public $friends;

    public function __construct($id,$name,$gender,$age, $email, $loggedIn, $location, $friends)
    {
        parent::__construct($id,$name,$gender);
        $this->age = $age;
        $this->email = $email;
        $this->loggedIn = $loggedIn;
        $this->location = $location;
        $this->friends = $friends;
    }

    public function graph()
    {
        return parent::graph()->extend(
            ['User']
        )
        ->properties(
            [
                'age'       => Type::Integer(),
                'email'     => Type::String(),
                'loggedIn'  => Type::Boolean(),
                'location'  => Type::Object(Location::create()),
                'friends'   => Type::Collection(Person::create())
            ]
        );
    }
}

注意:GraphAdapter特质必须重新定义,并在graph()方法中像上面那样扩展父类的graph()方法。

特性

  • 注入:

    使用注入方法代替map方法将强制GraphAdapter特质使用对象的构造函数。此外,injectWithInstance方法接受一个图形对象实例作为参数,该参数将用于映射数据。

  • 映射空:

    特质中的mapEmpty方法只映射对象中为null的值。当在单独的步骤中创建对象属性时很有用。

  • 预期 & 绑定:

    类型的expected()方法接受一个索引数组,其中可能包含属性值的传入数据的位置。属性名称将优先于预期值。要覆盖此行为,请使用bind方法(也接受一个索引数组,该索引数组将优先于预期值和属性名称)。语法

    'id'    => Type::String()
     ->bindTo(['_id','ID'])
     ->expected(['identity','identifier','identification'])
    
  • 回调:

    实体通过finalize()方法接收一个finaliza回调,该回调在映射完成后立即提供对映射对象和原始数据的访问,可以在继续之前验证对象属性。

    类型通过handler()方法接收一个处理器回调,该回调将提供对属性有效负载中数据的访问以及数据找到的索引。如果定义了返回值,则将使用处理器回调返回的值,如果处理器回调返回null,则适配器将正常映射属性。

    return Entity::graph(
            ['Person']
        )
        ->properties(
        [
            'id'        => Type::Integer(),
            'name'      => Type::String(),
            'gender'    => Type::String()
            ->handler(function($data,$name,$scenario)
            {
                return $data === 'fem' ? 'female' : 'male'
            })
        ]
        )->finalize(function(Person $instance, $scenario, $data)
        {
            if($instance->id === null)
            {
                throw new Exception('invalid person data');
            }
        });
    

    注意:两个回调都提供对名为'scenario'的变量的访问。该变量可以通过inject/map方法传递,并在属性用于不同场景时很有用,例如不同的区域数据。