codememory/entity-response-control

根据约束控制实体或其他对象返回的响应

v4.0 2024-07-18 19:36 UTC

This package is auto-updated.

Last update: 2024-09-18 19:56:59 UTC


README

该库旨在简化API响应的原型设计。其中,您的类作为您提供的输入对象的响应原型参与

安装

$ composer require codememory/entity-response-control

本文档将涵盖哪些内容?

  • 如何创建您的ResponsePrototype?
  • 有哪些类型的装饰器?
  • 有哪些装饰器存在?
  • 如何创建您自己的Collector?
  • 如何创建上下文工厂?
  • 如何创建您自己的键命名策略?
  • 如何创建您自己的原型属性提供者?

让我们创建我们的ResponsePrototype

[ ! ] 注意,在ResponsePrototype中,我们处理的所有属性都必须具有"private"访问修饰符,这是默认的,如果您想更改创建,请编写您提供者的属性。如何创建提供者将在稍后介绍

use Codememory\EntityResponseControl\Decorators as RCD;
use Codememory\EntityResponseControl\AbstractResponsePrototype;
use Codememory\EntityResponseControl\Collectors\BaseCollector;
use Codememory\Reflection\ReflectorManager;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Codememory\EntityResponseControl\Factory\ConfigurationFactory;
use Codememory\EntityResponseControl\Factory\ExecutionContextFactory;
use Codememory\EntityResponseControl\DecoratorHandlerRegistrar;

// We have some entity User
class User {
    private int $id = 1;
    private string $name = 'My Name';
    
    public function getId(): int 
    {
        return $this->id;
    }
    
    public function getName(): string 
    {
        return $this->name;
    }
    
    public function getCreatedAt(): DateTimeInterface
    {
        return new DateTime();
    }
}

// Our ResponsePrototype
class UserResponsePrototype extends AbstractResponsePrototype
{
    private ?int $id = null;
    private ?string $name = null;
    
    #[RCD\DateTime] // The default format is Y-m-d H:i:s
    private ?string $createdAt = null;
}

$userResponse = new UserResponsePrototype(
    new BaseCollector(),
    new ConfigurationFactory(),
    new ExecutionContextFactory(),
    new DecoratorHandlerRegistrar(),
    new ReflectorManager(new FilesystemAdapter('entity-response-control', '/var/cache/codememory'))
);
$response = $userResponse->collect(new User())->toArray();
        
// We get the answer in the form of an array:
[
    "id" => 1,
    "name" => "My Name",
    "created_at" => "2023-01-03 00:00"
]

这只是一个简单的例子,在实际项目中,您可以根据用户的权限或请求的类型等控制每个属性

让我们看看装饰器

  • AliasInResponse - 在响应中显示不同的名称
    • $name: string - 响应中的属性名称
  • Prefix - 更改调用方法的名称前缀(默认为get)或更改响应中的名称前缀
    • $prototypeObject: string | null - 获取值的原型对象的getter方法前缀
    • $responsePrefix: string | null - 响应中的前缀
  • Custom - 自定义属性,调用get方法将被忽略
    • $methodName: string - 方法名称
  • HiddenNullable - 隐藏响应中具有null值的属性
    • $ignoreEmptyString (默认: true): bool - 如果设置为false,则具有空字符串的属性也将从响应中隐藏
  • Count - 如果属性是数组或实现Countable接口,则调用计数方法,如果值是字符串,则计算字符串的长度,响应类型始终为整数
  • ArrayValues - 将多维数组或对象数组转换为值数组
    • $key: string - 数组键的名称或要调用的方法的名称
  • Callback - 创建您自己的回调,此方法必须在您的ResponseControl内部以公共访问修饰符创建
    • $methodName: string - 方法名称
  • NestedPrototype - 将属性值传递给另一个ResponseControl。请注意使用最后的参数之一,以免产生循环依赖
    • $prototype: string - ResponseControl类的命名空间
    • $skipProperties: array - 忽略一些来自$prototype的属性
    • $skipAllPropertiesExpect: array - 忽略$prototype中的所有属性,但列出那些除外
  • DateTime - 期望属性值是DateTimeInterface接口,如果是,则给定对象将转换为默认格式或您指定的格式
    • $format: string - default(Y-m-d H:i:s) - 格式化日期
    • $full: bool - 默认(false) - 如果为true,则返回一个包含完整信息的DateTime数组,而不是字符串
  • XSS - 保护输入字符串或数组中的字符串免受XSS攻击
  • FromEnum - 返回一个由枚举的键和标签组成的数组
    • $enum: string|null - 如果值是字符串,则为命名空间枚举,如果值已经是枚举对象,则留空
  • CropString - 压缩字符串到最大长度
    • $maxlength: INT - 字符串最大长度
    • $end: string - 默认(...) - 如果字符串被截断,则在字符串末尾的字符
  • PrototypeObjectGetterMethod - 设置从原型对象中获取值的新名称获取器名称
    • $name: string - 方法名称

创建自己的装饰器

use Attribute;
use Codememory\EntityResponseControl\Interfaces\DecoratorInterface;
use Codememory\EntityResponseControl\Interfaces\DecoratorHandlerInterface;
use Codememory\EntityResponseControl\Interfaces\ExecutionContextInterface;
use Symfony\Component\String\u;

