chevere/workflow

PHP工作流程程序

0.9.0 2024-02-12 15:35 UTC

README

🔔 订阅通讯,不错过任何关于Chevere的更新。

Chevere

Build Code size Apache-2.0 PHPStan Mutation testing badge

Quality Gate Status Maintainability Rating Reliability Rating Security Rating Coverage Technical Debt CodeFactor

摘要

工作流程是一个可配置的存储过程,可以运行一个或多个作业。作业相互独立,但通过在作业之间传递响应引用而相互关联。作业支持基于变量和先前作业响应的条件执行。

安装

工作流程通过Packagist提供,并存储在chevere/workflow

composer require chevere/workflow

它做什么?

工作流程包提供基于工作流程模式定义执行过程的工具。其目的是将逻辑指令抽象为相互关联的独立作业单元。

而不是构建一个单体过程,您定义一个由作业组成的工作流程,使开发者能够轻松测试和维护可重用多用途逻辑。

::: tip 💡 工作流程介绍 在Rodolfo的博客中阅读Workflow for PHP,了解此包的全面介绍。 ::

如何使用

工作流程在Chevere\Workflow命名空间中提供了以下函数。使用这些函数定义工作流程、其变量以及命名作业的响应引用。

  • 作业由其Action定义
  • 作业相互独立,使用variable()函数定义共享变量
  • 使用函数response()通过引用{作业#A响应} -> {作业#B输入}

使用此包产生逻辑

  1. 使用workflow函数创建工作流程
  2. 使用syncasync函数定义作业
  3. 使用run函数运行工作流程

创建工作流程

要创建工作流程,请定义其命名作业。

通过传递Action及其预期运行参数(可以是原始值、变量和/或响应到另一个作业的输出)来创建一个作业

编写工作流程作业的语法需要为作业的名称指定name,根据作业运行方式指定sync/async,并为每个Action::main参数绑定命名parameter

<name>: <sync|async>(
    <action>,
    <parameter>: <variable|reference|raw>,
)

例如,对于给定的MyAction操作

use function Chevere\Action\Action;

class MyAction extends Action
{
    protected function main(string $foo, string $bar): array
    {
        return [];
    }
}

您将能够编写如下工作流程

use function Chevere\Workflow\sync;

workflow(
    greet: sync(
        new MyAction(),
        foo: variable('super'),
        bar: variable('taldo'),
    )
);

使用同步作业

使用sync函数创建同步作业,它将在得到解决之前阻塞执行。

以下示例描述了一个工作流程,描述了一个图像上传过程。

use function Chevere\Workflow\sync;
use function Chevere\Workflow\response;
use function Chevere\Workflow\variable;
use function Chevere\Workflow\workflow;

workflow(
    user: sync(
        new GetUser(),
        request: variable('payload')
    ),
    validate: sync(
        new ValidateImage(),
        mime: 'image/png',
        file: variable('file')
    ),
    meta: sync(
        new GetMeta(),
        file: variable('file'),
    ),
    store: sync(
        new StoreFile(),
        file: variable('file'),
        name: response('meta', 'name'),
        user: response('user')
    ),
);
  • variable('payload')variable('file')声明了一个变量
  • response('meta', 'name')response('user')声明了一个响应引用。

这个工作流的图表表示,所有作业都是使用 sync 定义的,因此它们是依次运行的。

$workflow->jobs()->graph()->toArray();
// contains
[
    ['user'],
    ['validate'],
    ['meta'],
    ['store']
];

为了完成示例,以下是运行先前定义的工作流的方法

use function Chevere\Workflow\run;

run(
    $workflow,
    payload: $_REQUEST,
    file: '/path/to/file',
);

使用异步作业

使用 async 函数创建一个异步作业,它在并行中非阻塞地运行。

下面的示例中,一个工作流描述了为多个图像大小创建图像的过程。

use function Chevere\Workflow\sync;
use function Chevere\Workflow\response;
use function Chevere\Workflow\variable;
use function Chevere\Workflow\workflow;

workflow(
    thumb: async(
        new ImageResize(),
        image: variable('image'),
        width: 100,
        height: 100,
        fit: 'thumb'
    ),
    medium: async(
        new ImageResize(),
        image: variable('image'),
        width: 500,
        fit: 'resizeByW'
    ),
    store: sync(
        new StoreFiles(),
        response('thumb', 'filename'),
        response('medium', 'filename'),
    ),
);
  • variable('image') 声明了一个 变量
  • response('thumb', 'filename')response('medium', 'filename') 声明了一个 响应 引用。

这个工作流的图表表明 thumbmediumposter 是非阻塞地并行运行的。作业 store 是阻塞的(另一个节点)。

$workflow->jobs()->graph()->toArray();
// contains
[
    ['thumb', 'medium', 'poster'],
    ['store']
];

为了完成示例,以下是运行先前定义的工作流的方法

use function Chevere\Workflow\run;

run(
    workflow: $workflow,
    arguments: [
        'image' => '/path/to/file',
    ]
);

变量

使用 variable 函数声明工作流变量。这表示一个变量必须在工作流运行层注入。

use function Chevere\Workflow\variable;

variable('myVar');

响应

使用 response 函数声明对先前作业返回的响应的作业响应引用。

🪄 使用响应时,将自动声明引用的作业为 依赖项

use function Chevere\Workflow\response;

response(job: 'task');

也可以通过 key 标识的响应成员来进行引用。

use function Chevere\Workflow\response;

response(job: 'task', key: 'name');

创建作业

Job 类定义了一个带有参数的 操作,这些参数可以通过构造函数使用命名参数“按原样”传递、变量响应

同步作业

use function Chevere\Workflow\job;

sync(
    new SomeAction(),
    ...$argument
);

异步作业

use function Chevere\Workflow\job;

async(
    new SomeAction(),
    ...$argument
);

注意:操作必须支持 序列化,才能在 async 作业中使用。对于不支持序列化的操作(例如与连接交互的操作,即流、数据库客户端等),应使用 sync 作业。

作业变量和引用

sync(
    new SomeAction(),
    context: 'public',
    role: variable('role'),
    userId: response('user', 'id'),
);

对于上面的代码,参数 context 将“按原样”(public)传递给 SomeAction,参数 roleuserId 将动态提供。当运行工作流时,这些参数将与在 SomeAction主方法 中定义的参数匹配。

条件运行

方法 withRunIf 允许传递类型为 变量响应 的参数,以有条件地运行作业。

sync(
    new CompressImage(),
    file: variable('file')
)
    ->withRunIf(
        variable('compressImage'),
        response('SomeAction', 'doImageCompress')
    )

对于上面的代码,所有条件都必须满足才能运行作业,并且变量 compressImage 和引用 SomeAction:doImageCompress 必须为 true 才能运行作业。

依赖关系

使用 withDepends 方法显式声明先前作业作为依赖项。依赖作业不会运行,直到依赖项得到解决。

job(new SomeAction())
    ->withDepends('myJob');

运行工作流

要运行工作流,请使用 run 函数,并传递一个工作流及其变量的 array(如果有)。

use function Chevere\Workflow\run;

$run = run($workflow, ...$variables);

使用 getResponse 获取作业响应作为 CastArgument 对象,该对象可以用于获取类型化响应。

$string = $run->getResponse('myJob')->string();

代码示例

你好,世界

运行实时示例:php demo/hello-world.php Rodolfo - 查看源代码

基本示例工作流定义了对给定用户名的问候。作业 greet 是一个命名参数,它接受 GreetAction 及其 主方法 参数。使用 run 函数执行工作流。

use Chevere\Demo\Actions\Greet;
use function Chevere\Workflow\run;
use function Chevere\Workflow\sync;
use function Chevere\Workflow\variable;
use function Chevere\Workflow\workflow;

$workflow = workflow(
    greet: sync(
        new Greet(),
        username: variable('username'),
    ),
);
$run = run(
    $workflow,
    username: $argv[1] ?? 'World'
);
$greet = $run->getReturn('greet')->string();
echo <<<PLAIN
{$greet}

PLAIN;

该工作流程的图表只包含 greet 任务。

$workflow->jobs()->graph()->toArray();
// contains
[
    ['greet'],
];

图像缩放示例(异步)

运行实时示例:php demo/image-resize.php - 查看源码

对于此示例,工作流程定义了两种图像尺寸的缩放过程。所有任务都被定义为异步,但由于任务之间存在依赖关系(参见 variableresponse),系统将解析一个合适的运行策略。

use Chevere\Demo\Actions\ImageResize;
use Chevere\Demo\Actions\StoreFile;
use function Chevere\Workflow\async;
use function Chevere\Workflow\response;
use function Chevere\Workflow\run;
use function Chevere\Workflow\variable;
use function Chevere\Workflow\workflow;

$workflow = workflow(
    thumb: async(
        new ImageResize(),
        file: variable('image'),
        fit: 'thumbnail',
    ),
    poster: async(
        new ImageResize(),
        file: variable('image'),
        fit: 'poster',
    ),
    storeThumb: async(
        new StoreFile(),
        file: response('thumb'),
        dir: variable('saveDir'),
    ),
    storePoster: async(
        new StoreFile(),
        file: response('poster'),
        dir: variable('saveDir'),
    )
);
$run = run(
    $workflow,
    image: __DIR__ . '/src/php.jpeg',
    saveDir: __DIR__ . '/src/output/',
);

上述工作流程的图表显示,thumbposter 运行异步,就像 storeThumbstorePoster,但 store* 任务在解决第一个依赖级别后才运行。

$workflow->jobs()->graph()->toArray();
// contains
[
    ['thumb', 'poster'],
    ['storeThumb', 'storePoster']
];

使用函数 run 来运行工作流程,变量作为命名参数传递。

use function Chevere\Workflow\run;

$run = run(
    $workflow,
    image: '/path/to/image-to-upload.png',
    savePath: '/path/to/storage/'
);

使用 getReturn 来检索作业响应作为 CastArgument 对象,可以用来获取类型化响应。

$thumbFile = $run->getReturn('thumb')->string();

同步与异步

运行实时示例:php demo/sync-vs-async.php - 查看源码

对于此示例,您可以比较同步和异步作业的执行时间。示例使用 FetchUrl 动作获取三个 URL 的内容。

use Chevere\Demo\Actions\FetchUrl;
use function Chevere\Workflow\async;
use function Chevere\Workflow\run;
use function Chevere\Workflow\sync;
use function Chevere\Workflow\variable;
use function Chevere\Workflow\workflow;

$sync = workflow(
    php: sync(
        new FetchUrl(),
        url: variable('php'),
    ),
    github: sync(
        new FetchUrl(),
        url: variable('github'),
    ),
    chevere: sync(
        new FetchUrl(),
        url: variable('chevere'),
    ),
);
$async = workflow(
    php: async(
        new FetchUrl(),
        url: variable('php'),
    ),
    github: async(
        new FetchUrl(),
        url: variable('github'),
    ),
    chevere: async(
        new FetchUrl(),
        url: variable('chevere'),
    ),
);
$variables = [
    'php' => 'https://php.ac.cn',
    'github' => 'https://github.com/chevere/workflow',
    'chevere' => 'https://chevere.org',
];
$time = microtime(true);
$run = run($sync, ...$variables);
$time = microtime(true) - $time;
echo "Time sync: {$time}\n";
$time = microtime(true);
$run = run($async, ...$variables);
$time = microtime(true) - $time;
echo "Time async: {$time}\n";

在运行同步(阻塞)作业时,执行时间高于异步(非阻塞)作业。这是因为异步作业并行运行。

Time sync: 2.5507028102875
Time async: 1.5810508728027

条件性作业

运行实时示例:php demo/run-if.php - 查看源码

对于此示例,工作流程定义了对给定用户名的问候,但只有当 sayHello 变量设置为 true 时。

use Chevere\Demo\Actions\Greet;
use function Chevere\Workflow\run;
use function Chevere\Workflow\sync;
use function Chevere\Workflow\variable;
use function Chevere\Workflow\workflow;

/*
php demo/run-if.php Rodolfo
php demo/run-if.php
*/

$workflow = workflow(
    greet: sync(
        new Greet(),
        username: variable('username'),
    )->withRunIf(
        variable('sayHello')
    ),
);

方法 withRunIf 接受一个或多个 variableresponse 引用。所有条件必须同时为真,作业才能运行。

调试

当使用此包时,您可能想要调试工作流程以确保作业按预期声明。

要调试工作流程,请检查作业图。它将显示每个执行级别的作业名称及其依赖关系。

$workflow->jobs()->graph()->toArray();
[
    ['job1', 'job2'], // 1st level
    ['job3', 'job4'], // 2nd level
    ['job5'],         // 3rd level
];

对于每个级别,作业将并行运行,但下一级将在前一级解决后运行。

注意

对于运行时调试,强烈建议使用非阻塞调试器如 xrDebug

测试

工作流程检查变量、引用和任何其他配置,因此您不必担心这些问题。

测试工作流程本身是不必要的,因为它只是配置。您需要测试的是工作流程定义及其作业(操作)。

测试工作流程

对于测试工作流程,您需要断言的是预期的工作流程图(执行顺序)。

assertSame(
    $expectedGraph,
    $workflow->jobs()->graph()->toArray()
);

测试作业

对于测试作业,您需要测试的是定义给定作业的操作与操作 main 方法的响应。

$action = new MyAction();
assertSame(
    $expected,
    $action->main(...$arguments)
);

文档

文档可在 chevere.org/packages/workflow 查找。

许可证

版权所有 Rodolfo Berrios A.

本软件根据 Apache License,版本 2.0 许可。有关完整许可文本,请参阅 LICENSE

除非适用法律要求或经书面同意,否则在许可证下分发的软件按“原样”分发,不提供任何明示或暗示的保证或条件。有关许可证的具体语言和限制,请参阅许可证。