
# Job

I job sono l'unità di lavoro fondamentale in Queuety. Inviate un job con il nome di un handler e un payload; un worker lo preleva ed esegue in background.

## Invio dei job

```php
use Queuety\Queuety;

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

`dispatch()` restituisce un builder `PendingJob`. Potete concatenare le opzioni prima che venga inviato automaticamente:

```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 );
```

Per ottenere subito l'ID del job, chiamate `->id()`:

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

## Scrittura degli handler

Un handler è qualsiasi classe che implementa l'interfaccia `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',
        ];
    }
}
```

Il metodo `handle()` riceve il payload del job ed esegue il lavoro. Il metodo `config()` restituisce eventuali valori predefiniti:

| Chiave | Tipo | Descrizione |
|---|---|---|
| `queue` | `string` | Nome predefinito della coda |
| `max_attempts` | `int` | Numero massimo di esecuzioni, compresa la prima, prima che il job venga messo in archivio |
| `backoff` | `string` | Strategia di attesa tra i nuovi tentativi: `exponential`, `linear` oppure `fixed` |
| `rate_limit` | `array` | Limite di frequenza come `[max_executions, window_seconds]` |
| `concurrency_group` | `string` | Gruppo condiviso di ammissione dei worker per handler costosi |
| `concurrency_limit` | `int` | Numero massimo di job elaborati contemporaneamente nel gruppo |
| `cost_units` | `int` | Costo relativo di esecuzione usato da code ponderate o budget dei provider, budget dei flussi di lavoro e criteri euristici di ammissione |

Queste chiavi delle risorse fanno parte del più ampio modello [Gestione delle risorse](https://queuety.inline0.dev/it/docs/features/resource-management) di Queuety, in cui i metadati dei job, i budget dei flussi di lavoro e l'ammissione dei worker operano insieme.

Registrate l'handler:

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

Oppure usate gli attributi [PHP](https://queuety.inline0.dev/it/docs/features/attributes) per la registrazione automatica.

## Priorità

I job hanno quattro livelli di priorità. I worker acquisiscono per primi i job con priorità più alta.

```php
use Queuety\Enums\Priority;

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

| Livello | Valore | Descrizione |
|---|---|---|
| `Priority::Low` | `0` | Predefinita. Lavoro in background che può attendere. |
| `Priority::Normal` | `1` | Priorità standard. |
| `Priority::High` | `2` | Elaborata prima di Low e Normal. |
| `Priority::Urgent` | `3` | Elaborata per prima. Usatela con moderazione. |

## Ritardo

Ritardate un job in modo che diventi disponibile solo dopo il numero di secondi specificato:

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

Il job viene inserito immediatamente, ma i worker non lo acquisiscono finché non è trascorso il ritardo.

## Job univoci

Impedite invii duplicati per lo stesso handler e payload. Se esiste già un job in attesa o in elaborazione con lo stesso handler e payload, viene restituito l'ID del job esistente invece di crearne uno nuovo.

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

## Dipendenze tra job

Un job può dipendere da un altro job. Un worker non lo acquisirà finché la dipendenza non sarà stata completata.

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

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

## Invio in batch

Inviate più job con un singolo INSERT su più righe per ottenere il massimo throughput:

```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' ] ],
] );
```

Ogni elemento può includere chiavi facoltative: `queue`, `priority`, `delay`, `max_attempts`.

## Nuovi tentativi e attesa progressiva

Quando un job genera un'eccezione, Queuety lo rimette in `pending` con un `available_at` ritardato, finché non è stato eseguito `max_attempts` volte in totale (si conta anche la prima esecuzione, quindi `max_attempts( 3 )` consente fino a due nuovi tentativi). Il ritardo prima di ogni nuovo tentativo dipende dalla strategia di attesa progressiva, in cui `attempt` indica il numero dell'esecuzione appena fallita:

| Strategia | Formula del ritardo | Esempio (dopo le esecuzioni 1, 2, 3) |
|---|---|---|
| `exponential` | `2^attempt * 30` secondi, fino a un massimo di 3600 | 60s, 120s, 240s |
| `linear` | `attempt * 60` secondi | 60s, 120s, 180s |
| `fixed` | `60` secondi | 60s, 60s, 60s |

Impostate il valore predefinito globale in `wp-config.php`:

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

Oppure per handler in `config()`:

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

Oppure per singolo invio:

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

## Processi sepolti

Dopo aver esaurito tutti i tentativi, un job viene contrassegnato come **messo in archivio**. I job archiviati restano nel database per essere ispezionati, ma non vengono elaborati. Potete riprovarli manualmente. Un nuovo tentativo manuale rende di nuovo il job `pending` senza azzerarne il conteggio dei tentativi; pertanto, un job archiviato e riprovato viene eseguito un'altra volta e rimesso in archivio se anche quell'esecuzione fallisce:

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

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

