aedart/dto

此包已被废弃且不再维护。作者建议使用 aedart/athenaeum 包代替。

数据传输对象(DTO)设计模式(分发模式)的变体/解释。为这样的DTO提供抽象

5.1.0 2018-09-15 15:37 UTC

This package is auto-updated.

Last update: 2022-02-01 12:50:58 UTC


README

Build Status Latest Stable Version Total Downloads Latest Unstable Version License

已弃用 - 数据传输对象(DTO)

该包已被 aedart/athenaeum 替换

数据传输对象(DTO)设计模式(分发模式)的变体/解释。DTO不过是一个可以持有一些数据的对象。通常用于在系统之间传输数据,例如客户端和服务器之间。

此包为这样的DTO提供了抽象。

如果你不了解DTO,我建议你阅读 Martin Fowler对DTO的描述,并可能对这个主题进行一些 Google搜索

内容

何时使用此

  • 当有强烈的需要接口DTO时,例如哪些属性必须通过getter和setter可用
  • 当你需要封装需要在系统之间或组件实例之间通信的数据时

尽管如此,使用DTO会增加你的项目的复杂性。因此,只有当你确实需要它们时,才应该使用它。

如何安装

composer require aedart/dto

此包使用 composer。如果你不知道它是做什么的或它的工作原理,我建议你在尝试使用此包之前先了解一下。

快速入门

为你的DTO创建自定义接口

首先为你的DTO创建一个接口。以下是一个简单的Person接口的示例

<?php
use Aedart\DTO\Contracts\DataTransferObject as DataTransferObjectInterface;

interface PersonInterface extends DataTransferObjectInterface
{
    /**
     * Set the person's name
     *
     * @param string|null $name
     */
    public function setName(?string $name);
    
    /**
     * Get the person's name
     *
     * @return string
     */
    public function getName() : ?string;
    
    /**
     * Set the person's age
     *
     * @param int $age
     */
    public function setAge(?int $age);
    
    /**
     * Get the person's age
     *
     * @return int
     */
    public function getAge() : ?int;
}

你的DTO的具体实现

创建接口的具体实现。让它扩展默认的 DataTransferObject 抽象。

<?php
declare(strict_types=1);

use Aedart\DTO\DataTransferObject;

class Person extends DataTransferObject implements PersonInterface
{
 
    protected $name = '';
    
    protected $age = 0;
 
    /**
     * Set the person's name
     *
     * @param string $name
     */
    public function setName(?string $name)
    {
        $this->name = $name;
    }
    
    /**
     * Get the person's name
     *
     * @return string
     */
    public function getName() : ?string
    {
        return $this->name;
    }
    
    /**
     * Set the person's age
     *
     * @param int $age
     */
    public function setAge(?int $age)
    {
        $this->age = $age;
    }
    
    /**
     * Get the person's age
     *
     * @return int
     */
    public function getAge() : ?int
    {
        return $this->age;
    } 

}

现在你可以使用DTO了。以下部分将突出一些使用场景。

属性重载

每个定义的属性可以通过多种方式访问,如果为该属性定义了获取器或设置器方法。

如需更多信息,请阅读有关修改器和访问器PHP的魔术方法PHP的数组访问的说明。

<?php

// Create a new instance of your DTO
$person = new Person();

// Name can be set using normal setter methods
$person->setName('John');

// But you can also just set the property itself
$person->name = 'Jack'; // Will automatically invoke setName()

// And you can also set it, using an array-accessor
$person['name'] = 'Jane'; // Will also automatically invoke setName()

// ... //

// Obtain age using the regular getter method
$age = $person->getAge();

// Can also get it via invoking the property directly
$age = $person->age; // Will automatically invoke getAge()

// Lastly, it can also be access via an array-accessor
$age = $person['age']; // Also invokes the getAge()

提示:PHPDoc的property标签

如果您正在使用现代的集成开发环境,那么它很可能支持PHPDoc

通过在接口或具体实现中添加一个@property标签,您的IDE将能够自动完成可重载的属性。

