jeeflow工作流引擎-php版
| 文件 | 最后提交记录 | 最后更新时间 |
|---|---|---|
ci(verify): 修首跑判红——安装事实改读 composer.lock(composer show 列格式把 awk 掏出一个冒号) | 16 天前 | |
feat(facade,pdo,memory): issues/152 ②③ 委托分页归属不变式两层 + 授权人三档归一(未发版) - surrogatePage 注入 query->add('t.operator','EQ',operatorOf(args)),与 instancePage 同形(§2.5 归一,缺省 user1) - PdoProcessExtRepository::buildSurrogateConditions 归属列 EQ 空值 ⇒ AND 1=0;InMemoryProcessExtRepository 同判据 (条款 6 双仓同答案)。两处都只收 t.operator + EQ,m_ 可选过滤的"空值当没填"通用放行原样保留 - applySurrogateFields 只认非空白显式值:空串/全空白不落进 operator(死行),save 走归一缺省、update 保留原授权人 - 用例:档1 主用例 + 兜底腿、档4 三档归一 + 阳性对照(显式值仍胜、update 空白保留原人)、档5 门面注入 + 直调两仓空归属⇒空页; MysqlSmoke / JeeflowFacadeExt 的既有用例按新注入补 operator 实参,断言值一字未改 - 改正 PdoSqliteOwnershipBlankConditionTest 的 t.operator 档(原断言 2 行全库 ⇒ 现断言空页), 逐字交代写在该文件类注释里;同用例的 process_name 空值放行与真值档保留,证明只收归属列 - 主会话接手两处:新用例夹具原用 01-simple(发起后停在 leader 的 task1,往 user1 名下配委托永不命中) ⇒ 改 02-multi-task 的 apply 单(参与者=applicant=user1),三条红转绿;并补做阳性对照: 两道兜底改成 if (false && …) ⇒ 档5 报 2 failures,按字节还原后复绿 - T0 读数(主会话复跑):core 317 OK / repository-pdo 114 OK(37 skipped) / web-contract 171 OK / web-psr 9 OK / persist 25 OK;mysql-smoke 腿需 160 真库,归 T1 轮 | 1 天前 | |
feat(core,parser,handler): issues/142 A 批 从零补齐 custom 记录类一档(CustomModel+CustomNodeParser+按名注册表+历史行真落库) php 这一栈不是"形状偏了",是这条能力整个没有:ModelParser 只注册七档、无 custom 档 ⇒ snaker:custom 节点连同出边在解析期被 error_log+continue 吞掉。比普查写的更糟一条实测: 被丢节点的入边 target 为 null,TransitionModel.php:33 直接 $this->target->execute() ⇒ 办理动作硬崩(Call to a member function execute() on null),"记日志再跳过"其实跳不干净。 补齐后按 §6.2 三条落: - 新增 CustomModel/CustomNodeParser/CustomHandlerInterface/CustomHandlerRegistry (注册表沿用本栈 AssignmentHandlerRegistry 的形状挂在既有 ServiceContext 定位器上,不另起容器); ModelParser 只多注册一档,**查表方式一字未改、未做大小写归一与别名**(G4 义务 1/3 另批); - 历史行走独立列表(Execution.historyTasks)→ persistHistoryTasks 覆盖发起+办理两条漏斗 ⇒ **只 saveTask 发 INSERT,不进 processTaskList**:不发码 3(记录类出生即 20,混进去=广播假待办)、 不过 applySurrogate(别把代理人并进留痕行的参与者)、不出现在 executeProcessTask 返回值里; 子流程腿(EndProcessHandler)同样把父实例临时 exec 的历史行上收; - clazz 两档日志分别可诊断(空串/缺失 一档带 nodeId;非空未注册 一档带 clazz 原串+注册姿势), 两条都照常落行续流;处理器自身抛错、以及 methodName 指向不存在的方法 ⇒ 照旧外抛(后者 java 逐字同文案, 不在豁免面);args 逗号 key 从执行变量取实参、缺失档保留 null 位置(同 java), 返回值按 val/缺省 custom_return_val 落变量(那个零调用者常量本单接上)。 任务类腿**实读确认本来就合规**(CreateTaskHandler 无 actors 判空、ProcessTask 直收空集、PDO 侧任务行照 INSERT), 未动,只加一格 testTaskLegStillCreatesRowWithZeroActors 当回归钉(该格改前也绿)。 测试:新增 16 格(Core 14 + PDO/SQLite 真表 2,含"第二个仓储实例复用同连接再读"=证据在库里不在对象里、 待办只 audit 一条、expire_time/form_key/task_type 均 NULL 不造默认值)。 逐处还原实测改前会红:custom 档摘回未知类型 15 红(含 10 处 execute() on null 硬崩)/ 落库通道摘掉 9 红/两档还原成抛错 4 红。 既有格一条按裁定删除:UnknownNodeTypeDiagnosisTest::testCustomNodeInSharedFixtureIsDiagnosed ——它断言的正是"夹具里的 custom1 不进模型、只出未知档诊断"(注释还写着"本轮不新增 custom 档(owner 明确)", 那是 G4 C 方案当时的边界),与本单裁定正面互斥、且同方法内两条断言在摘掉第一条后仍会红; 义务 2 的诊断覆盖由同文件另两格继续守着,custom 的新形状由 CustomNodeRecordLegTest 反向钉住。 phpunit 两种口径 518 格 0 红(T0 Skipped 44/全跑 Skipped 37;基线 503)。 ProcessRepositoryInterface 零改动 ⇒ 上一轮 G2 那两个必选方法的致命账没有加重。 | 10 天前 | |
fix(core): issues/126 案 A 任务行 expire_time 五处写点按节点表达式真算(基准=boot2) 本栈原先只在**实例**级搬一次原串,且注释自承"不处理变量替换";任务行五处写点一个都没有, 也没有表达式求值器(§1.5 普查)。 - packages/core/src/Util/FlowUtil.php:50 新增 processTime() 三档(变量档→毫秒/日期串, 类型不认识落穿;Ns/Nm/Nh/Nd,d 走日历加不乘 86400;绝对档严格 Y-m-d H:i:s;失败 null, 任何分支不返回 now());:118 前缀非整数按 §1.9-3 走"落穿→null"(与 Java 抛 NumberFormatException 是故意差异:配置写错不该让建单卡死),注释里写明 - Domain/ProcessInstance.php:154 applyExpireTime / :169 applyNodeExpireTime(绕道路径的公开入口), 写点 :191 普通建单 / :227 串行首位 / :239 并行全员 / :299 回退新建(随行 vars); Handler/CountersignHandler.php:163 第五处=串行推进出的下一位成员; Handler/CreateTaskHandler.php 把节点表达式交给 helper(跳转新建走这条) - tests/Core/ExpireTimeOnCreateTest.php 六格:同行 expire−create≈2h、变量名档、未配两态、 解析不出、并行全员、回退新建;负向格把"行没读到"与"值为空"分开断 - SharedFlowParseTest 计数 15→16:新夹具 06-countersign-sequential-expire.json 进共享 flows, 注释按该测试既有惯例写明是谁加的、为什么(唯一一处既有断言改动,属台账计数不属判据) 三场景:正向 / 负向 / 回归 phpunit 全量 **Tests: 394, Assertions: 2935, Failures: 0, Skipped: 37** (37 skip 是改前基线同数,env 依赖)。变异对照两层都有牙:①求值换成 date('Y-m-d H:i:s') ⇒ 新件 6 跑 4 红("实得 0s"、"变量档期望 2026-12-31 10:00:00 实得 now");②摘掉第五处写点 ⇒ "推进新建的 第二成员 必须带到期时间 Failed asserting that null is not null",首成员格仍绿=红可归因。 两次均按 sha256 还原核对(ProcessInstance 74a99767… / CountersignHandler c6a825ec… == 变异前备份), grep MUTATION 零残留。未跑:T1(37 skip 沿用基线)、L2-27 跨栈门禁、镜像轮。 实例级 expire_time(JeeflowEngine.php:106 原串直搬)按 §1.9-2 只报不改。 | 11 天前 | |
feat(web-contract): issues/153·154·155 php 侧四条分叉收口(模型腿补全 + label 并派 + 九键 id + 视图端点不报错) 1) highLight 补整条模型腿:collectPath/evalDecisionExpr 逐字照 jeeflow-java 与 go facade.go:1231-1270 (start 沿 outputs 递归、遇活跃节点停止、visited 引用传递=java 跨分支共享 HashSet、false 边的边名 与目标节点都不收)。表达式口复用引擎那一句 ServiceContext::find(ExpressionEvaluatorInterface::class) (与 DecisionModel.php:40 同一个口、同一把尺子 === true),SPI 未注册才整档 false(降级档,注释写明)。 实测踩点记档:$visited 传字面量 [] 会因引用传参抛 catchable Error,被既有 catch(\Throwable) 吞掉 ⇒ 模型腿静默不执行而 nodeProgress 照常出口(看着像只有边腿坏),已改真变量并由新用例照出。 2) getAssigneeTextData 的 label 并派七栈形状 displayName:actorId(删 realName ?: actorId 异类)。 3) approvalRecord 补首列 id(显式 (string))成九键;实例不存在改出空数组; 取数路径就地 usort 成 id ASC(compareNumericId,不 (int) 强转防 19 位溢出),未动聚合根共享方法。 ccList 默认分页现读已是 1/10、ext 无实例变量回落——两条本栈本就合规,只加用例钉住。 4) 未动 highLight 的实例校验(spec ② 只对 approvalRecord 立法,不擅自扩面)。 判据:新增 8 条用例(决策低/高两支边集恰为 true 边、模型腿补 end、无 SPI 降级档、九键+id 字符串+ ext 不回退、id ASC 用 setTasks(array_reverse) 造坏形状、not-found 空数组、label 用 userId 需注册 realName 的 Provider 才有判别力)。既有 :410 那条浅判据未动,测试文件 diff 为 288 增 / 0 删=零期望值改动。 读数:T0 改前 646/4697/Skipped47 → 改后 654/4778/Skipped47 OK(主会话独立复跑一次同读数); T1 连 160 REQUIRE_MYSQL=1 两次均 654 OK。 变异对照五挂一不挂:恒 true 求值/摘 id 列/摘模型腿/摘排序与方向打反/label 回退 realName 全红; 唯 "id 出 number" 不红——flow() 边界那道 issues/75 全局 stringifyIds(isIdKey('id')) 会把 int 转回串, 该判据在这一层无判别力,显式 (string) 是引擎侧自守,已把这层实情写进注释而非伪造判别。 | 5 小时前 | |
feat(web-contract): issues/153·154·155 php 侧四条分叉收口(模型腿补全 + label 并派 + 九键 id + 视图端点不报错) 1) highLight 补整条模型腿:collectPath/evalDecisionExpr 逐字照 jeeflow-java 与 go facade.go:1231-1270 (start 沿 outputs 递归、遇活跃节点停止、visited 引用传递=java 跨分支共享 HashSet、false 边的边名 与目标节点都不收)。表达式口复用引擎那一句 ServiceContext::find(ExpressionEvaluatorInterface::class) (与 DecisionModel.php:40 同一个口、同一把尺子 === true),SPI 未注册才整档 false(降级档,注释写明)。 实测踩点记档:$visited 传字面量 [] 会因引用传参抛 catchable Error,被既有 catch(\Throwable) 吞掉 ⇒ 模型腿静默不执行而 nodeProgress 照常出口(看着像只有边腿坏),已改真变量并由新用例照出。 2) getAssigneeTextData 的 label 并派七栈形状 displayName:actorId(删 realName ?: actorId 异类)。 3) approvalRecord 补首列 id(显式 (string))成九键;实例不存在改出空数组; 取数路径就地 usort 成 id ASC(compareNumericId,不 (int) 强转防 19 位溢出),未动聚合根共享方法。 ccList 默认分页现读已是 1/10、ext 无实例变量回落——两条本栈本就合规,只加用例钉住。 4) 未动 highLight 的实例校验(spec ② 只对 approvalRecord 立法,不擅自扩面)。 判据:新增 8 条用例(决策低/高两支边集恰为 true 边、模型腿补 end、无 SPI 降级档、九键+id 字符串+ ext 不回退、id ASC 用 setTasks(array_reverse) 造坏形状、not-found 空数组、label 用 userId 需注册 realName 的 Provider 才有判别力)。既有 :410 那条浅判据未动,测试文件 diff 为 288 增 / 0 删=零期望值改动。 读数:T0 改前 646/4697/Skipped47 → 改后 654/4778/Skipped47 OK(主会话独立复跑一次同读数); T1 连 160 REQUIRE_MYSQL=1 两次均 654 OK。 变异对照五挂一不挂:恒 true 求值/摘 id 列/摘模型腿/摘排序与方向打反/label 回退 realName 全红; 唯 "id 出 number" 不红——flow() 边界那道 issues/75 全局 stringifyIds(isIdKey('id')) 会把 int 转回串, 该判据在这一层无判别力,显式 (string) 是引擎侧自守,已把这层实情写进注释而非伪造判别。 | 5 小时前 | |
refactor(flows): 流程定义解耦 java 仓,本仓自带 flows/ 副本 + 执行期精确镜像 - 新增 flows/(15 个 JSON 入库副本)+ jeeflow-flows-dir.php(仓根 resolver,composer psr-4 只发 packages/*,不打包)+ .dockerignore - 读路径只认本仓 flows/;java 兄弟目录存在时执行期全量镜像(复制+删孤儿) - 18 文件 30 处路径改用 jeeflow_flows_dir();新增 tests/bootstrap.php;Dockerfile.demo/CI 改单仓上下文 - docs 同步(15 个 + resolver 说明 + 会签段 issues/91 回灌) - 单语言用户下载即用,无需 checkout jeeflow-java | 1 个月前 | |
feat(repository-pdo): §3-10 步骤③ schema-mysql.sql 副本改为编辑源逐字分发件+补 .gitattributes - 副本由手写 VARCHAR(64) 版换成 jeeflow-java 编辑源的逐字分发件:15 个 BIGINT id 列、14 条二级索引、 8 条表级 COMMENT、content 由 TEXT 变 BLOB 一起带入;与编辑源 diff 0 行(剥 CR 后逐字相同,与其余七栈同形) - 原副本首行那 58 行差异说明删除(差异已不存在),其中被实测确认为真的三条(IF NOT EXISTS 静默不生效、 1366 报错码、引擎侧 string 绑定/strId 归正/stringifyIds 三处等价)迁进 scripts/sync-schema.sh 的纳入注释留档 - 首行"对齐 Java schema-mysql.sql"这句自 08-11 起假了 50 天(09-30 才诚实化标注),本轮起变成真的 - 本仓此前无 .gitattributes,按 rust/csharp 同形补 *.sql text eol=lf(只加这一条,不动其它行尾规则); 现读两份 .sql 的 blob CR 字节数均为 0 ⇒ 不触发重规范化、零行尾漂移 - gate:分发幂等复跑后其余七仓 git status 全干净、只有本仓副本按预期变更; REQUIRE_MYSQL=1 全量 640 tests 4832 assertions Skipped 0 | 7 天前 | |
feat: SQLite default + RepositoryFactory for demo persistence - Add PdoProcessExtRepository (design/surrogate tables) - Add RepositoryFactory (env: sqlite/mysql/memory) - Add bin/demo-init.php (idempotent schema + flow deploy) - Rewrite public/index.php to use RepositoryFactory - HTTP cross-request persistence verified (A→B→C→D) | 1 个月前 | |
refactor(flows): 流程定义解耦 java 仓,本仓自带 flows/ 副本 + 执行期精确镜像 - 新增 flows/(15 个 JSON 入库副本)+ jeeflow-flows-dir.php(仓根 resolver,composer psr-4 只发 packages/*,不打包)+ .dockerignore - 读路径只认本仓 flows/;java 兄弟目录存在时执行期全量镜像(复制+删孤儿) - 18 文件 30 处路径改用 jeeflow_flows_dir();新增 tests/bootstrap.php;Dockerfile.demo/CI 改单仓上下文 - docs 同步(15 个 + resolver 说明 + 会签段 issues/91 回灌) - 单语言用户下载即用,无需 checkout jeeflow-java | 1 个月前 | |
docs: 补 LICENSE/NOTICE(Apache-2.0, Copyright mldong),composer MIT→Apache-2.0,README 去版本 changelog Co-authored-by: Cursor <cursoragent@cursor.com> | 1 个月前 | |
docs: 补 LICENSE/NOTICE(Apache-2.0, Copyright mldong),composer MIT→Apache-2.0,README 去版本 changelog Co-authored-by: Cursor <cursoragent@cursor.com> | 1 个月前 | |
refactor(flows): 流程定义解耦 java 仓,本仓自带 flows/ 副本 + 执行期精确镜像 - 新增 flows/(15 个 JSON 入库副本)+ jeeflow-flows-dir.php(仓根 resolver,composer psr-4 只发 packages/*,不打包)+ .dockerignore - 读路径只认本仓 flows/;java 兄弟目录存在时执行期全量镜像(复制+删孤儿) - 18 文件 30 处路径改用 jeeflow_flows_dir();新增 tests/bootstrap.php;Dockerfile.demo/CI 改单仓上下文 - docs 同步(15 个 + resolver 说明 + 会签段 issues/91 回灌) - 单语言用户下载即用,无需 checkout jeeflow-java | 1 个月前 | |
docs: 补 LICENSE/NOTICE(Apache-2.0, Copyright mldong),composer MIT→Apache-2.0,README 去版本 changelog Co-authored-by: Cursor <cursoragent@cursor.com> | 1 个月前 | |
refactor(flows): 流程定义解耦 java 仓,本仓自带 flows/ 副本 + 执行期精确镜像 - 新增 flows/(15 个 JSON 入库副本)+ jeeflow-flows-dir.php(仓根 resolver,composer psr-4 只发 packages/*,不打包)+ .dockerignore - 读路径只认本仓 flows/;java 兄弟目录存在时执行期全量镜像(复制+删孤儿) - 18 文件 30 处路径改用 jeeflow_flows_dir();新增 tests/bootstrap.php;Dockerfile.demo/CI 改单仓上下文 - docs 同步(15 个 + resolver 说明 + 会签段 issues/91 回灌) - 单语言用户下载即用,无需 checkout jeeflow-java | 1 个月前 | |
refactor(flows): 流程定义解耦 java 仓,本仓自带 flows/ 副本 + 执行期精确镜像 - 新增 flows/(15 个 JSON 入库副本)+ jeeflow-flows-dir.php(仓根 resolver,composer psr-4 只发 packages/*,不打包)+ .dockerignore - 读路径只认本仓 flows/;java 兄弟目录存在时执行期全量镜像(复制+删孤儿) - 18 文件 30 处路径改用 jeeflow_flows_dir();新增 tests/bootstrap.php;Dockerfile.demo/CI 改单仓上下文 - docs 同步(15 个 + resolver 说明 + 会签段 issues/91 回灌) - 单语言用户下载即用,无需 checkout jeeflow-java | 1 个月前 |
jeeflow-php
jeeflow 工作流引擎 · PHP 实现
jeeflow 是一个零框架依赖的轻量工作流引擎,PHP 版与 Java / Go / Python / Node / Rust 保持行为同构。
模块结构
packages/
├── core/ 引擎聚合根、状态机、流程执行、SPI
├── repository-pdo/ MySQL/PDO 持久化
├── persist/ ARCHIVE/SYNC 写侧(PersistPostInterceptor)
├── web-contract/ 统一门面 Facade、DTO、错误码与契约测试
└── web-psr/ 可选 PSR HTTP adapter
demo-slim/ PHP 引擎参考 demo(Slim 4)
tests/ 测试套件
docs/ 文档
环境要求
- PHP 8.1+
- Composer 2.x
- MySQL 5.7+(PDO 模块)
快速开始
composer install
composer test
对齐事实源
- 引擎语义:
jeeflow-java(Java 是参考实现) - HTTP 契约:
jeeflow-doc/docs/spec/06-facade.md - 共享流程定义:本仓
flows/(唯一编辑源jeeflow-java/jeeflow-core/src/test/resources/flows/,jeeflow-flows-dir.php在维护者机器上执行时把 Java 源精确镜像进本仓,单语言用户下载即用)
已实现能力
- 引擎核心:ProcessInstance 聚合根 + 状态机 + SPI + Handler
- 流程类型:simple / multi-task / decision / fork-join / countersign / reject / withdraw
- 持久化:PdoProcessRepository(MySQL)+ InMemoryProcessRepository(测试)
- 统一门面:JeeflowFacade(与 Java 对齐,含
bizData/candidatePage) - 查询解析:JeeflowQueryParser(m_ 前缀过滤)
- 扩展仓储:IProcessExtRepository
- PSR adapter:JeeflowRequestHandler(PSR-15)
- persist 写侧:ARCHIVE/SYNC(PersistPostInterceptor)
- 参考 demo:demo-slim(Slim 4)
文档
统一规范:jeeflow-doc
License
Copyright © 2025-2026 mldong
Licensed under the Apache License, Version 2.0. See LICENSE and NOTICE.