codememory / dto
Requires
- php: >=8.1
- codememory/reflection: ^3.0
- symfony/string: ^7.1
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.21
- symfony/var-dumper: ^6.1
README
这个库主要用于Symfony,但也可以在原生PHP中使用。这个库与其它库有什么不同?该库可以根据您指定的规则自动从您指定的数据中收集对象,基本上这是来自Request的数据
安装
$ composer require codememory/dto
本文档将涵盖哪些内容?
- 如何使用DTO?
- 如何使用symfony/validator验证DTO?
- 如何使用装饰器?
- 装饰器中的上下文是什么意思?
- 如何创建自己的装饰器?
- 收集器是什么?如何创建自己的收集器?
- 如何创建上下文工厂?
- 如何创建自己的键命名策略?
- 如何创建自己的DTO属性提供者?
[ ! ] 注意:在DataTransfer中,我们处理的所有属性都必须有访问修饰符 "public"
使用示例
<?php use Codememory\Dto\Collectors\BaseCollector; use Codememory\Dto\Decorators as DD; use Codememory\Dto\Factory\ConfigurationFactory; use Codememory\Dto\DecoratorHandlerRegistrar; use Codememory\Reflection\ReflectorManager; use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Codememory\Dto\DataTransferObjectManager; use Codememory\Dto\Factory\ExecutionContextFactory; enum StatusEnum { case ACTIVATED; case NOT_ACTIVATED; } #[DD\ToType] final class UserDto extends DataTransferObjectManager { public ?string $name = null; public ?string $surname = null; public ?int $age = null; #[DD\ToEnum] public ?StatusEnum $status = null; } $userDto = new UserDto( new BaseCollector(), new ConfigurationFactory(), new ExecutionContextFactory(), new DecoratorHandlerRegistrar(), new ReflectorManager(new FilesystemAdapter('dto', '/var/cache/codememory')) ); // We start the assembly of DTO based on the transferred data $userDto->collect([ 'name' => 'My Name', 'surname' => 'My Surname', 'age' => 80, 'status' => 'ACTIVATED' ]); // Result dump UserDto /** name -> My Name surname -> My Surname age -> 80 status -> StatusEnum::ACTIVATED (object) */
使用symfony/validator验证DTO
use Symfony\Component\Validator\Validation; use Symfony\Component\Validator\Constraints as Assert; use Codememory\Reflection\ReflectorManager; use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Codememory\Dto\DataTransferObjectManager; use Codememory\Dto\Factory\ConfigurationFactory; use Codememory\Dto\DecoratorHandlerRegistrar; use Codememory\Dto\Factory\ExecutionContextFactory; final ProductDto extends DataTransferObjectManager { #[DD\Validation([ new Assert\NotBlank(message: 'Name is required'), new Assert\Length(max: 5, maxMessage: 'Name must not exceed 5 characters') ])] public ?string $name = null; } $productDto = new ProductDto( new BaseCollector(), new ConfigurationFactory() new ExecutionContextFactory(), new DecoratorHandlerRegistrar(), new ReflectorManager(new FilesystemAdapter('dto', '/var/cache/codememory')) ); $productDto->collect(['name' => 'Super name']); // Validate $validator = Validation::createValidatorBuilder() ->enableAnnotationMapping() ->getValidator(); $errors = $validator->validate($productDto); foreach ($errors as $error) { echo $error->getMessage(); // Name must not exceed 5 characters }
使用装饰器
装饰器有两种类型,一种指向DTO类,另一种指向DTO属性
与类上的装饰器不同的是,给定的装饰器将针对所有DTO属性执行,并且给定的装饰器将首先执行
use Codememory\Dto\Decorators as DD // Decorator for class #[DD\ToType] // This decorator will cast all DTO properties to the type specified by the property final class OneDto extends AbstractDataTransferObject { public ?int $number = null; public array $list = []; } // Decorators for properties final class TestDto extends AbstractDataTransferObject { #[DD\NestedDTO(OneDto::class)] public ?OneDto $one = null; // Multiple decorators // Priority works here, first ToEnum will be executed, and then IgnoreSetterCallForHarvestableObject #[DD\ToEnum] #[DD\IgnoreSetterCallForHarvestableObject] public ?StatusEnum $status = null; }
装饰器列表
-
IgnoreSetterCallForHarvestableObject - 忽略对可收集对象的setter调用
-
PrefixSetterMethodForHarvestableObject - 改变设置可收集对象值的方法的前缀
- $prefix - 前缀名称,例如 "set"
-
SetterMethodForHarvestableObject - 通过更改方法的全名来设置可收集对象的值
- $name - 方法名称,例如 "setName"
-
NestedDTO - 嵌套数据传输,嵌套在数据传输属性中
- $dto - 数据传输命名空间
- $object (默认: null) - 要收集的对象的命名空间。如果没有传递值,则此装饰器附加的属性将忽略对收集对象的setter调用
- $thenCallback (默认: null) - 回调方法名称,该方法应返回一个bool值,表示是否值得检查
-
ToEnum - 将收集数据中的值转换为枚举对象
- $byValue (默认: false) - 在枚举中按值搜索,默认按名称搜索
-
ToEnumList - 与ToEnum装饰器类似,但此装饰器期望一个数组,并将尝试将值的每个元素转换为枚举
- $unique (默认: true) - 这是一个新的参数,它将过滤输入数组以实现唯一性
-
ToType - 将收集数据中的值转换为特定类型
- $type (默认: auto) - PHP类型或Interface DateTime的名称。默认情况下,它在属性的类型上工作
- $onyData (默认: false) - 强制类型转换,仅对收集数据级别的值进行转换
-
Validation - 将symfony assert约束添加到验证队列
- $assert - 如果此属性将被处理,则包含验证规则的数组
-
XSS - 保护输入字符串或数组中的字符串免受XSS攻击
-
ExpectArray - 期望一个常规数组
- $expectKeys - 待处理键的数组,其余将被删除
-
ExpectMultiArray - 期望一个常规数组
- $expectKeys - 待处理键的数组,其余将被删除
- $itemKeyAsNumber (默认: true) - 将所有项目键转换为数字顺序
-
ExpectOneDimensionalArray - 期望一个一维数组
- $types (默认: any) - 跳过的值类型数组
数据键命名策略列表
- Codememory\Dto\DataKeyNamingStrategy\DataKeyNamingStrategyCamelCase - 将属性名称转换为 camelCase 以在数据中进行查找
- Codememory\Dto\DataKeyNamingStrategy\DataKeyNamingStrategySnakeCase - 将属性名称转换为 snake_case 以在数据中进行查找
- Codememory\Dto\DataKeyNamingStrategy\DataKeyNamingStrategyUpperCase - 将属性名称转换为 UPPER_CASE 以在数据中进行查找
属性提供者列表
- Codememory\Dto\Provider\DataTransferObjectPrivatePropertyProvider - 只允许私有属性,忽略 AbstractDataTransferObject 属性
- Codememory\Dto\Provider\DataTransferObjectProtectedPropertyProvider - 只允许受保护的属性,忽略 AbstractDataTransferObject 属性
- Codememory\Dto\Provider\DataTransferObjectPublicPropertyProvider - 只允许公共属性,忽略 AbstractDataTransferObject 属性
解析上下文
这是一个 API 类,它位于装饰器内部,用于管理 dto 的状态或值、收集的对象以及收集数据中的值
方法
- getDataTransferObject - 返回包含正在处理的属性的当前数据传输对象
- getProperty - 返回当前处理的属性
- getData - 返回用于收集 dto 和对象的输入数据
- getDataValue - 从数据(在数据传输构建期间传递)中返回一个值
- getDataTransferObjectValue - 返回设置到数据传输属性中的值
- getValueForHarvestableObject - 返回设置到正在收集的对象中的值
- getDataKey - 返回可用于从数据中获取值的键
- getNameSetterMethodForHarvestableObject - 返回正在收集的对象的设置方法名称
- isIgnoredSetterCallForHarvestableObject - 是否忽略收集对象的设置方法调用
- isSkippedThisProperty - 是否跳过当前属性的处理
许多这些方法都有设置器。
数据传输对象方法
- getCollector - 返回用于收集 dto 的收集器
- getConfiguration - 返回 dto 配置
- getExecutionContextFactory - 返回用于创建装饰器上下文的工厂
- getReflectorManager - 返回反射管理器,更多详细信息请参阅 codememory/reflection 库
- getClassReflector - 返回当前 dto 的反射
- getHarvestableObject - 返回可收集的对象
- setHarvestableObject - 设置构建对象
- addDataTransferObjectPropertyConstraintsCollection - 添加 DTO 属性验证集合
- getListDataTransferObjectPropertyConstrainsCollection - 获取所有 dto 属性验证集合的列表
- getDataTransferObjectPropertyConstrainsCollection - 获取特定 dto 的集合验证属性
- collect - 开始整个构建过程
- recollectHarvestableObject - 将可收集对象重新构建为新传递的对象
创建自己的装饰器
use Attribute; use Codememory\Dto\Interfaces\DecoratorInterface; use Codememory\Dto\Interfaces\DecoratorHandlerInterface; use Codememory\Dto\Interfaces\ExecutionContextInterface; use Codememory\Reflection\ReflectorManager; use Symfony\Component\Cache\Adapter\FilesystemAdapter; use Codememory\Dto\DataTransferObjectManager; use Codememory\Dto\Factory\ExecutionContextFactory; use Codememory\Dto\Factory\ConfigurationFactory; use Codememory\Dto\DecoratorHandlerRegistrar; // Let's create a decorator that will combine the value of all properties and separate it with a certain character #[Attribute(Attribute::TARGET_PROPERTY)] // Will only apply to properties final class PropertyConcatenation implements DecoratorInterface { public function __construct( public readonly string $propertyNames, public readonly string $separator ) {} public function getHandler() : string { return PropertyConcatenationHandler::class; } } // Create decorator Handler final class PropertyConcatenationHandler implements DecoratorHandlerInterface { /** * @param PropertyConcatenation $decorator */ public function handle(ConstraintInterface $decorator, ExecutionContextInterface $context) : void { // Get the values of all passed properties $assignedValues = array_map(static function (string $property) use ($context) { return $context->getDataTransferObject()->$property; }, $decorator->propertyNames); // Update the current value by concatenating multiple values separating them with $separator $context->setDataTransferObjectValue(implode($decorator->separator, $assignedValues)); $context->setValueForHarvestableObject($context->getDataTransferObject()); } } // Let's test our decorator final class TestDto extends DataTransferObjectManager { public ?string $name = null; public ?string $surname = null; #[PropertyConcatenation(['name', 'surname'], '+')] public ?string $fullName = null; } $testDto = new TestDto( new BaseCollector(), new ConfigurationFactory(), new ExecutionContextFactory(), new DecoratorHandlerRegistrar(), new ReflectorManager(new FilesystemAdapter('dto', '/var/cache/codememory')) ); // To register this decorator when you create a new instance by passing the configuration as the second argument to it, you must first register the decorator through this configuration $testDto->getDecoratorHandlerRegistrar()->register(new PropertyConcatenationHandler()); $testDto->collect([ 'name' => 'Code', 'surname' => 'Memory', 'full_name' => 'test_full_name' // Our decorator will override this value ]); echo $testDto->fullName // Code+Memory
创建自己的收集器
收集器 - 是一个 DTO 收集器,它在处理每个 DTO 属性中起着重要作用
use Codememory\Dto\Interfaces\CollectorInterface; use Codememory\Dto\Interfaces\ExecutionContextInterface; use Codememory\Dto\Interfaces\DecoratorInterface; final class MyCollector implements CollectorInterface { public function collect(ExecutionContextInterface $context) : void { // Here all processing begins on each property, this method is called every iteration of the properties // Example get property attributes foreach ($context->getProperty()->getAttributes() as $attribute) { $attributeInstance = $attribute->newInstance(); if ($attributeInstance instanceof DecoratorInterface) { $decoratorHandler = $context->getDataTransferObject()->getDecoratorHandlerRegistrar()->getHandler($attributeInstance->getHandler()); // .... } } // .... } }
如何创建上下文工厂?
use Codememory\Dto\Interfaces\ExecutionContextFactoryInterface; use Codememory\Dto\Interfaces\ExecutionContextInterface; use Codememory\Dto\Interfaces\DataTransferObjectInterface; use Codememory\Reflection\Reflectors\PropertyReflector; use Codememory\Dto\Interfaces\ExecutionContextInterface; use Codememory\Dto\Factory\ConfigurationFactory; // Create a context final class MyContext implements ExecutionContextInterface { // Implementing Interface Methods... } // Creating a context factory final class MyContextFactory implements ExecutionContextFactoryInterface { public function createExecutionContext(DataTransferObjectInterface $dataTransferObject, PropertyReflector $property, array $data) : ExecutionContextInterface { $context = new MyContext(); // ... return $context; } } // When creating a DTO instance, we pass this context factory // Example: new MyDto(new BaseCollector(), new ConfigurationFactory(), new MyContextFactory(), ...);
如何创建自己的键命名策略?
此策略将在传递给收集的“_{dto 属性名称}”中的数据中查找值
use Codememory\Dto\Interfaces\DataKeyNamingStrategyInterface; use Codememory\Dto\Factory\ConfigurationFactory; final class MyStrategyName implements DataKeyNamingStrategyInterface { private ?\Closure $extension = null; public function convert(string $propertyName) : string { $name = "_$propertyName"; if (null !== $this->extension) { return call_user_func($this->extension, $name); } return $name; } // With this method, you need to give the opportunity to extend the convert method public function setExtension(callable $callback) : DataTransferObjectPropertyProviderInterface { $this->extension = $callback; return $this; } } $myDto = new MyDTO(new BaseCollector(), new ConfigurationFactory(), ...); // To use this strategy, you need to change the configuration $myDto->getConfiguration()->setDataKeyNamingStrategy(new MyStrategyName());
如何创建自己的DTO属性提供者?
提供者必须返回允许由收集器处理的 dto 属性!不要忘记忽略 AbstractDataTransferObject 属性,否则这些属性也将被处理
use Codememory\Dto\Interfaces\DataTransferObjectPropertyProviderInterface; use Codememory\Reflection\Reflectors\ClassReflector; use Codememory\Dto\Factory\ConfigurationFactory; // The provider will say that only private properties need to be processed final class MyPropertyProvider implements DataTransferObjectPropertyProviderInterface { private ?\Closure $extension = null; public function getProperties(ClassReflector $classReflector) : array { $properties = $classReflector->getPrivateProperties(); if (null !== $this->extension) { return call_user_func($this->extension, $properties); } return $properties; } // With this method, you need to give the opportunity to extend the getProperties method public function setExtension(callable $callback) : DataTransferObjectPropertyProviderInterface { $this->extension = $callback; return $this; } } $myDto = new MyDTO(new BaseCollector(), new ConfigurationFactory(), ...); // Change the provider in the configuration $myDto->getConfiguration()->setDataTransferObjectPropertyProvider(new MyPropertyProvider());