通过数组填充

您可以使用数组来填充您的DTO。

<?php

// property-name => value array
$data = [
    'name' => 'Timmy Jones',
    'age'  => 32
];

// Create instance and invoke populate
$person = new Person();
$person->populate($data); // setName() and setAge() are invoked with the given values

如果您正在扩展默认的DTO抽象,那么您也可以在构造函数中传递一个数组

<?php

// property-name => value array
$data = [
    'name' => 'Carmen Rock',
    'age'  => 25
];

// Create instance and invoke populate
$person = new Person($data); // invokes populate(...), which then invokes the setter methods

将属性导出到数组

每个DTO都可以导出到一个数组。

<?php

// Provided that you have a populated instance, you can export those properties to an array 
$properties = $person->toArray();

var_dump($properties);  // Will output a "property-name => value" list
                        // Example:
                        //  [
                        //      'name'  => 'Timmy'
                        //      'age'   => 16
                        //  ]

序列化为Json

所有DTO都是Json序列化的,这意味着它们继承自JsonSerializable接口。这意味着当使用json_encode()时,DTO会自动确保其属性可以通过编码方法进行序列化。

<?php

$person = new Person([
    'name' => 'Rian Dou',
    'age' => 29
]);

echo json_encode($person);

上面的示例将输出以下内容;

{
    "name":"Rian Dou",
    "age":29
}

您还可以直接在DTO上执行json序列化,通过调用toJson()方法。

<?php

$person = new Person([
    'name' => 'Rian Dou',
    'age' => 29
]);

echo $person->toJson(); // The same as invoking json_encode($person);

高级用法

控制反转(IoC)/依赖注入

在这个DTO设计模式的解释中,每个实例都必须持有对IoC服务容器的引用。

如果您不知道这是什么意思或它是如何工作的,请先阅读有关它的维基文章

启动服务容器

如果您在这个Laravel应用程序内部使用此包,则可以跳过此部分;它不是必需的!

<?php

use Aedart\DTO\Providers\Bootstrap;

// Invoke the bootstrap's boot method, before using any DTOs
// Ideally, this should happen along side your application other bootstrapping logic
Bootstrap::boot(); // A default service container is now available 

嵌套实例

想象一下,您的Person DTO接受更复杂的属性,例如地址;

注意:此示例仅当;

a) 您在Laravel应用程序内部使用DTO

