---
title: "Spúšťače — Workflows for Laravel | Roundly"
description: "Spúšťajte workflowy ručne s overeným vstupom či formulárom, z udalostí aplikácie a Laravelu, podľa cron plánu alebo z podpísaného webhooku."
url: https://roundly-consulting.com/sk/open-source/navody/workflows-for-laravel/spustace
language: sk
---

[Všetky balíky](https://roundly-consulting.com/sk/open-source.md)

[Workflows for Laravel](https://roundly-consulting.com/sk/open-source/navody/workflows-for-laravel.md)

# Spúšťače

Konfigurácia uzla trigger je spúšťačom verzie; pri uložení konceptu sa skopíruje do indexovaných stĺpcov (trigger\_type, trigger\_key):

| Spúšťač | Konfigurácia | Spôsob spustenia | Idempotentný kľúč |
| --- | --- | --- | --- |
| `manual` | inputs: list<{name, type, label?, required?, options?, rules?}> or form: "<forms key>"; optional condition | Workflows::start($key, $subject, $owner)->input(\[...\])->dispatch() | idempotencyKey() volajúceho, inak žiadny. |
| `event` | event: "request.submitted"; optional condition | Workflows::emit(...) or a mapped Laravel event | Kľúč emitu / idempotentný kľúč udalosti. |
| `schedule` | cron: "0 9 \* \* 1-5" or preset: {every, at?, day?, interval?}; timezone?; for\_each? | workflows:wake | schedule:{definition}:{local slot + zone} |
| `webhook` | idempotency: {from: "header:<name>" \| "payload:<path>"}; optional subject: {lookup: "<field>", value: {"ref": "payload.<path>"}}; optional condition | Workflows::triggers()->webhook($hookId, $payload, $headers) or the route | Povinný. |

start() slúži len ručným spúšťačom: pri inom type spúšťača vyhodí TriggerMismatchException, takže ručné volanie nikdy neobíde pravidlá idempotencie či podpisu spúšťača. Každé spustenie odmietne vyradenú definíciu (DefinitionNotActiveException) aj subjekt iného vlastníka (SubjectOwnerMismatchException, nič sa nezapíše).

## Ručné spustenie

```php
$result = Workflows::start('purchase-approval', $request, $team)
    ->input(['reason' => 'New laptops'])
    ->by($user)                          // the triggering user (required for on_behalf_of)
    ->idempotencyKey("submit:{$request->id}")
    ->dispatch();                         // StartResult{instance, created, definition}

$result->created;          // false when the idempotency key was seen before
$result->wasSkipped();     // the trigger condition was false (skipped: condition_false)
$result->wasRefused();     // the start rate limit refused it — send the same start again later
```

```jsonc
{ "trigger": "manual",
  "inputs": [
    { "name": "reason", "type": "string", "label": "Reason", "required": true, "rules": ["max:200"] },
    { "name": "priority", "type": "enum", "options": ["low", "normal", "high"] },
    { "name": "approver", "type": "party", "label": "Approver" } ],
  "condition": { "field": "subject.amount", "op": "gt", "value": "0" } }

// or a forms-for-laravel form
{ "trigger": "manual", "form": "purchase-form" }
```

- Vstupy overuje validátor Laravelu: pravidlá vychádzajú z type, required a options, plus z povoleného zoznamu ďalších rules (max:<n>, min:<n>, email, url; čokoľvek iné vrátane regex sa odmietne). Chyby vyhodia InvalidTriggerInputException (422) s errors().
- Typy vstupov sú typy polí (string, text, integer, decimal, boolean, date, datetime, enum, party, list); options obmedzí hodnoty (každá voľba je jedna hodnota; enum s voľbami ich prijme v akomkoľvek skalárnom type).
- Vstup party prijme kľúč používateľa (alebo model) z parties.user\_model; neznámy kľúč sa odmietne. input.<name> sa číta ako tento používateľ — v podmienke aj ako strana {kind: field, field: input.<name>} (členstvo vo vlastníkovi stále platí).
- Formuláre (prepojenie s forms): form: "purchase-form" overí vstup cez forms-for-laravel. Ak požiadavku odovzdáte cez ->withForm($request, submit: true), uloží sa odoslanie a jeho uuid sa dostane do payloadu spúšťača.
- Opakovaný idempotencyKey vráti prvú inštanciu (created: false). Bez neho každé volanie spustí novú inštanciu. Spustenie, ktoré odmietne limit (limits.max\_starts\_per\_minute), sa vráti s created: false, refused: 'start\_rate\_exceeded' a zlyhanou inštanciou a kľúč nespotrebuje: rovnaké spustenie pošlite znova, keď sa v minúte uvoľní miesto.

## Udalosti

```php
Workflows::emit('purchase_request.submitted', $request, ['amount' => '1200'], idempotencyKey: "sub:{$request->id}", user: $user);
// or on the subject
$request->workflows()->emit('purchase_request.submitted', ['amount' => '1200']);

$result = Workflows::emit('invoice.paid', $invoice);   // EmitResult
$result->started;     // list<StartResult> — one per matching active definition
$result->resumed;     // how many waiting steps it signalled
$result->skipped();   // starts whose trigger condition was false
$result->failed;      // list<EmitFailure{definition, exception}> — definitions that refused to start; the rest still ran

foreach ($result->failed as $failure) {
    $failure->definition;   // the definition key
    $failure->code();       // the exception's errorCode(), e.g. missing_triggering_user
}
```

emit() spustí jednu inštanciu každej aktívnej definície, ktorej aktuálnu verziu spúšťa tento kľúč a ktorá patrí vlastníkovi subjektu (alebo celej aplikácii) a typu subjektu, a signalizuje čakajúcim krokom wait na tento kľúč — len v inštanciách toho istého vlastníka. Definícia, ktorá sa odmietne spustiť (definícia on\_behalf\_of emitovaná bez používateľa, medzitým vyradená definícia), nezastaví ostatné ani čakania: uvedie sa vo failed ako EmitFailure{definition, exception} s code(). Kľúče udalostí zodpovedajú ^\[a-z0-9\_\]+(\\.\[a-z0-9\_\]+)\*$; kľúče začínajúce @ sú interné a odmietnu sa (InvalidEventKeyException).

## Namapované Laravel udalosti

Laravel udalosť namapujete na emit v metóde boot() service providera alebo v konfigurácii:

```php
use RoundlyConsulting\Workflows\Facades\Workflows;

Workflows::triggers()->map(PurchaseRequestSubmitted::class, 'purchase_request.submitted',
    subject: 'request',                 // the event property holding the subject
    user: 'user',                       // … and the triggering user
    payload: ['amount', 'currency'],    // public properties, or one method name returning an array
    idempotencyKey: 'submissionId',     // the property that makes the emit idempotent
);

Workflows::triggers()->listen(InvoiceOverdue::class);   // a self-describing WorkflowEvent
Workflows::triggers()->events();                        // event basename => trigger key
```

```php
// config/workflows.php — the same, as data
'events' => [
    App\Events\RequestSubmitted::class => 'purchase_request.submitted',
    App\Events\RequestApproved::class => ['key' => 'purchase_request.approved', 'subject' => 'request', 'user' => 'approver',
        'payload' => ['amount'], 'idempotency_key' => 'approvalId'],
    App\Events\InvoiceOverdue::class => '*',
],
```

- Payload: zoznam názvov verejných vlastností alebo názov jednej metódy, ktorá vráti pole. Modely v ňom sa zmenia na {id, type} (type = kľúč subjektu v registri, inak krátky názov triedy) — nikdy názov triedy. V podmienkach sa číta ako trigger.payload.<key>.
- Idempotencia: hodnota vlastnosti sa zmení na "{event key}:{value}" (model prispeje svojím kľúčom): tá istá udalosť odoslaná dvakrát spustí jednu inštanciu. Bez nej spustí inštanciu každé odoslanie a workflows:doctor varuje.
- listen() odmietne triedu, ktorá nie je WorkflowEvent (InvalidWorkflowsConfigurationException); map() odmietne '\*' ako kľúč.
- Každá namapovaná trieda dostane jeden listener bez ohľadu na to, koľkokrát je namapovaná. Opätovné namapovanie nahradí pôvodné (kód po konfigurácii, vyhráva posledné) a workflows:doctor duplicitu nahlási. Mapovania z providera žijú v singletone, takže prežijú požiadavky v Octane.
- Listener spracuje udalosť až po commite transakcie aplikácie — odvolané podanie nič nespustí a emit nikdy nedrží zámky riadkov vo vnútri transakcie aplikácie.
- Výsledok emitu namapovanej udalosti nikto nečíta, preto sa definícia, ktorá sa odmietne spustiť, nahlási obsluhe výnimiek (do logu) namiesto zlyhania požiadavky aplikácie.

Udalosť, ktorá sa opisuje sama, implementuje Contracts\\WorkflowEvent a registruje sa cez listen() alebo mapuje na '\*':

```php
use Illuminate\Database\Eloquent\Model;
use RoundlyConsulting\Workflows\Contracts\WorkflowEvent;

final class InvoiceOverdue implements WorkflowEvent
{
    public function __construct(public Invoice $invoice, public int $daysOverdue) {}

    public function workflowKey(): string { return 'invoice.overdue'; }
    public function workflowSubject(): ?Model { return $this->invoice; }
    public function workflowPayload(): array { return ['days_overdue' => $this->daysOverdue]; }
    public function workflowUser(): ?Model { return null; }
    public function workflowIdempotencyKey(): ?string { return "{$this->invoice->id}:{$this->daysOverdue}"; }
}
```

## Plány

```jsonc
{ "trigger": "schedule", "cron": "0 9 * * 1-5", "timezone": "Europe/Bratislava" }

{ "trigger": "schedule", "preset": { "every": "weekday", "at": "09:00" } }

// one instance per matching subject each slot
{ "trigger": "schedule", "cron": "0 9 * * 1-5",
  "for_each": { "where": { "field": "subject.status", "op": "equals", "value": "open" }, "limit": 200 } }
```

```php
Trigger::schedule('0 9 * * 1-5')->forEach(When::field('subject.status')->equals('open'), limit: 200);
Trigger::schedule(preset: ['every' => 'month', 'at' => '08:00', 'day' => 1], timezone: 'Europe/Bratislava');
```

- cron: päť polí; \*, a, a-b, \*/n, a-b/n, zoznamy; JAN–DEC, SUN–SAT; 0 aj 7 sú nedeľa; makrá @hourly, @daily, @weekly, @monthly, @yearly. Ak je obmedzený deň v mesiaci aj deň v týždni, platí sémantika Vixie. L, W, #, ?, sekundy a roky sa odmietnu (invalid\_cron).
- preset: {every: day | weekday | week | month | hour | minutes, at: "09:00", day?, interval?} sa preloží na cron.
- workflows:wake spúšťa splatné termíny v časovom pásme spúšťača. Zmeškané termíny v okne schedule.catch\_up (PT1H) sa zlúčia do jedného spustenia. Opakovaná hodina pri prechode času beží raz; vynechaná hodina nebeží.
- Plánované spúšťače nemôžu bežať ako on\_behalf\_of (actor\_requires\_user).
- for\_each na definícii s typom subjektu spustí pri každom termíne jednu inštanciu pre každý subjekt: každý subjekt tohto typu (pri definícii s vlastníkom len jeho), ktorý spĺňa where, najviac limit — a nikdy viac než wake.batch — na termín. Každé spustenie má kľúč podľa termínu a subjektu. Subjekty sa čítajú v poradí kľúčov a filtrujú v PHP, v kontexte vlastníka (pri definícii tímu Teams::runAs($team), takže model subjektu viazaný na tím vidí riadky tohto tímu, hoci workflows:wake beží bez kontextu): pri veľkej tabuľke radšej použite spúšťač udalosťou.

## Webhooky

```jsonc
{ "trigger": "webhook",
  "idempotency": { "from": "payload:event.id" },                         // or "header:Idempotency-Key"
  "subject": { "lookup": "invoice_number", "value": { "ref": "payload.invoice" } } }
```

Workflows::triggers()->webhook($hookId, $payload, $headers) spustí definíciu, ktorej aktuálna verzia má webhook spúšťač s týmto id (webhook\_id, vytvorí sa pri prvom publikovaní, obnoví sa cez rotateWebhook()). Idempotentný kľúč je povinný (MissingIdempotencyKeyException, 422) a berie sa z hlavičky alebo cesty v payloade. subject.lookup uvádza registrované pole (nie reláciu), jedinečné v rámci vlastníka, podľa ktorého sa subjekt nájde, a subject.value musí byť {"ref": "payload.<path>"}: hodnota sa prečíta z tela webhooku na danej ceste s bodkami — čítajú sa tu len odkazy payload. a chýbajúca alebo neskalárna hodnota znamená SubjectOwnerMismatchException. Definícia s vlastníkom vezme prvú zhodu, ktorá patrí jej vlastníkovi, a bez zhody nastane SubjectOwnerMismatchException (na route 404).

## Prichádzajúca route (voliteľná)

```php
// config/workflows.php
'webhooks' => [
    'enabled' => 'on',
    'prefix' => 'workflows',
    'middleware' => ['api'],
    'verify_middleware' => ['sentinel.signed:partners'],   // REQUIRED
    'max_bytes' => 262144,
],
```

```php
// POST /workflows/hooks/{hook} — route name workflows.webhooks.start
// 202 {instance, created} · 429 start_rate_exceeded (Retry-After: 60) · 413 payload_too_large · 422 · 404

// The same start from PHP (a queue consumer, a test):
$result = Workflows::triggers()->webhook($hookId, $payload, $headers);

Workflows::definition('invoice-intake', $team)->model()->webhook_id;   // minted at first publish
Workflows::definition('invoice-intake', $team)->rotateWebhook();       // a new hook id
```

POST /workflows/hooks/{hook} (názov route workflows.webhooks.start). Je nastavená bezpečne: ak je zapnutá bez verify\_middleware, route sa vôbec nezaregistruje (workflows:doctor na tom zlyhá). Odpovede: 202 {instance, created}; 429 {code: start\_rate\_exceeded, message, instance, created: false} s Retry-After: 60, keď spustenie odmietol limit (kľúč ostane voľný, takže opakovanie od odosielateľa ho spustí); 413 payload\_too\_large; 422 invalid\_payload (nie je JSON objekt) alebo kód balíka; 404 pre neznámu, vyradenú alebo cudziu definíciu (existenciu nikdy nepotvrdí). So sentinel-for-laravel definujte profil podpisu a nastavte verify\_middleware = \['sentinel.signed:<profile>'\]: nepodpísané požiadavky sa odmietnu ešte pred controllerom.

## 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](https://roundly-consulting.com/sk/podporte-nas.md)

Odoslaním daru súhlasíte s našimi [podmienkami prijímania darov](https://roundly-consulting.com/sk/podmienky-darov.md).

[Podporte našu open-source prácu (otvorí sa v novej karte)](https://donate.stripe.com/dRmeVe8FX5PF1Qd9pXcEw00) [Pridajte sa k nám na Patreone (otvorí sa v novej karte)](https://www.patreon.com/cw/roundly)

## 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.

[Ponuka do 48 hodín](https://roundly-consulting.com/sk/kontakt.md) [Prezrieť všetky balíky](https://roundly-consulting.com/sk/open-source/navody/workflows-for-laravel.md)
