
# Tarpinė programinė įranga

Tarpinė programinė įranga apgaubia užduoties vykdymą sluoksniuotu konvejeriu. Kiekviena tarpinė programinė įranga gali vykdyti logiką prieš užduotį ir po jos, sustabdyti vykdymą anksčiau arba perimti išimtis. Queuety turi penkias įtaisytąsias tarpines programas ir palaiko pasirinktines per `Contracts\Middleware` sąsają.

Tarpinę programinę įrangą galima naudoti [užduočių klasėse, kurias galima perduoti vykdyti](https://queuety.inline0.dev/lt/docs/jobs#dispatchable-job-classes) (klasėse, įgyvendinančiose `Contracts\Job` ir naudojančiose savybę `Dispatchable`).

## Sąsaja `Contracts\Middleware`

```php
use Queuety\Contracts\Middleware;

interface Middleware {
    public function handle( object $job, \Closure $next ): void;
}
```

Parametras `$job` yra apdorojamas užduoties egzempliorius. Iškvieskite `$next( $job )`, kad perduotumėte vykdymą kitai konvejerio tarpinei programinei įrangai (arba užduoties metodui `handle()`, jei tai paskutinė tarpinė programa).

## Tarpinės programinės įrangos įtraukimas į užduotį

Užduoties klasėje apibrėžkite metodą `middleware()`, grąžinantį tarpinių programų egzempliorių masyvą:

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

class SyncInventoryJob implements Job {
    use Dispatchable;

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

    public function handle(): void {
        // Sync inventory for this SKU...
    }

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

## Konvejerio vykdymo tvarka

Tarpinė programinė įranga taikoma masyvo tvarka, o pirmoji yra išorinis sluoksnis. Jei jūsų metodas `middleware()` grąžina `[A, B, C]`, vykdymo tvarka yra tokia:

```
A::handle() -> B::handle() -> C::handle() -> $job->handle()
```

Kiekviena tarpinė programa apgaubia kitą. `A` pirmiausia vykdoma prieš užduotį, o paskiausiai – jai pasibaigus, todėl puikiai tinka trukmei matuoti arba try/catch apgaubai.

## Pasirinktinės tarpinės programinės įrangos kūrimas

Įgyvendinkite sąsają `Contracts\Middleware`:

```php
use Queuety\Contracts\Middleware;

class LogExecution implements Middleware {
    public function handle( object $job, \Closure $next ): void {
        $class = get_class( $job );
        error_log( "[Queuety] Starting: {$class}" );

        $start = microtime( true );

        try {
            $next( $job );
        } finally {
            $elapsed = round( microtime( true ) - $start, 3 );
            error_log( "[Queuety] Finished: {$class} in {$elapsed}s" );
        }
    }
}
```

Naudokite ją užduotyje:

```php
public function middleware(): array {
    return [
        new LogExecution(),
    ];
}
```

### Vykdymo sustabdymas

Tarpinė programa gali visiškai praleisti užduotį, jei neiškviečia `$next()`:

```php
class SkipOnMaintenanceMode implements Middleware {
    public function handle( object $job, \Closure $next ): void {
        if ( wp_is_maintenance_mode() ) {
            return; // Job silently skipped.
        }

        $next( $job );
    }
}
```

## Integruota tarpinė programinė įranga

### `ThrottlesExceptions`

Apriboja užduoties apdorojimo funkcijos išimčių skaičių, kad būtų išvengta užduočių antplūdžio. Jei apdorojimo funkcijoje per tam tikrą laikotarpį įvyko per daug išimčių, vėlesnės jos užduotys atmetamos su `RuntimeException`, o ne apdorojamos. Taip neleidžiama neveikiančiai išorinei paslaugai vienu metu išeikvoti visų pakartotinių bandymų daugelyje užduočių.

```php
use Queuety\Middleware\ThrottlesExceptions;

public function middleware(): array {
    return [
        new ThrottlesExceptions( max_attempts: 10, decay_minutes: 10 ),
    ];
}
```

| Parametras | Tipas | Numatytoji reikšmė | Aprašymas |
|---|---|---|---|
| `$max_attempts` | `int` | `10` | Didžiausias išimčių skaičius per mažėjimo langą |
| `$decay_minutes` | `int` | `10` | Lango trukmė minutėmis |

Išimčių skaičius sekamas pagal apdorojimo funkcijos klasę Queuety užraktų lentelėje ir bendras visiems vykdytojo procesams. Pasiekus ribą, tarpinė programa išmeta `RuntimeException` neiškvietusi užduoties, o vykdytojas tai apdoroja kaip bet kurią kitą klaidą: bandymas įskaičiuojamas į `max_attempts`, užduotis pakartojama taikant įprastą delsą, o pasibaigus bandymams perkeliama į nepavykusių užduočių saugyklą.

Pavyzdys: jei neveikia išorinė API, pirmieji 10 nesėkmingų bandymų apdorojami įprastai (kiekvienas padidina išimčių skaitiklį). Kiti bandymai iškart nepavyksta neiškvietus API, kol 10 minučių neįvyksta nauja išimtis; langas prasideda iš naujo po kiekvienos užregistruotos išimties.

### `RateLimited`

Užtikrina didžiausią vykdymų skaičių per laikotarpį. Jei tarpinėje programinėje įrangoje riba viršijama, išmetama `RateLimitExceededException`; vykdytojas ją apdoroja kaip įprastą nesėkmingą bandymą, pakartoja taikydamas įprastą delsą ir įskaičiuoja į `max_attempts`. Tarpinei programai užregistravus ribą vykdytojo spartos ribotuve, vėlesnės apdorojimo funkcijos užduotys, kol galioja riba, taip pat grąžinamos į `pending` dar prieš jas vykdant (žr. [Spartos ribojimas](https://queuety.inline0.dev/lt/docs/features/rate-limiting)).

```php
use Queuety\Middleware\RateLimited;

public function middleware(): array {
    return [
        new RateLimited( max: 60, window: 60 ), // 60 per minute
    ];
}
```

| Parametras | Tipas | Aprašymas |
|---|---|---|
| `$max` | `int` | Didžiausias vykdymų skaičius per langą |
| `$window` | `int` | Lango trukmė sekundėmis |

Spartos ribotuvas naudoja vidinį `RateLimiter` egzempliorių, priklausantį Queuety, todėl ribos taikomos visiems vykdytojo procesams.

### `Timeout`

Nustato kiekvienos užduoties vykdymo laiko limitą naudodamas `pcntl_alarm`; numatyta išmesti `TimeoutException`, jei užduotis trunka per ilgai. Šiuo metu laiko limitas netaikomas ([#10](https://github.com/inline0/queuety/issues/10)): Queuety neįjungia asinchroninių signalų, todėl žadintuvo apdorojimo funkcija nevykdoma, kol vykdoma užduotis. Be to, tarpinė programinė įranga grąžindama nustato žadintuvą iš naujo ir atšaukia paties vykdytojo užduočiai skirtą žadintuvą.

```php
use Queuety\Middleware\Timeout;

public function middleware(): array {
    return [
        new Timeout( seconds: 30 ),
    ];
}
```

| Parametras | Tipas | Aprašymas |
|---|---|---|
| `$seconds` | `int` | Ilgiausia vykdymo trukmė sekundėmis |

Reikia plėtinio `pcntl` PHP. Jei `pcntl` nepasiekiamas, tarpinė programinė įranga tyliai praleidžiama (laiko limitas netaikomas).

### `UniqueJob`

Neleidžia vienu metu vykdyti užduočių su tuo pačiu raktu. Naudoja duomenų bazės užraktų lentelę, kad vienu metu veiktų tik vienas egzempliorius. Jei užraktas jau laikomas, užduotis tyliai praleidžiama (nekartojama).

```php
use Queuety\Middleware\UniqueJob;

public function middleware(): array {
    return [
        new UniqueJob(),                         // Lock key defaults to the job class name
        new UniqueJob( key: 'sync_inventory' ),   // Custom lock key
    ];
}
```

| Parametras | Tipas | Numatytoji reikšmė | Aprašymas |
|---|---|---|---|
| `$key` | `string\|null` | `null` (naudojamas klasės pavadinimas) | Unikalumo užrakto raktas |

### `WithoutOverlapping`

Neleidžia vienu metu vykdyti užduočių, kurioms naudojamas tas pats loginis raktas. Užraktas automatiškai nustoja galioti, kad apsaugotų nuo užstrigusių užraktų, jei vykdytojas netikėtai nutrūksta. Panašu į `UniqueJob`, tačiau skirta ilgai trunkančioms operacijoms, kurių užraktas turi būti automatiškai atleidžiamas pasibaigus skirtajam laikui.

```php
use Queuety\Middleware\WithoutOverlapping;

public function middleware(): array {
    return [
        new WithoutOverlapping( key: 'import_products', release_after: 600 ),
    ];
}
```

| Parametras | Tipas | Numatytoji reikšmė | Aprašymas |
|---|---|---|---|
| `$key` | `string` | (privalomas) | Loginis operacijos raktas |
| `$release_after` | `int` | `300` | Ilgiausia užrakto trukmė sekundėmis iki automatinio atleidimo |

Jei užraktas jau laikomas, užduotis tyliai praleidžiama. Prieš kiekvieną bandymą užrakinti automatiškai pašalinami nebegaliojantys užraktai.

## Pavyzdys: tarpinių programų derinimas

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

class ImportProductsJob implements Job {
    use Dispatchable;

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

    public function handle(): void {
        // Fetch and import products from the supplier...
    }

    public function middleware(): array {
        return [
            new Timeout( seconds: 300 ),
            new WithoutOverlapping(
                key: "import_supplier_{$this->supplier_id}",
                release_after: 600,
            ),
            new RateLimited( max: 5, window: 60 ),
        ];
    }
}
```

Ši užduotis:

1. Bus nutraukta, jei vykdoma ilgiau nei 5 minutes (Timeout)
2. Bus praleista, jei jau vykdomas to paties tiekėjo importavimas (WithoutOverlapping)
3. Bus apribota iki 5 importavimų per minutę tarp visų tiekėjų (RateLimited)
