NovinkaZverejnili sme 50+ Laravel balíkov ako open source
Custom AI apps, agents and automation — Roundly ConsultingRoundly
Všetky balíky

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ódaZapíš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 only

Kroky

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ódaUzol
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ódaZapíš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íkJSON
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 kryptomien

Odoslaní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.