shampine/sequence

一个现代的管道包

v2.0.0 2023-04-17 23:29 UTC

This package is auto-updated.

Last update: 2024-09-18 02:21:46 UTC


README

example workflow name

一个框架无关的管道包,用于处理基于The PHP League Pipeline Package的完整请求。

Laravel 示例 - https://github.com/shampine/sequence-demo
教程 - https://medium.com/gosteady/day-5-sequence-how-to-guide-56c0af1b2303

原因

使用管道模式,开发者可以快速开发、回收流程和测试一切。

pipeline diagram

使用管道为 MVC 框架带来的好处包括

  • 苗条的、一致的控制器
  • 能够在不同的管道之间共享流程
  • 简单地将服务或仓库类注入到流程中,以保持代码整洁
  • 轻松测试单个流程
  • 清晰、一致的 API 响应
  • 消除在堆栈内部尝试/捕获异常的需要

安装

composer require shampine/sequence

使用

以下示例使用 Laravel 惯例,但此包是无框架的。

请参阅这三个文件,以获取详细的使用示例和 phpunit 测试中的实时演示。

样本有效载荷
样本响应
样本管道

有效载荷

这是活动工作区。有效载荷在通过每个阶段时被修改。任何需要从一个阶段到另一个阶段的数据都需要在有效载荷上设置,然后从有效载荷中检索。

当定义您的有效载荷时,您可以可选地定义一个 $allowlist$overrides

$allowlist = ['email']; // Only hydrate `email` from post/patch
$overrides = ['email_address' => 'email']; // Allows `email_address` to be hydrated as `email`
$payload = new \Sample\SamplePayload($allowlist, $overrides);

允许列表将限制用户提供的输入将填充到有效载荷中。重写参数允许将不同的外部键映射到内部键。例如,如果帖子包含 email_address,但在有效载荷中方法被调用为 setEmail。映射 ['email_address' => 'email'] 将正确地对齐填充。

管道组合

管道可以在 $pipelines 属性中存储多个命名的闭包。这将允许将类似的管道分组在一起,例如 GET、POST、PATCH、DELETE 管道。您可以通过类构造函数或闭包构造函数将属性传递到管道中。

服务、仓库和其他依赖注入参数最好通过类构造函数设置。而标志和其他阶段相关属性可以通过使用 ->process($pipelineName, $payload, ...$argments) 注入。

此示例管道在构造函数中注入了服务,但在 ->process() 上的 $arguments 参数通过两个布尔标志传递。

class SamplePipeline extends AbstractPipeline
{
    public const SAMPLE_PIPELINE = 'SamplePipeline';

    public function __construct(?SampleUseService $sampleUseService = null)
    {
        $this->pipelines = [
            self::SAMPLE_PIPELINE => static function(
                bool $validationFailure = false,
                bool $sequenceFailure = false
            ) use ($sampleUseService)
            {
                return (new Pipeline)
                    ->pipe(new ValidationExceptionProcess($validationFailure, $sampleUseService))
                    ->pipe(new SequenceExceptionProcess($sequenceFailure))
                    ->pipe(new HydrateResponseProcess(SampleResponsePayload::class));
            }
        ];

        $this->excludeWhenEmpty = [
            'empty_value',
        ];

        $this->excludeWhenNull = [
            'null_value',
        ];
    }
}

$excludeWhenEmpty$excludeWhenNull 属性将检查任何根或数据键的值是否为 empty()=== null。如果是这样,它们将被排除在最终数组之外,所有键都应该使用 snake_case

响应

响应是最终的输出容器,应该在管道的最终阶段进行填充。类上的所有属性都可以有 getter,如果没有 getter,则属性将被神奇地访问。

public function __construct(SamplePayload $payload)
{
    $this->setSampleAbout('This is an about statement.');
}

在格式化过程中,将使用 getSampleAbout 编译最终数组,该数组将作为 json 返回。

控制器示例(Laravel)

使用依赖注入在控制器中实例化管道。

class SampleController
{
    public function __construct(SamplePipeline $samplePipeline)
    {
        $this-samplePipeline = $samplePipeline;
    }
}

GET

public function get(Request $request)
{
    $payload = new SamplePayload();
    $response = $this->samplePipeline->process(SamplePipeline::SAMPLE_PIPELINE, $payload)->format();

    return response()->json($response, $response['http_code']);
}

POST

public function post(Request $request)
{
    $payload = (new SamplePayload())->hydratePost($request->all());
    $response = $this->samplePipeline->process(SamplePipeline::SAMPLE_PIPELINE, $payload)->format();

    return response()->json($response, $response['http_code']);
}

PATCH

Patch 负载数据需要 PatchInterfacePatchTrait。有效载荷将包含解析请求要修补的内容的方法 ->getPatch(),以及有效载荷是否为修补请求 ->isPatch()

public function patch(Request $request)
{
    $payload = (new SamplePayload())->hydratePatch($request->all());
    $response = $this->samplePipeline->process(SamplePipeline::SAMPLE_PIPELINE, $payload)->format();

    return response()->json($response, $response['http_code']);
}

异常

包括两个异常,ValidationException 和 SequenceException。这两个异常都会被捕获并转换为 JSON 格式。您可以通过扩展这些类来定义特定的异常。它们与正常的有效负载以相同的方式被捕获和渲染,以便于返回 JSON。

class FetchUser extends AbstractProcess
{
    public function process($payload)
    {
        $user = $this->userService->getUserRepository()->fetchUserById($payload->getId());

        if ($user === null) {
            throw new SequenceException(1000, 'User not found', 400);
        }
    
        $payload->setUser($user);

        return $payload;
    }
}

返回空的用户

{
  "error_code": 1000,
  "status_code": 400,
  "data": null,
  "message": "User not found",
  "error_messages": null
}

任何标准的 PHP 异常都将以正常的方式导致应用程序崩溃。在扩展 ValidationException 或 SequenceException 的异常类构造函数中应该存在日志记录。

测试

要运行所有测试,请运行 ./tests/run.sh

这将执行

  • phpstan 级别 8
  • phpunit 与代码覆盖率(期望 100% 覆盖率)

许可

MIT