somnambulist / laravel-domain-input-mapper
Requires
- php: >=5.6.0
- illuminate/http: ~5.2
- illuminate/support: ~5.2
- somnambulist/collection: ~1.0
Requires (Dev)
- phpunit/phpunit: ~5.6
README
该项目已被:somnambulist/domain-input-mapper 替换 https://github.com/dave-redfern/somnambulist-domain-input
请迁移到替代包。
域输入映射库
该库提供请求对象(例如Http\Request)与域实体之间的抽象。不是直接传递请求,而是将其转换为包含要映射到域对象的信息的DomainInput对象。域输入包含只读的请求数据和文件的集合。
对于Http,使用Laravel Illuminate的UploadedFile和Request,但可以使用任何请求。
要求
- PHP 5.6+
- Illuminate/Http 5.2+
- Illuminate/Support 5.2+
- somnambulist/collection 1.0+
安装
使用composer安装,或从github.com检出/拉取文件。
- composer require somnambulist/laravel-domain-input-mapper
域输入
第一个组件是DomainInput和相关工厂类:DomainInputFactory。工厂包含从HttpRequest或直接从传递的集合创建的方法。
DomainInput对象包含所有要映射到域中的实体或聚合路由的输入和文件数据。通过抽象请求类型,可以将域映射/处理保持得干净,并更易于在其他上下文中使用。
DomainInput由以下组成
- 输入
- 文件
两者都从标准集合转换为不可变集合。一旦创建,就不应修改它们,因为它们现在代表请求进入域。
DomainInput提供对输入(别名get())和文件的访问器。两者都支持Laravel点表示法来访问嵌套数组的数据,例如:object.type.file。
域响应
当从域返回数据时,最好将结果表示为单个单元,它包括
- 转换后的域数据
- 事务/域状态
- 任何消息(例如错误/警告)
提供了一个基本接口和实现,实现了域响应。
此响应为只读,并使用不可变集合来存储域数据和任何消息。此外,原始的DomainInput与响应相关联。这确保在进一步处理域数据时,原始输入可用。
一个重要功能是,域处理结果提供在这个响应中。它不需要再次由视图/响应层“发现”。状态可以是应用程序所需的任何数据类型,尽管建议使用字符串或整数。
由于响应是只读的,并且通过构造函数构建,因此应该在集合中收集任何域数据,并将其传递给构造函数。
域映射器
最后一个组件是一个接口和基本的聚合实现,用于将DomainInput映射到您的实体/聚合。这是一个非常简单的接口,包含两个方法:
- map
- supports
map方法执行工作,接受DomainInput和预先创建的实体。需要注意的是,映射器并不打算创建主根实体/聚合。这应该在数据映射之前通过单独的工厂步骤提供。话虽如此,映射器可以创建将附加到根的所需子实体。
supports是一个简单的检查,以查看映射器是否支持传递的实体。这通常是对实体进行的instanceof类型检查。在聚合映射器中使用,以防止在不支持映射器的情况下使用实体。
映射器本身可以像需要的那么复杂或简单;需要注意的是,它应该只为单个实体或聚合的子集执行映射。例如:您有一个由订单及其子实体(如行项、客户、地址)组成的聚合。每个这些可能都需要单独的存储库或额外的支持逻辑。这可以封装在单个映射器中,并使用OrderMapper聚合一次映射整个输入。
示例
主订单映射器
use Somnambulist\Domain\Contracts\DataInputMapper as DataInputMapperContract;
class OrderMapper implements DataInputMapperContract
{
/**
* @param Input $input
* @param Order $entity
*/
public function map(Input $input, $entity)
{
$entity
->setProperty($input->get('order.property'))
// ... do other mapping
;
}
/**
* @return boolean
*/
public function supports($entity)
{
return ($entity instanceof Order);
}
}
订单项映射器
use Somnambulist\Domain\Contracts\DataInputMapper as DataInputMapperContract;
class OrderItemMapper implements DataInputMapperContract
{
protected $factory;
public function __construct(OrderFactory $factory)
{
$this->factory = $factory;
}
/**
* @param Input $input
* @param Order $entity
*/
public function map(Input $input, $entity)
{
// look up existing items, or make new ones
foreach ($input->get('order.item') as $item) {
$orderItem = $this->factory->createOrderItem($entity);
// item will be an array, convert to collection
$item = new Immutable($item);
$orderItem
->setSomeProperty($item->get('some_property))
// ... do other mapping
;
}
}
/**
* @return boolean
*/
public function supports($entity)
{
return ($entity instanceof Order);
}
}
地址映射器
use Contracts\AddressableEntity;
use Somnambulist\Domain\Contracts\DataInputMapper as DataInputMapperContract;
class AddressMapper implements DataInputMapperContract
{
/**
* @param Input $input
* @param Order $entity
*/
public function map(Input $input, $entity)
{
$address = new Address();
$address
->setAddressLine1($input->get('address.address_line_1')
//... assign the rest
;
// addresses should be value objects so we'll check if it is the same
// these methods will all be defined in the AddressableEntity interface.
// The address being a value object will have an isSameAs method.
if (!$entity->hasAddress() || !$entity->getAddress()->isSameAs($address)) {
$entity->setAddress($address);
}
}
/**
* @return boolean
*/
public function supports($entity)
{
return ($entity instanceof AddressableEntity);
}
}
将它们全部组合在一起
class OrderAggregateMapper extends AggregateMapper
{
}
// in an input handler / command (better defined in the DI container)
$mapper = new OrderAggregateMapper([
new OrderMapper(),
new OrderItemMapper(new OrderFactory()),
new AddressMapper(),
]);
$input = $inputFactory->createFromHttpRequest($request);
$entity = new Order();
$mapper->map($input, $entity);
这种方法的优点是各个部分的隔离和分离。这使得每个部分更容易测试、更容易管理,在某些情况下,映射器可以重用(例如,地址)。替代方案可能是单个处理程序,它必须接收实体管理器实例或许多存储库来完成相同的工作。如果您不想映射所有数据或您想在多个步骤中部分映射聚合根,每个都必须单独处理,而通过这种方法,只需在各个阶段添加所需的映射器即可。
最后:重要的是要记住,数据输入映射器是您领域的一部分,而不是控制器或上下文类型的一部分。它不应了解HTTP或CLI的具体情况。