梦游者 / 域名输入
一组域名输入/响应类,充当域名和环境之间的桥梁。
Requires
- php: >=8.0
- somnambulist/collection: ~5.0
- symfony/http-foundation: ^5.3|^6.0
Requires (Dev)
- phpunit/phpunit: ~9.5
README
这个库提供了一个请求对象(例如 Http\Request)和您的领域实体之间的抽象。而不是直接传递请求,它被转换为一个域名输入对象,该对象包含映射到域名对象的信息。域名输入包含只读的请求数据集合和文件。
对于Http,使用了Symfony的UploadedFile和Request,然而任何请求都可以使用。
要求
- PHP 8.0+
- somnambulist/collection 5.0+
安装
使用composer安装,或从github.com检查/提取文件。
- composer require somnambulist/domain-input
域名输入
第一个组件是域名输入及其相关的工厂类:DomainInputFactory。工厂包含从HttpRequest或直接从传递的集合创建的方法。
域名输入对象包含所有要映射到域名中的实体或聚合路由的输入和文件数据。通过抽象请求类型,可以保持域名映射/处理的简洁性,并更易于在其他上下文中使用。
域名输入由以下组成
- 输入
- 文件
两者都从标准集合转换为不可变集合。一旦创建,它们就不应被修改,因为它们现在代表了请求到域名。
域名输入为输入(别名get())和文件()提供访问器。两者都支持点符号来访问嵌套数组数据,例如:object.type.file。
域名响应
当从域名返回数据时,最好将结果表示为单个单元,该单元包括
- 转换后的域名数据
- 事务/域名状态
- 任何消息(例如错误/警告)
提供了基本接口和实现,实现了域名响应。
此响应为只读,并使用不可变集合来存储域名数据和任何消息。此外,原始域名输入与响应相关联。这确保了在进一步处理域名数据时,原始输入可用。
一个重要特性是,域名处理结果包含在这个响应中。它不需要再次由视图/响应层“发现”。状态可以是应用程序所需的数据类型,尽管建议使用字符串或整数。
由于响应是只读的,并且通过构造函数构建,因此任何域名数据都应该收集在集合中,并传递给构造函数。
域名映射器
最后一个组件是一个接口和基本的聚合实现,用于将域名输入映射到您的实体/聚合。这是一个非常简单的接口,包含两个方法
map
supports
map
执行工作并接受域名输入和预创建的实体。需要注意的是,映射器不打算创建主要根实体/聚合。这应该在将数据映射到它之前通过单独的工厂步骤提供。尽管如此,映射器可以创建需要的子实体,这些子实体将附加到根上。
supports
是一个简单的检查,用于判断映射器是否支持传递的实体。这通常是对实体进行的 instanceof 类型检查。这在聚合映射器中用于防止不支持的映射器使用实体进行调用。
映射器本身可以是复杂或简单的,但需要注意的是,它应该只为单个实体或聚合的子集执行映射。例如:你有一个由订单组成且包含子实体行项目、客户、地址的聚合。每个这些可能都需要单独的存储库或额外的支持逻辑。这可以通过单个映射器和用于一次性映射整个输入的 OrderMapper 聚合来封装。
示例
主要订单映射器
use Somnambulist\Components\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\Components\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\Components\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 具体情况。