// This decorator will change the getter prefix
#[Attribute(Attribute::TARGET_PROPERTY)]
final class MyDecorator implements DecoratorInterface
{
    public function __construct
    (
        public readonly string $prefix
    ) {}
    
    public function getHandler() : string
    {
        return MyDecoratorHandler::class;
    }
}

// Decorator handler
final class MyDecoratorHandler implements DecoratorHandlerInterface 
{
    // This method can return any result.
    public function handle(DecoratorInterface $decorator, ExecutionContextInterface $context) : void
    {
        $propertyName = $context->getProperty()->getName();
        
        $context->setNameGetterToGetValueFromObject(u("{$decorator->prefix}_{$propertyName}")->camel()); // Example: isPay
        
        // Update the value by getting it from the new method
        $context->setValue($context->getPrototypeObject()->{$context->getNameGetterToGetValueFromObject()}()); 
    }
}

注册装饰器

// Before calling collect, refer to the configuration
$responsePrototype->getDecoratorHandlerRegistrar()->register(new MyDecoratorHandler());

// Collect prototype...

考虑创建自己的收集器

use Codememory\Reflection\ReflectorManager;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
use Codememory\EntityResponseControl\Interfaces\CollectorInterface;
use Codememory\EntityResponseControl\Interfaces\ResponsePrototypeInterface;
use Codememory\Reflection\Reflectors\PropertyReflector;
use Codememory\EntityResponseControl\Interfaces\DecoratorInterface;
use Codememory\EntityResponseControl\Factory\ExecutionContextFactory;
use Codememory\EntityResponseControl\Factory\ConfigurationFactory;
use Codememory\EntityResponseControl\DecoratorHandlerRegistrar;

class MyObjectCollector implements CollectorInterface {
    public function collect(ResponsePrototypeInterface $responsePrototype, object $prototypeObject, array $properties): array
    {
        $collectedResponse = [];
    
        foreach ($properties as $property) {
            if ($property instanceof PropertyReflector) {
                // Create a context
                $context = $responsePrototype->getExecutionContextFactory()->createExecutionContext($responsePrototype, $property, $prototypeObject);
                foreach ($property->getAttributes() as $attribute) {
                    $decorator = $attribute->getInstance();
                    
                    if ($decorator instanceof DecoratorInterface) {
                        // Getting a decorator handler
                        $decoratorHandler = $responsePrototype->getDecoratorHandlerRegistrar()->getHandler($decorator->getHandler());
                        
                        // Calling a decorator handler
                        $decoratorHandler->handle($decorator, $context);
                    }
                }
                
                // Collecting an array of data
                $collectedResponse[$context->getResponseKey()] = $context->getValue();
            }
        }
        
        return $collectedResponse;
    }
}

// An example of using our UserPrototype with the new Collector

$userResponse = new UserResponse(
    new MyObjectCollector(),
    new ConfigurationFactory(),
    new ExecutionContextFactory(),
    new DecoratorHandlerRegistrar(),
    new ReflectorManager(new FilesystemAdapter('entity-response-control', '/var/cache/codememory'))
);

$userResponse
    ->collect([new User()])
    ->toArray(); // Response to array

如何创建上下文工厂?

use Codememory\EntityResponseControl\Interfaces\ExecutionContextInterface;
use Codememory\EntityResponseControl\Interfaces\ExecutionContextFactoryInterface;
use Codememory\EntityResponseControl\Interfaces\ResponsePrototypeInterface;
use Codememory\Reflection\Reflectors\PropertyReflector;

// Create a context
final class MyContext implements ExecutionContextInterface
{
    // Implementing Interface Methods...
}

// Creating a context factory
final class MyContextFactory implements ExecutionContextFactoryInterface
{
    public function createExecutionContext(ResponsePrototypeInterface $responsePrototype, PropertyReflector $property, object $prototypeObject): ExecutionContextInterface
    {
        $context = new MyContext();
        // ...
        
        return $context;
    }
}

如何创建您自己的键命名策略?

此策略将在传递给collect的作为 "_{原型属性名称}" 的数据中查找值

use Codememory\EntityResponseControl\Interfaces\ResponseKeyNamingStrategyInterface;

final class MyStrategyName implements ResponseKeyNamingStrategyInterface
{
    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) : ResponseKeyNamingStrategyInterface
    {
        $this->extension = $callback;
        
        return $this;
    }
}

$myPrototype = new MyResponsePrototype(new BaseCollector(), new ConfigurationFactory(), ...);

// To use this strategy, you need to change the configuration
$myPrototype->getConfiguration()->setDataKeyNamingStrategy(new MyStrategyName());

如何创建自己的ResponsePrototype属性提供者?

提供者必须返回允许由收集器处理的dto属性!不要忘记忽略 AbstractResponsePrototype 属性,否则这些属性也会被处理

use Codememory\EntityResponseControl\Provider\ResponsePrototypePrivatePropertyProvider;
use Codememory\Reflection\Reflectors\ClassReflector;

// The provider will say that only private properties need to be processed
final class MyPropertyProvider implements ResponsePrototypePrivatePropertyProvider
{
    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) : ResponsePrototypePrivatePropertyProvider
    {
        $this->extension = $callback;
        
        return $this;
    }
}

$myPrototype = new MyResponsePrototype(new BaseCollector(), new ConfigurationFactory(), ...);

// Change the provider in the configuration
$myPrototype->getConfiguration()->setResponsePrototypePropertyProvider(new MyPropertyProvider());