b) 您在使用给定的DTO之前调用了Bootstrap::boot()方法(再次强调,如果您在Laravel应用程序内部使用此包,则不需要此操作

<?php
declare(strict_types=1);

use Aedart\DTO\DataTransferObject;

// None-interfaced DTO class is on purpose for this example
class Address extends DataTransferObject
{

    protected $street = '';

    /**
     * Set the street
     *
     * @param string $street
     */
    public function setStreet(?string $street)
    {
        $this->street = $street;
    }
    
    /**
     * Get the street
     *
     * @return string
     */
    public function getStreet() : ?string
    {
        return $this->street;
    }
}

// You Person DTO now accepts an address object
class Person extends DataTransferObject implements PersonInterface
{
 
    protected $name = '';
    
    protected $age = 0;
 
    protected $address = null;
 
    // ... getters and setters for name and age not shown ... //

     /**
      * Set the address
      *
      * @param Address $address
      */
     public function setAddress(?Address $address)
     {
         $this->address = $address;
     }
     
     /**
      * Get the address
      *
      * @return Address
      */
     public function getAddress() : ?Address
     {
         return $this->address;
     }
}

// ... some place else, in your application ... //

// Data for your Person DTO
$data = [
    'name' => 'Arial Jackson',
    'age' => 42,
    
    // Notice that we are NOT passing in an instance of Address, but an array instead!
    'address' => [
        'street' => 'Somewhere str. 44'
    ]
];

$person = new Person($data);                                    
$address = $person->getAddress();   // Instance of Address - Will automatically be resolved (if possible).

在上面的示例中,Laravel的服务容器尝试查找并创建任何期望的具体实例。

此外,默认的DTO抽象(Aedart\DTO\DataTransferObject)将尝试自动填充该实例。

接口绑定

如果您更愿意使用接口,则需要在使用DTO或服务容器处理和解析之前,将接口绑定到具体的实例。

Laravel 应用程序外部

如果您不在Laravel应用程序中,则可以按照以下方式将接口绑定到具体实例:

<?php

// Somewhere in your application's bootstrapping logic

use Aedart\DTO\Providers\Bootstrap;

// Boot up the service container
Bootstrap::boot(); 

// Register / bind your interfaces to concrete instances
Bootstrap::getContainer()->bind(CityInterface::class, function($app){
    return new City();
});

Laravel 应用程序内部

在您的应用程序的服务提供者(或可能是一个自定义服务提供者)中,您可以绑定您的DTO接口到具体的实例;

<?php

// ... somewhere inside your service provider

// Register / bind your interfaces to concrete instances
$this->app->bind(CityInterface::class, function($app){
    return new City();
});

示例

假设您已经将接口绑定到具体实例,那么以下操作是可能的

<?php
use Aedart\DTO\Contracts\DataTransferObject as DataTransferObjectInterface;
use Aedart\DTO\DataTransferObject;

// Interface for a City
interface CityInterface extends DataTransferObjectInterface
{
    /**
     * Set the city's name
     *
     * @param string $name
     */
    public function setName(string $name) : void;
    
    /**
     * Get the city's name
     *
     * @return string
     */
    public function getName() : string;
}

// Concrete implementation of City
class City extends DataTransferObject implements CityInterface
{
    protected $name = '';
    
    // ... getter and setter implementation not shown ... //
}

// Address class now also accepts a city property, of the type CityInterface
class Address extends DataTransferObject
{

    protected $street = '';

    protected $city = null;

    // ... street getter and setter implementation not shown ... //
    
     /**
      * Set the city
      *
      * @param CityInterface $address
      */
     public function setCity(?CityInterface $city)
     {
         $this->city = $city;
     }
     
     /**
      * Get the city
      *
      * @return CityInterface
      */
     public function getCity() : ?CityInterface
     {
         return $this->city;
     }
}

// ... some other place in your application ... //

$addressData = [
    'street' => 'Marshall Street 27',
    'city' => [
        'name' => 'Lincoln'
    ]
];

// Create new instance and populate
$address = new Address($addressData);   // Will attempt to automatically resolve the expected city property,
                                        // of the CityInterface type, by creating a concrete City, using
                                        // the service container, and resolve the bound interface instance

贡献

您是否发现了缺陷(错误或设计缺陷),或者您希望进行改进?在以下章节中,您可能会找到有关您可以如何帮助此项目的有用信息。无论如何,我感谢您花时间帮助我改进这个项目的成果和整体质量。

错误报告

如果您确信您已经发现了错误,那么至少您应该创建一个新问题。在那个特定的问题中,您至少应该描述以下内容;

  • 缺陷位于何处
  • 对缺陷的良好、简短且精确的描述(为什么它是缺陷)
  • 如何复制缺陷
  • 解决缺陷的可能方法

当有时间的时候,我会审查您的问题并采取行动。

分支代码并发送拉取请求

一个好的、写得好的错误报告可以极大地帮助我。然而,如果您能够或希望自行解决缺陷,以下是您可以这样做的方法;

  • 分支此项目
  • 为给定的缺陷修复创建一个新的本地开发分支
  • 编写代码/更改
  • 创建可执行的测试用例(证明您的更改是可靠的!)
  • 提交并将您的更改推送到您的分支仓库
  • 发送包含更改的拉取请求
  • 喝一杯啤酒 - 您应得的 :)

当我收到拉取请求时(并且有时间处理),我会审查您的更改并将它们合并到这个项目中。如果没有,我会通知您为什么我选择不这么做。

致谢

版本控制

本软件包遵循 语义版本控制 2.0.0

许可

BSD-3-Clause,请阅读本软件包中包含的 LICENSE 文件