chevere / workflow
PHP工作流程程序
Requires
- php: ^8.1
- amphp/parallel: ^1.4
- chevere/action: ^1.0.0
- chevere/data-structure: ^1.0.1
- chevere/parameter: ^1.0.1
- chevere/regex: ^1.0.1
- ramsey/uuid: ^4.7
Requires (Dev)
- chevere/filesystem: ^1.0.x-dev
- chevere/var-dump: ^1.0
- phpstan/phpstan: ^1.9
- phpunit/phpunit: ^9.5
- symplify/easy-coding-standard: ^11.1
README
🔔 订阅通讯,不错过任何关于Chevere的更新。
摘要
工作流程是一个可配置的存储过程,可以运行一个或多个作业。作业相互独立,但通过在作业之间传递响应引用而相互关联。作业支持基于变量和先前作业响应的条件执行。
安装
工作流程通过Packagist提供,并存储在chevere/workflow。
composer require chevere/workflow
它做什么?
工作流程包提供基于工作流程模式定义执行过程的工具。其目的是将逻辑指令抽象为相互关联的独立作业单元。
而不是构建一个单体过程,您定义一个由作业组成的工作流程,使开发者能够轻松测试和维护可重用多用途逻辑。
::: tip 💡 工作流程介绍 在Rodolfo的博客中阅读Workflow for PHP,了解此包的全面介绍。 ::
如何使用
工作流程在Chevere\Workflow
命名空间中提供了以下函数。使用这些函数定义工作流程、其变量以及命名作业的响应引用。
- 作业由其Action定义
- 作业相互独立,使用
variable()
函数定义共享变量 - 使用函数
response()
通过引用{作业#A响应} -> {作业#B输入}
使用此包产生逻辑
- 使用
workflow
函数创建工作流程 - 使用
sync
或async
函数定义作业 - 使用
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') ), );
这个工作流的图表表示,所有作业都是使用 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')
声明了一个 响应 引用。
这个工作流的图表表明 thumb
、medium
和 poster
是非阻塞地并行运行的。作业 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
,参数 role
和 userId
将动态提供。当运行工作流时,这些参数将与在 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
- 查看源码
对于此示例,工作流程定义了两种图像尺寸的缩放过程。所有任务都被定义为异步,但由于任务之间存在依赖关系(参见 variable
和 response
),系统将解析一个合适的运行策略。
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/', );
上述工作流程的图表显示,thumb
和 poster
运行异步,就像 storeThumb
和 storePoster
,但 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
接受一个或多个 variable
和 response
引用。所有条件必须同时为真,作业才能运行。
调试
当使用此包时,您可能想要调试工作流程以确保作业按预期声明。
要调试工作流程,请检查作业图。它将显示每个执行级别的作业名称及其依赖关系。
$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。
除非适用法律要求或经书面同意,否则在许可证下分发的软件按“原样”分发,不提供任何明示或暗示的保证或条件。有关许可证的具体语言和限制,请参阅许可证。