aedart / dto
Requires
- php: >=7.1.0
- aedart/overload: ~5.0
- aedart/util: ~5.0
- illuminate/container: 5.6.*
- illuminate/support: 5.6.*
Requires (Dev)
- aedart/license: 1.*
- aedart/license-file-manager: ~2.0
- aedart/testing: ~2.0
- symfony/var-dumper: ~4.0
README
已弃用 - 数据传输对象(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
贡献
您是否发现了缺陷(错误或设计缺陷),或者您希望进行改进?在以下章节中,您可能会找到有关您可以如何帮助此项目的有用信息。无论如何,我感谢您花时间帮助我改进这个项目的成果和整体质量。
错误报告
如果您确信您已经发现了错误,那么至少您应该创建一个新问题。在那个特定的问题中,您至少应该描述以下内容;
- 缺陷位于何处
- 对缺陷的良好、简短且精确的描述(为什么它是缺陷)
- 如何复制缺陷
- (解决缺陷的可能方法)
当有时间的时候,我会审查您的问题并采取行动。
分支代码并发送拉取请求
一个好的、写得好的错误报告可以极大地帮助我。然而,如果您能够或希望自行解决缺陷,以下是您可以这样做的方法;
- 分支此项目
- 为给定的缺陷修复创建一个新的本地开发分支
- 编写代码/更改
- 创建可执行的测试用例(证明您的更改是可靠的!)
- 提交并将您的更改推送到您的分支仓库
- 发送包含更改的拉取请求
- 喝一杯啤酒 - 您应得的 :)
当我收到拉取请求时(并且有时间处理),我会审查您的更改并将它们合并到这个项目中。如果没有,我会通知您为什么我选择不这么做。
致谢
- Martin Fowler,因为他分享了关于DTO以及许多其他设计模式的知识
- 感谢 Taylor Otwell 创建了 Laravel,特别是我每天都在使用的 服务容器。
- 感谢 Jeffrey Way 创建了 Laracasts —— 一个学习新知识的好地方... 而且我终于理解了 IoC 的一些原则!
版本控制
本软件包遵循 语义版本控制 2.0.0
许可
BSD-3-Clause,请阅读本软件包中包含的 LICENSE 文件