
# Sarcini

Sarcinile sunt unitatea fundamentală de lucru în Queuety. Lansați o sarcină cu numele unui gestionar și datele utile, iar un lucrător o preia și o execută în fundal.

## Lansarea sarcinilor

```php
use Queuety\Queuety;

Queuety::dispatch( 'send_email', [ 'to' => 'user@example.com' ] );
```

`dispatch()` returnează un constructor `PendingJob`. Puteți înlănțui opțiuni înainte de lansarea automată:

```php
use Queuety\Enums\Priority;

Queuety::dispatch( 'send_email', [ 'to' => 'user@example.com' ] )
    ->on_queue( 'emails' )
    ->with_priority( Priority::High )
    ->delay( 300 )
    ->max_attempts( 5 );
```

Pentru a obține imediat ID-ul sarcinii, apelați `->id()`:

```php
$job_id = Queuety::dispatch( 'send_email', $payload )->id();
```

## Scrierea gestionarilor

Un gestionar este orice clasă care implementează interfața `Queuety\Handler`:

```php
use Queuety\Handler;

class SendEmailHandler implements Handler {
    public function handle( array $payload ): void {
        wp_mail( $payload['to'], $payload['subject'], $payload['body'] );
    }

    public function config(): array {
        return [
            'queue'           => 'emails',
            'max_attempts'    => 5,
            'backoff'         => 'exponential',
        ];
    }
}
```

Metoda `handle()` primește datele utile ale sarcinii și efectuează lucrarea. Metoda `config()` returnează valori implicite opționale:

| Cheie | Tip | Descriere |
|---|---|---|
| `queue` | `string` | Numele implicit al cozii |
| `max_attempts` | `int` | Numărul maxim de execuții, incluzând prima, înainte ca sarcina să fie trecută în arhivă |
| `backoff` | `string` | Strategia de așteptare progresivă la reîncercare: `exponential`, `linear` sau `fixed` |
| `rate_limit` | `array` | Limită de rată exprimată ca `[max_executions, window_seconds]` |
| `concurrency_group` | `string` | Grup comun de admitere a lucrătorilor pentru gestionare costisitoare |
| `concurrency_limit` | `int` | Numărul maxim de sarcini procesate simultan în cadrul grupului |
| `cost_units` | `int` | Cost relativ de execuție folosit de bugetele ponderate ale cozilor sau furnizorilor, de bugetele fluxurilor de lucru și de euristicile de admitere |

