Graf v PHP
Workflows::graph() zapíše v PHP rovnaký dokument roundly.workflows.graph/v1, aký ukladá vizuálny editor — pre seedery, testy, systémové workflowy celej aplikácie a súbory definícií. Dokument len zostaví: validácia ostáva na validate() a publikovaní, takže duplicitné id či chýbajúca hrana sa nahlási tam, nikdy sa neskryje. Je čistý — pod Workflows::fake() prejde a nič nezaznamená.
use RoundlyConsulting\Workflows\Facades\Workflows;
use RoundlyConsulting\Workflows\Graph\Build\{After, Escalate, Party, Stage, Trigger, When};
$graph = Workflows::graph()
->settings(timezone: 'Europe/Bratislava', calendar: 'weekdays')
->trigger('start', Trigger::event('purchase_request.submitted'))
->condition('big', When::field('subject.amount')->gt('50000'))
->approval('legal_cfo', stages: [
Stage::named('Legal')->from(Party::role('legal'))->any(),
Stage::named('CFO')->from(Party::role('cfo'))->any(),
])->escalate(After::businessDays(2), Escalate::reassign(Party::field('subject.approver_manager')))
->end('done')
->connect('start', 'big')
->connect('big', 'legal_cfo', on: 'true')
->connect('big', 'done', on: 'false')
->connect('legal_cfo', 'done', on: ['approved', 'rejected', 'expired']);
Workflows::definition('purchase-approval', $team)->edit($graph); // or $graph->toArray() / json_encode($graph)
$graph->validate(subjectType: 'purchase_request', owner: $team); // the same ValidationReport as versions()->validate()Všetko, čo prijíma pole s grafom, prijme aj zostavovač: DefinitionHandle::edit(), VersionsAccessor::validate() a VersionsAccessor::dryRun(). GraphBuilder je Macroable, Arrayable, JsonSerializable a Stringable (jeho JSON). toArray() vždy začína "schema": "roundly.workflows.graph/v1", zachová poradie uzlov a vynechá prázdne settings (ak ich nemal dokument, z ktorého bol načítaný).
Dokument
| Metóda | Zapíše |
|---|---|
settings(?string $actor, ?string $timezone, ?string $calendar, ?int $maxSteps, ?RetryPolicy $retry) | settings.{actor, timezone, calendar, limits.max_steps, retry} — zlúčené; argumenty null kľúče vynechajú. |
node(string $id, string $type, array $config = []) | Ľubovoľný uzol — pre vlastné typy krokov. |
remove(string $id) | Odstráni uzol a všetky hrany, ktoré sa ho dotýkajú. |
GraphBuilder::from(array|WorkflowVersion $graph) | Načíta dokument (alebo graf verzie) na úpravu; from($g)->toArray() sa obsahom rovná $g. |
use RoundlyConsulting\Workflows\Graph\Build\{After, GraphBuilder, Ref};
$graph = GraphBuilder::from($definition->currentVersion) // or an array; from($g)->toArray() equals $g by content
->remove('notify_legal') // removes the node and every edge touching it
->setField('mark', 'subject.priority', Ref::input('priority'))
->retry(5, [10, 60, 300]) // modifiers apply to the last node added
->ui(320, 80, 'Mark priority')
->delay('cool_off', After::hours(4))
->chain('mark', 'cool_off', 'done'); // outcome-less edges: next onlyKroky
Každá metóda pridá uzol a urobí z neho „posledný uzol“ pre modifikátory. approval() a notify() sú obyčajná konfigurácia — typ je dostupný, keď je nainštalovaný jeho balík:
| Metóda | Uzol |
|---|---|
trigger($id, Trigger $trigger) | {"type": "trigger", "config": …} |
condition($id, When $when) | condition — výsledky true / false |
branch($id, array<string, When> $cases, string $default = 'default') | branch — cases: [{outcome, when}], default |
setField($id, string $field, mixed $value) | set_field — {field, value} (literál alebo Ref) |
transition($id, string $transition, ?string $lifecycle, ?string $reason) | transition |
delay($id, After|CarbonInterface|string $for) | delay — for (After alebo ISO trvanie ako PT2H) alebo until (okamih) |
delayUntilField($id, string $field, ?After $offset) | delay — {until_field, offset?} |
waitForEvent($id, string $event, string $match = 'subject', ?After $timeout, ?When $where) | wait — {event, match?, timeout?, where?} |
waitUntil($id, When $when, ?After $poll, ?After $timeout) | wait — {condition, poll?, timeout?} |
loop($id, int $max) | loop |
startWorkflow($id, string $definition, array|string|null $subject = 'same', array $input = [], bool $wait = false) | start_workflow — pri same sa subject vynechá |
end($id, ?string $status, ?string $reason) | end (bez konfigurácie, ak chýba status / reason) |
approval($id, list<Party> $approvers = [], string $rule = 'any', list<Stage> $stages = [], ?After $expiresIn, ?string $preset) | approval — {approvers, rule} (jedna fáza), stages, preset, expires_in |
notify($id, string $notification, Party|list<Party> $to, array $data = []) | notify — {notification, recipients, data?} |
Modifikátory a hrany
Modifikátor použitý pred prvým uzlom vyhodí LogicException (chyba vývojára, nie doménová výnimka):
| Metóda | Zapíše |
|---|---|
retry(int $maxAttempts, ?list<int> $backoff) | retry: {max_attempts, backoff?} |
escalate(After $after, Escalate ...$actions) | Pridá escalations[]: {after, actions} — jedna úroveň na volanie. |
ui(int $x, int $y, ?string $label) | ui: {x, y, label?} — engine ho nikdy nečíta. |
connect(string $from, string $to, string|list<string>|null $on = null) | Jedna hrana, alebo jedna na každý výsledok zo zoznamu. |
chain(string ...$ids) | Hrany bez výsledku pozdĺž zoznamu — znamenajú next, takže uzol s iným výsledkom spojte cez connect(…, on: …). |
Hodnoty
| Pomocník | JSON |
|---|---|
When::field('subject.amount')->gt('50000') | {"field": "subject.amount", "op": "gt", "value": "50000"} |
->equals(v), ->in([…]), ->lt(v), ->between(a, b), ->isEmpty(), ->contains(v, insensitive: true), ->before(v), ->after(v) | Operátory podmienok (between → [a, b], isEmpty → op: empty bez hodnoty, insensitive → case: insensitive). |
When::all(…), When::any(…), When::not(When) | {"all": […]}, {"any": […]}, {"not": {…}} |
Ref::subject('amount'), Ref::owner('plan'), Ref::input('reason'), Ref::trigger('payload.amount'), Ref::step('approve', 'request_id'), Ref::now(), Ref::path('…') | {"ref": "subject.amount"}, …, {"ref": "steps.approve.output.request_id"}, {"ref": "now"} |
Ref::relative(After::days(-7)) | {"relative": {"amount": -7, "unit": "days"}} |
Party::role('legal'), Party::field('subject.approver'), Party::user($user|7), Party::triggeredBy() | {"kind": "role", "role": "legal"}, {"kind": "field", …}, {"kind": "user", "id": 7}, {"kind": "triggered_by"} |
After::minutes|hours|days|weeks|months(n) | {"amount": n, "unit": "days"} |
After::businessMinutes|businessHours|businessDays(n, ?string $calendar) | {"amount": n, "unit": "business_days", "calendar"?: …} |
After::iso('P2D') | "P2D" |
Trigger::manual(Input ...$inputs), Trigger::form('purchase') | {"trigger": "manual", "inputs"?: […]}, {"trigger": "manual", "form": "purchase"} |
Trigger::event('doc.signed', ?When $condition) | {"trigger": "event", "event": …, "condition"?: …} |
Trigger::schedule(?string $cron, ?array $preset, ?string $timezone), Trigger::webhook(?string $idempotencyFrom) | {"trigger": "schedule", …}, {"trigger": "webhook", "idempotency"?: {"from": …}} |
->when(When $condition), ->forEach(?When $where, ?int $limit) | Pridá condition ku ktorémukoľvek spúšťaču; pridá for_each k plánu. |
Input::string|integer|decimal|date($name, ?label, required, rules), Input::boolean($name, ?label, required), Input::choice($name, $options, ?label, required) | {"name", "type", "label"?, "required"?, "options"?, "rules"?} (choice → type: enum) |
Stage::named('Legal')->from(Party ...)->any()|unanimous()|quorum(n)|weighted(n) | {"name", "approvers", "rule", "quorum"?, "required"?} |
Escalate::reassign(Party ...), Escalate::notify('purchase.escalated', Party ...), Escalate::startWorkflow('chase') | {"type": "reassign", "to": […]}, {"type": "notify", "notification", "recipients"}, {"type": "start_workflow", "definition"} |
Hodnoty konfigurácie sa zapisujú ako čistý JSON: hodnoty zostavovača sa zmenia na polia, dátumy na reťazce ISO-8601, enumy na ich hodnotu a modely na ich kľúč. Každý pomocník sa vie vykresliť sám — When, Ref, Party, Trigger, Input, Stage a Escalate majú toArray(), After má toValue() a Party::list() vykreslí zoznam strán — takže ich môžete použiť v node($id, $type, $config) pri konfigurácii vlastných krokov.
Makrá aplikácie
use RoundlyConsulting\Workflows\Graph\Build\{GraphBuilder, Party, Trigger};
GraphBuilder::macro('task', fn (string $id, string $title) => $this->node($id, 'acme.create_task', ['title' => $title]));
Workflows::graph()->trigger('start', Trigger::manual())->task('order', 'Order')->end('done')->chain('start', 'order', 'done');
// Host step configs take the value helpers too — they are rendered for you:
Workflows::graph()->node('assign', 'acme.create_task', ['title' => 'Review', 'assignee' => Party::field('subject.owner')]);Prejavte lásku k open source
Tento balík je zadarmo pod licenciou MIT. Ak vám šetrí čas, jednorazový príspevok alebo členstvo na Patreone nám pomôže ho ďalej udržiavať, testovať a dokumentovať.
Ďalšie spôsoby podpory vrátane kryptomienOdoslaním daru súhlasíte s našimi podmienkami prijímania darov.
Chcete to zabudovať do svojho produktu?
Naše balíky integrujeme do zákazkových Laravel a AI riešení. Napíšte nám, na čom pracujete, a ozveme sa do 48 hodín.