Oppure tramite CLI:

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

## Timeout

Queuety attiva un `pcntl_alarm()` per la durata massima di esecuzione e dovrebbe far fallire il tentativo con un `TimeoutException` se il limite viene superato. Al momento il timeout non viene applicato ([#10](https://github.com/inline0/queuety/issues/10)): il worker non abilita i segnali asincroni, quindi un handler di lunga durata viene eseguito fino al completamento, mentre un handler bloccato in `sleep()` viene interrotto quando scatta l'allarme e poi contrassegnato come completato. Configurate il timeout globale:

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

I job che restano nello stato `processing` per più di `QUEUETY_STALE_TIMEOUT` secondi vengono recuperati automaticamente:

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

Ripristinate manualmente i job obsoleti:

```bash
wp queuety recover
```

## Code

I job vengono inviati a code denominate. Ogni worker elabora una coda oppure un elenco ordinato di code (consultate [Worker](https://queuety.inline0.dev/it/docs/features/workers#multi-queue-priorities)):

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

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

La coda predefinita è `default`. Potete eseguire processi worker separati per ogni coda, così da isolare i carichi di lavoro.

## Limitazione della frequenza

Limitate la frequenza con cui un handler può essere eseguito nell'arco di un intervallo di tempo:

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

Per i dettagli, consultate [Limitazione della frequenza](https://queuety.inline0.dev/it/docs/features/rate-limiting).

## Classi di job inviabili

Queuety v0.6.0 introduce una classe API per i job. Invece di separare il nome dell'handler, il payload e la classe dell'handler, definite un'unica classe che racchiude tutto: il payload (come proprietà pubbliche tipizzate), la logica di esecuzione e il middleware facoltativo.

### L'interfaccia `Contracts\Job`

```php
use Queuety\Contracts\Job;

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

Una classe `Job` contiene il proprio payload in proprietà pubbliche ed esegue la propria logica in `handle()`. Non è necessario un metodo `config()` separato né registrare un handler.

### Il trait `Dispatchable`

Aggiungete il trait `Dispatchable` per ottenere un metodo statico `dispatch()` nella classe del job:

```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 );
    }
}
```

Invitatelo passando gli argomenti del costruttore:

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

Il trait `Dispatchable` chiama internamente `Queuety::dispatch()`, quindi tutte le opzioni `PendingJob` (queue, priority, delay, max_attempts, unique, after) funzionano esattamente allo stesso modo.

### Come funziona la serializzazione

Quando inviate un'istanza di `Job`, Queuety estrae il nome completo della classe come handler e tutte le proprietà pubbliche come payload tramite il suo `JobSerializer`. Sul worker, il serializzatore ricostruisce l'istanza associando le chiavi del payload ai nomi dei parametri del costruttore. Gli enum con valore di supporto vengono serializzati nel valore sottostante e ripristinati automaticamente.

### Proprietà del job

Le classi dei job possono dichiarare proprietà pubbliche per controllare il comportamento dei nuovi tentativi e dei timeout. Il worker le legge tramite reflection e le applica automaticamente.

```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...
    }
}
```

| Proprietà | Tipo | Descrizione |
|---|---|---|
| `$tries` | `int` | Numero massimo di tentativi prima che il job venga messo in archivio. Sostituisce il valore `max_attempts` definito al momento dell'invio. |
| `$timeout` | `int` | Durata massima di esecuzione in secondi. Sostituisce la costante globale `QUEUETY_MAX_EXECUTION_TIME`. Richiede `pcntl` e al momento non viene applicata (consultate [Timeout](https://queuety.inline0.dev/it/docs/jobs#timeout)). |
| `$backoff` | `array` | Array di valori di ritardo in secondi per ogni nuovo tentativo. Il valore dell'ultimo elemento viene riutilizzato per i tentativi successivi. |
| `$concurrency_group` | `string` | Gruppo condiviso di ammissione dei worker per i job costosi. |
| `$concurrency_limit` | `int` | Numero massimo di job `processing` simultanei nel gruppo. Ignorato senza `$concurrency_group`. |
| `$cost_units` | `int` | Costo relativo di esecuzione usato dai budget dei flussi di lavoro e dall'ammissione dei worker. |

L'array `$backoff` definisce ritardi crescenti tra i nuovi tentativi. Per esempio, `[10, 30, 60, 120]` indica che il primo nuovo tentativo attende 10 secondi, il secondo 30 secondi, il terzo 60 secondi e gli eventuali ulteriori tentativi attendono 120 secondi.

Le proprietà relative alle risorse sono utili quando le classi dei job rappresentano profili di costo diversi. Un aggiornamento rapido della cache e una chiamata di grandi dimensioni a un provider possono essere entrambi «un job», ma non dovrebbero necessariamente avere lo stesso peso quando un budget del flusso di lavoro o una decisione di ammissione dei worker mirano a mantenere il processo entro limiti sicuri.

Per il modello completo, compresi i budget dei flussi di lavoro e l'ammissione lato worker, consultate [Gestione delle risorse](https://queuety.inline0.dev/it/docs/features/resource-management).

### Hook di errore

Quando un job viene messo in archivio dopo aver esaurito tutti i tentativi, Queuety chiama un metodo `failed()` sull'istanza del job, se definito. Usatelo per la pulizia, gli avvisi o la registrazione nei log.

```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()}" );
    }
}
```

Il metodo `failed()` riceve l'eccezione che ha causato l'errore finale. L'istanza del job viene deserializzata con il payload originale, quindi tutte le proprietà pubbliche sono disponibili.

### Invio condizionale

Il trait `Dispatchable` fornisce `dispatch_if()` e `dispatch_unless()` per l'invio condizionale. Entrambi accettano come primo argomento una condizione booleana, seguita dagli argomenti del costruttore del job.

```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()` invia il job se la condizione è `true` e restituisce `PendingJob`. Se la condizione è `false`, restituisce `null`.

`dispatch_unless()` invia il job se la condizione è `false` e restituisce `PendingJob`. Se la condizione è `true`, restituisce `null`.

Entrambi i metodi supportano la concatenazione delle opzioni `PendingJob` al momento dell'invio:

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

### Invio sincrono

Usate `Queuety::dispatch_sync()` per eseguire subito un job nel processo corrente senza aggiungerlo alla coda. È utile per i test e per i job che devono essere eseguiti inline.

```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' ] );
```

Il metodo `handle()` del job viene chiamato direttamente. Non viene creata alcuna riga nel database, non interviene alcun worker e non si applica il middleware.

### Concatenamento dei job

Il concatenamento dei job esegue una sequenza di job uno dopo l'altro. Ogni job della catena dipende dal completamento corretto del precedente. Se un job non riesce, i job successivi della catena restano in attesa.

#### Uso di `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()` restituisce un `ChainBuilder` con metodi fluenti per impostare la coda e un handler degli errori. Il metodo `dispatch()` crea tutti i job con relazioni `depends_on` sequenziali e restituisce l'ID del primo job.

#### Uso di `with_chain()`

Il trait `Dispatchable` fornisce un metodo `with_chain()`. Al momento equivale a `Queuety::chain( $jobs )`: la classe su cui viene chiamato non viene aggiunta alla catena ([#13](https://github.com/inline0/queuety/issues/13)), quindi includete anche il primo job nell'array:

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

#### Gestione degli errori della catena

Registrate una classe handler degli errori usando `catch()`. La classe handler deve avere un metodo `handle()`. Viene chiamato quando un qualsiasi job della catena viene definitivamente messo in archivio. Il worker supporta `handle()`, `handle( Job $job )` e `handle( Job $job, \Throwable $exception )`.

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

### Aggiunta di middleware

Le classi dei job possono definire un metodo `middleware()` per restituire middleware che avvolge l'esecuzione:

```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 ),
        ];
    }
}
```

Per i dettagli sul middleware integrato e su come scriverne di personalizzato, consultate [Middleware](https://queuety.inline0.dev/it/docs/features/middleware).

### Confronto con i job basati su handler

| | Basato su handler (v0.1+) | Basato su classi (v0.6+) |
|---|---|---|
| Dispatch | `Queuety::dispatch( 'send_email', $payload )` | `SendEmailJob::dispatch( $to, $subject )` |
| Handler | Classe separata che implementa `Handler` | La stessa classe implementa `Contracts\Job` |
| Payload | `array $payload` non tipizzato | Proprietà pubbliche tipizzate |
| Registrazione | `Queuety::register()` o `#[QueuetyHandler]` | Non richiesta (usa FQCN) |
| Middleware | Non disponibile | Metodo `middleware()` facoltativo |
| Configurazione | Metodo `config()` | Proprietà del job + catena `PendingJob` |

Entrambi gli approcci sono pienamente supportati. I job esistenti basati su handler continuano a funzionare senza modifiche. Potete combinare entrambi gli stili nello stesso progetto.

## Ciclo di vita dei job

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

| Stato | Descrizione |
|---|---|
| `pending` | In attesa di essere preso in carico da un worker |
| `processing` | In fase di esecuzione |
| `completed` | Completato correttamente |
| `failed` | Riservato; al momento il worker non lo imposta. Un tentativo non riuscito che verrà riprovato torna a `pending` |
| `buried` | Tutti i tentativi di ripetizione esauriti |