Aceste chei de resurse fac parte din modelul mai amplu Queuety de [Gestionarea resurselor](https://queuety.inline0.dev/ro/docs/features/resource-management), în care metadatele sarcinilor, bugetele fluxurilor de lucru și admiterea lucrătorilor funcționează împreună.

Înregistrați gestionarul:

```php
Queuety::register( 'send_email', SendEmailHandler::class );
```

Sau folosiți [atributele](https://queuety.inline0.dev/ro/docs/features/attributes) PHP pentru înregistrare automată.

## Prioritate

Joburile au patru niveluri de prioritate. Joburile cu prioritate mai mare sunt preluate primele de către workeri.

```php
use Queuety\Enums\Priority;

Queuety::dispatch( 'handler', $payload )->with_priority( Priority::Urgent );
```

| Nivel | Valoare | Descriere |
|---|---|---|
| `Priority::Low` | `0` | Implicită. Lucru de fundal care poate aștepta. |
| `Priority::Normal` | `1` | Prioritate standard. |
| `Priority::High` | `2` | Procesat înaintea nivelurilor Low și Normal. |
| `Priority::Urgent` | `3` | Procesat primul. Folosiți cu moderație. |

## Întârziere

Întârziați un job astfel încât să devină disponibil numai după numărul specificat de secunde:

```php
Queuety::dispatch( 'send_reminder', $payload )->delay( 3600 ); // 1 hour
```

Jobul este inserat imediat, dar workerii nu îl vor prelua până la expirarea întârzierii.

## Joburi unice

Preveniți trimiterea duplicată pentru același handler și același payload. Dacă există deja un job în așteptare sau în curs de procesare cu același handler și payload, este returnat ID-ul jobului existent în loc să fie creat unul nou.

```php
Queuety::dispatch( 'sync_inventory', [ 'sku' => 'ABC-123' ] )->unique();
```

## Dependențe între joburi

Un job poate depinde de un alt job. Un worker nu îl va prelua până când dependența nu este finalizată.

```php
$first_id = Queuety::dispatch( 'import_data', $payload )->id();

Queuety::dispatch( 'send_notification', [ 'import_id' => $first_id ] )
    ->after( $first_id );
```

## Trimiterea în lot

Trimiteți mai multe joburi printr-o singură instrucțiune INSERT cu mai multe rânduri, pentru un debit maxim:

```php
$job_ids = Queuety::batch( [
    [ 'handler' => 'send_email', 'payload' => [ 'to' => 'a@example.com' ] ],
    [ 'handler' => 'send_email', 'payload' => [ 'to' => 'b@example.com' ] ],
    [ 'handler' => 'send_email', 'payload' => [ 'to' => 'c@example.com' ] ],
] );
```

Fiecare element poate include chei opționale: `queue`, `priority`, `delay`, `max_attempts`.

## Reîncercare și întârziere progresivă

Când un job aruncă o excepție, Queuety îl readuce în `pending` cu o `available_at` întârziată, până când a fost executat de `max_attempts` ori în total (prima execuție se numără, așadar `max_attempts( 3 )` înseamnă cel mult două reîncercări). Întârzierea dinaintea fiecărei reîncercări este stabilită de strategia de întârziere progresivă, unde `attempt` este numărul execuției tocmai eșuate:

| Strategie | Formula întârzierii | Exemplu (după execuțiile 1, 2, 3) |
|---|---|---|
| `exponential` | `2^attempt * 30` secunde, plafonată la 3600 | 60s, 120s, 240s |
| `linear` | `attempt * 60` secunde | 60s, 120s, 180s |
| `fixed` | `60` secunde | 60s, 60s, 60s |

Setați valoarea implicită globală în `wp-config.php`:

```php
define( 'QUEUETY_RETRY_BACKOFF', 'linear' );
```

Sau per handler în `config()`:

```php
public function config(): array {
    return [ 'backoff' => 'exponential', 'max_attempts' => 5 ];
}
```

Sau la fiecare trimitere:

```php
Queuety::dispatch( 'handler', $payload )->max_attempts( 10 );
```

## Sarcini în carantină

După epuizarea tuturor încercărilor de reîncercare, un job este marcat ca **îngropat**. Joburile îngropate rămân în baza de date pentru examinare, dar nu sunt procesate. Le puteți reîncerca manual. O reîncercare manuală face jobul să fie din nou `pending` fără a reseta numărul de încercări, astfel că un job îngropat reîncercat mai primește o execuție și este îngropat din nou dacă aceasta eșuează:

```php
// Retry a specific buried job
Queuety::retry( $job_id );

// Retry all buried jobs
$count = Queuety::retry_buried();
```

Sau prin CLI:

```bash
wp queuety retry 42
wp queuety retry-buried
```

## Expirarea timpului

Queuety pornește o `pcntl_alarm()` pentru timpul maxim de execuție și ar trebui să facă încercarea să eșueze cu o `TimeoutException` dacă limita este depășită. Această limită nu este aplicată momentan ([#10](https://github.com/inline0/queuety/issues/10)): workerul nu activează semnalele asincrone, așadar un handler cu execuție îndelungată rulează până la capăt, iar un handler blocat în `sleep()` este oprit când se declanșează alarma și apoi marcat ca finalizat. Configurați limita global:

```php
define( 'QUEUETY_MAX_EXECUTION_TIME', 300 ); // 5 minutes
```

Joburile care rămân blocate în starea `processing` mai mult de `QUEUETY_STALE_TIMEOUT` secunde sunt recuperate automat:

```php
define( 'QUEUETY_STALE_TIMEOUT', 600 ); // 10 minutes
```

Recuperați manual joburile expirate:

```bash
wp queuety recover
```

## Cozi

Joburile sunt trimise în cozi cu nume. Fiecare worker procesează o singură coadă sau o listă ordonată de cozi (consultați [Workeri](https://queuety.inline0.dev/ro/docs/features/workers#multi-queue-priorities)):

```php
Queuety::dispatch( 'handler', $payload )->on_queue( 'emails' );
```

```bash
wp queuety work --queue=emails
```

Coada implicită este `default`. Puteți rula procese worker separate pentru fiecare coadă, pentru a izola sarcinile.

## Limitarea ratei

Limitați frecvența cu care poate fi executat un handler într-un interval de timp:

```php
Queuety::dispatch( 'call_api', $payload )
    ->rate_limit( 10, 60 ); // max 10 executions per 60 seconds
```

Consultați [Limitarea ratei](https://queuety.inline0.dev/ro/docs/features/rate-limiting) pentru detalii.

## Clase de joburi care pot fi trimise

Queuety v0.6.0 introduce o API de joburi bazată pe clase. În loc să separați numele handlerului, payload-ul și clasa handlerului, definiți o singură clasă care încapsulează totul: payload-ul (sub formă de proprietăți publice tipizate), logica de execuție și middleware-ul opțional.

### Interfața `Contracts\Job`

```php
use Queuety\Contracts\Job;

interface Job {
    public function handle(): void;
}
```

O clasă `Job` păstrează payload-ul în proprietăți publice și își execută propria logică în `handle()`. Nu este necesară o metodă `config()` separată sau înregistrarea handlerului.

### Trait-ul `Dispatchable`

Adăugați trait-ul `Dispatchable` pentru a obține o metodă statică `dispatch()` în clasa jobului:

```php
use Queuety\Contracts\Job;
use Queuety\Dispatchable;

class SendEmailJob implements Job {
    use Dispatchable;

    public function __construct(
        public readonly string $to,
        public readonly string $subject,
        public readonly string $body,
    ) {}

    public function handle(): void {
        wp_mail( $this->to, $this->subject, $this->body );
    }
}
```

Trimiteți-l pasând argumentele constructorului:

```php
SendEmailJob::dispatch( 'user@example.com', 'Welcome!', 'Hello there.' )
    ->on_queue( 'emails' )
    ->with_priority( Priority::High );
```

Trait-ul `Dispatchable` apelează intern `Queuety::dispatch()`, astfel că toate opțiunile `PendingJob` (queue, priority, delay, max_attempts, unique, after) funcționează la fel.

### Cum funcționează serializarea

Când trimiteți o instanță `Job`, Queuety extrage din `JobSerializer` numele complet calificat al clasei, pe care îl folosește ca handler, și toate proprietățile publice, pe care le folosește ca payload. Pe partea workerului, serializatorul reconstruiește instanța asociind cheile payload-ului cu numele parametrilor constructorului. Enumerațiile cu valori asociate sunt serializate în valoarea de bază și restaurate automat.

### Proprietățile sarcinii

Clasele joburilor pot declara proprietăți publice pentru a controla comportamentul de reîncercare și de expirare. Workerul le citește prin reflecție și le aplică automat.

```php
use Queuety\Contracts\Job;
use Queuety\Dispatchable;

class ImportProductsJob implements Job {
    use Dispatchable;

    public int $tries = 5;
    public int $timeout = 120;
    public array $backoff = [ 10, 30, 60, 120 ];
    public string $concurrency_group = 'imports';
    public int $concurrency_limit = 2;
    public int $cost_units = 4;

    public function __construct(
        public readonly int $supplier_id,
    ) {}

    public function handle(): void {
        // Import products...
    }
}
```

| Proprietate | Tip | Descriere |
|---|---|---|
| `$tries` | `int` | Numărul maxim de încercări înainte ca jobul să fie îngropat. Suprascrie valoarea `max_attempts` stabilită la trimitere. |
| `$timeout` | `int` | Timpul maxim de execuție, în secunde. Suprascrie constanta globală `QUEUETY_MAX_EXECUTION_TIME`. Necesită `pcntl` și nu este încă aplicată (consultați [Expirare](https://queuety.inline0.dev/ro/docs/jobs#timeout)). |
| `$backoff` | `array` | Tablou cu valorile întârzierii, în secunde, pentru fiecare încercare de reîncercare. Ultima valoare este reutilizată pentru încercările ulterioare. |
| `$concurrency_group` | `string` | Grup comun de admitere a workerilor pentru joburile costisitoare. |
| `$concurrency_limit` | `int` | Numărul maxim de joburi `processing` concurente în acel grup. Este ignorată fără `$concurrency_group`. |
| `$cost_units` | `int` | Costul relativ de execuție folosit de bugetele fluxurilor de lucru și de admiterea workerilor. |

Tabloul `$backoff` oferă întârzieri progresive între reîncercări. De exemplu, `[10, 30, 60, 120]` înseamnă că prima reîncercare așteaptă 10 secunde, a doua 30 de secunde, a treia 60 de secunde, iar orice reîncercare ulterioară așteaptă 120 secunde.

Proprietățile legate de resurse sunt utile când clasele joburilor reprezintă profiluri de cost diferite. O reîmprospătare rapidă a cache-ului și un apel amplu către un furnizor pot fi ambele „un job”, dar nu ar trebui neapărat să fie contabilizate la fel când un buget al fluxului de lucru sau o decizie de admitere a workerilor încearcă să mențină procesul în limite sigure.

Pentru modelul complet, inclusiv bugetele fluxurilor de lucru și admiterea pe partea workerului, consultați [Gestionarea resurselor](https://queuety.inline0.dev/ro/docs/features/resource-management).

### Hook la eșec

Când un job este îngropat după epuizarea tuturor încercărilor de reîncercare, Queuety apelează o metodă `failed()` pe instanța jobului, dacă este definită. Folosiți-o pentru curățare, alerte sau înregistrarea în jurnal.

```php
use Queuety\Contracts\Job;
use Queuety\Dispatchable;

class ProcessPaymentJob implements Job {
    use Dispatchable;

    public int $tries = 3;

    public function __construct(
        public readonly int $order_id,
    ) {}

    public function handle(): void {
        // Process the payment...
    }

    public function failed( \Throwable $exception ): void {
        // Notify the team that payment processing failed permanently.
        error_log( "Payment failed for order {$this->order_id}: {$exception->getMessage()}" );
    }
}
```

Metoda `failed()` primește excepția care a cauzat eșecul final. Instanța jobului este deserializată cu payload-ul inițial, astfel încât toate proprietățile publice sunt disponibile.

### Trimitere condiționată

Trait-ul `Dispatchable` oferă `dispatch_if()` și `dispatch_unless()` pentru trimitere condiționată. Ambele acceptă o condiție booleană ca prim argument, urmată de argumentele constructorului jobului.

```php
// Only dispatch if the user has opted in to notifications.
SendEmailJob::dispatch_if( $user->wants_notifications, $user->email, 'Update', $body );

// Dispatch unless the feature flag is disabled.
SyncInventoryJob::dispatch_unless( $maintenance_mode, $sku );
```

`dispatch_if()` trimite jobul când condiția este `true` și returnează `PendingJob`. Returnează `null` când condiția este `false`.

`dispatch_unless()` trimite jobul când condiția este `false` și returnează `PendingJob`. Returnează `null` când condiția este `true`.

Ambele metode acceptă înlănțuirea opțiunilor `PendingJob` atunci când trimit joburi:

```php
SendEmailJob::dispatch_if( $should_send, $to, $subject, $body )
    ?->on_queue( 'emails' )
    ?->with_priority( Priority::High );
```

### Trimitere sincronă

Folosiți `Queuety::dispatch_sync()` pentru a executa un job imediat în procesul curent, fără a-l adăuga în coadă. Este util pentru testare și pentru joburile care trebuie executate pe loc.

```php
use Queuety\Queuety;

// With a Job instance
Queuety::dispatch_sync( new SendEmailJob( to: 'user@example.com', subject: 'Hello', body: 'Hi!' ) );

// With a handler name and payload
Queuety::dispatch_sync( 'send_email', [ 'to' => 'user@example.com' ] );
```

Metoda `handle()` a sarcinii este apelată direct. Nu se creează niciun rând în baza de date, nu este implicat niciun worker și nu se aplică middleware.

### Înșiruirea joburilor

Înșiruirea joburilor execută o succesiune de joburi, unul după altul. Fiecare job din înșiruire depinde de finalizarea cu succes a jobului precedent. Dacă un job eșuează, joburile următoare din înșiruire rămân în așteptare.

#### Folosind `Queuety::chain()`

```php
use Queuety\Queuety;

$first_job_id = Queuety::chain( [
    new FetchDataJob( $url ),
    new ProcessDataJob(),
    new NotifyCompleteJob(),
] )
    ->on_queue( 'pipeline' )
    ->catch( ChainFailedHandler::class )
    ->dispatch();
```

`Queuety::chain()` returnează un `ChainBuilder` cu metode fluente pentru setarea cozii și a unui handler de eșec. Metoda `dispatch()` creează toate joburile cu relații `depends_on` secvențiale și returnează ID-ul primului job.

#### Folosind `with_chain()`

Trait-ul `Dispatchable` oferă o metodă `with_chain()`. Momentan, aceasta este echivalentă cu `Queuety::chain( $jobs )`: clasa pe care este apelată nu este adăugată în înșiruire ([#13](https://github.com/inline0/queuety/issues/13)), așadar includeți și primul job din tablou:

```php
FetchDataJob::with_chain( [
    new FetchDataJob(),
    new ProcessDataJob(),
    new NotifyCompleteJob(),
] )
    ->on_queue( 'pipeline' )
    ->dispatch();
```

#### Gestionarea eșecurilor din înșiruire

Înregistrați o clasă de gestionare a eșecurilor folosind `catch()`. Clasa handlerului trebuie să aibă o metodă `handle()`. Aceasta este apelată când orice job din înșiruire este îngropat definitiv. Workerul acceptă `handle()`, `handle( Job $job )` și `handle( Job $job, \Throwable $exception )`.

```php
class ChainFailedHandler {
    public function handle(): void {
        // Alert, clean up, etc.
    }
}
```

### Adăugarea middleware-ului

Clasele joburilor pot defini o metodă `middleware()` care returnează middleware-ul ce încadrează execuția:

```php
use Queuety\Contracts\Job;
use Queuety\Dispatchable;
use Queuety\Middleware\RateLimited;
use Queuety\Middleware\Timeout;

class CallOpenAIJob implements Job {
    use Dispatchable;

    public function __construct(
        public readonly string $prompt,
    ) {}

    public function handle(): void {
        // Call the API...
    }

    public function middleware(): array {
        return [
            new RateLimited( max: 60, window: 60 ),
            new Timeout( seconds: 120 ),
        ];
    }
}
```

Consultați [Middleware](https://queuety.inline0.dev/ro/docs/features/middleware) pentru detalii despre middleware-ul integrat și scrierea unui middleware personalizat.

### Comparație cu joburile bazate pe handler

| | Bazat pe handler (v0.1+) | Bazat pe clasă (v0.6+) |
|---|---|---|
| Trimitere | `Queuety::dispatch( 'send_email', $payload )` | `SendEmailJob::dispatch( $to, $subject )` |
| Handler | Clasă separată care implementează `Handler` | Aceeași clasă implementează `Contracts\Job` |
| Payload | `array $payload` fără tip | Proprietăți publice tipizate |
| Înregistrare | `Queuety::register()` sau `#[QueuetyHandler]` | Nu este necesară (folosește FQCN) |
| Middleware | Indisponibil | Metodă `middleware()` opțională |
| Configurare | Metoda `config()` | Proprietățile jobului + înșiruirea `PendingJob` |

Ambele abordări sunt acceptate integral. Joburile existente bazate pe handler continuă să funcționeze fără modificări. Puteți combina ambele stiluri în același proiect.

## Ciclul de viață al unui job

```
dispatch() -> pending -> processing -> completed
                  |           |
                  |           +-> exception -> pending (retry after backoff)
                  |                       |
                  |                       +-> buried (max attempts reached)
                  |
                  +-> delayed (available_at in the future)
```

| Stare | Descriere |
|---|---|
| `pending` | În așteptarea preluării de către un worker |
| `processing` | În curs de execuție |
| `completed` | Finalizat cu succes |
| `failed` | Rezervat; workerul nu îl setează în prezent. O încercare eșuată care va fi reîncercată revine în `pending` |
| `buried` | Toate încercările de reîncercare au fost epuizate |
