> ## Documentation Index
> Fetch the complete documentation index at: https://kawax.biz/llms.txt
> Use this file to discover all available pages before exploring further.

# Nebenläufige Verarbeitung

> Erläutert, wie Sie mit der Concurrency-Facade von Laravel 13 mehrere Verarbeitungen gleichzeitig ausführen und die Performance Ihrer Anwendung steigern.

## Was ist nebenläufige Verarbeitung?

Wenn Sie mehrere Anfragen an externe APIs oder Datenbank-Aggregationen, die nicht voneinander abhängen, **nacheinander ausführen**, entspricht die Gesamtdauer der Summe der einzelnen Laufzeiten.
Führen Sie sie hingegen **gleichzeitig** aus, verkürzt sich die Gesamtdauer auf die Zeit der langsamsten Einzelverarbeitung.

Die Facade `Concurrency` von Laravel realisiert eine solche nebenläufige Ausführung über eine einfache API.

<Info>
  Die Facade `Concurrency` wurde in Laravel 11 eingeführt und steht auch in Laravel 13 weiterhin zur Verfügung.
  Der Standardtreiber verwendet PHP-Kindprozesse und benötigt keine zusätzlichen Pakete.
</Info>

## Funktionsweise

Die Facade `Concurrency` serialisiert die übergebenen Closures, schickt sie an einen versteckten Artisan-Befehl und lässt sie jeweils in einem separaten PHP-Prozess ausführen.
Nach Abschluss werden die Rückgabewerte serialisiert und an den Elternprozess zurückgereicht.

Es stehen drei Treiber zur Verfügung:

| Treiber   | Beschreibung                                                                                               |
| --------- | ---------------------------------------------------------------------------------------------------------- |
| `process` | Standard. Startet PHP-Kindprozesse für die Ausführung. Funktioniert auch im Kontext eines Web-Requests.    |
| `fork`    | Benötigt das Paket `spatie/fork`. Erzeugt Kindprozesse per Fork; nur im CLI verwendbar, aber sehr schnell. |
| `sync`    | Führt die Closures nicht nebenläufig, sondern nacheinander aus. Eignet sich für Tests.                     |

## Grundlegende Verwendung

### Concurrency::run()

Übergeben Sie der Methode `run()` ein Array mit Closures, werden diese nebenläufig ausgeführt.
Der Rückgabewert enthält die Ergebnisse der jeweiligen Closures als Array.

```php theme={null}
use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\DB;

[$userCount, $orderCount] = Concurrency::run([
    fn () => DB::table('users')->count(),
    fn () => DB::table('orders')->count(),
]);
```

Sie können die Ergebnisse mit einem Array-Destructuring in einzelne Variablen aufteilen.
Die Reihenfolge der Closures entspricht der Reihenfolge der Rückgabewerte.

### Treiber angeben

Um einen bestimmten Treiber zu verwenden, nutzen Sie die Methode `driver()`.

```php theme={null}
$results = Concurrency::driver('fork')->run([
    fn () => fetchFromApiA(),
    fn () => fetchFromApiB(),
]);
```

Um den Standardtreiber zu ändern, veröffentlichen Sie die Konfigurationsdatei und passen die Option `default` an.

```shell theme={null}
php artisan config:publish concurrency
```

## Verwendung des `fork`-Treibers

Der `fork`-Treiber ist schneller als der `process`-Treiber, funktioniert aber ausschließlich in PHP-CLI-Umgebungen (z. B. in Artisan-Befehlen oder Queue-Workern).
Innerhalb eines Web-Requests kann er nicht verwendet werden.

Installieren Sie vor der Nutzung das Paket `spatie/fork`.

```shell theme={null}
composer require spatie/fork
```

<Warning>
  Der `fork`-Treiber funktioniert nicht während eines Web-Requests. Verwenden Sie ihn, wenn Sie nebenläufige Verarbeitung innerhalb eines Artisan-Befehls oder Queue-Workers wünschen.
</Warning>

## Ohne Rückgabewerte: Concurrency::defer()

Interessieren Sie sich nicht für die Ergebnisse und möchten die Verarbeitung im Hintergrund nach dem Senden der HTTP-Antwort ausführen, verwenden Sie die Methode `defer()`.

```php theme={null}
use App\Services\Metrics;
use Illuminate\Support\Facades\Concurrency;

Concurrency::defer([
    fn () => Metrics::report('users'),
    fn () => Metrics::report('orders'),
]);
```

Beim Aufruf von `defer()` werden die Closures noch nicht ausgeführt.
Sie laufen erst nebenläufig, nachdem die HTTP-Antwort an den Nutzer gesendet wurde.

<Tip>
  `defer()` eignet sich hervorragend für Arbeiten, bei denen der Nutzer nicht warten muss – etwa das Aufzeichnen von Analytics oder das Vorwärmen von Caches.
</Tip>

## Praxisbeispiel: mehrere externe APIs gleichzeitig aufrufen

Nehmen wir das Dashboard eines E-Commerce-Systems, das Daten aus drei APIs bezieht: Bestandsführung, Umsatzstatistik und Versandstatus.

### Sequenzielle Ausführung (vor der Optimierung)

```php theme={null}
use Illuminate\Support\Facades\Http;

public function dashboard(): array
{
    // Auf jede Anfrage nacheinander warten (insgesamt ca. 3 Sekunden)
    $inventory = Http::get('https://api.example.com/inventory')->json();
    $sales     = Http::get('https://api.example.com/sales')->json();
    $shipping  = Http::get('https://api.example.com/shipping')->json();

    return compact('inventory', 'sales', 'shipping');
}
```

### Nebenläufige Ausführung (nach der Optimierung)

```php theme={null}
use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\Http;

public function dashboard(): array
{
    // Alle drei Anfragen gleichzeitig ausführen (Dauer = langsamste API)
    [$inventory, $sales, $shipping] = Concurrency::run([
        fn () => Http::get('https://api.example.com/inventory')->json(),
        fn () => Http::get('https://api.example.com/sales')->json(),
        fn () => Http::get('https://api.example.com/shipping')->json(),
    ]);

    return compact('inventory', 'sales', 'shipping');
}
```

Braucht jede API 1 Sekunde, ergibt sich sequenziell eine Gesamtlaufzeit von 3 Sekunden, nebenläufig hingegen nur etwa 1 Sekunde.

### Mehrere Datenbank-Aggregationen gleichzeitig

```php theme={null}
use Illuminate\Support\Facades\Concurrency;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Cache;

public function statistics(): array
{
    [$totalUsers, $activeUsers, $totalOrders, $revenue] = Concurrency::run([
        fn () => DB::table('users')->count(),
        fn () => DB::table('users')->where('active', true)->count(),
        fn () => DB::table('orders')->count(),
        fn () => DB::table('orders')->sum('total_amount'),
    ]);

    return [
        'total_users'  => $totalUsers,
        'active_users' => $activeUsers,
        'total_orders' => $totalOrders,
        'revenue'      => $revenue,
    ];
}
```

<Tip>
  Wenn Sie den `process`-Treiber für Datenbank-Aggregationen verwenden, öffnet jeder Kindprozess eine eigene Datenbankverbindung.
  Achten Sie bei hoher Parallelität auf die maximale Verbindungsanzahl Ihrer Datenbank.
</Tip>

## Konfiguration für Tests

In Testumgebungen führt der `sync`-Treiber die Closures nacheinander aus.
Es entfällt der Overhead durch das Starten von Prozessen, wodurch Tests schneller laufen.

```php theme={null}
// tests/Feature/DashboardTest.php
use Illuminate\Support\Facades\Concurrency;

public function test_dashboard_returns_correct_data(): void
{
    Concurrency::fake(); // Auf den sync-Treiber umschalten

    $response = $this->get('/dashboard');

    $response->assertStatus(200);
}
```

Alternativ können Sie den Standardtreiber in `.env.testing` festlegen.

```ini theme={null}
CONCURRENCY_DRIVER=sync
```

## Hinweise

<AccordionGroup>
  <Accordion title="Einschränkungen bei Closures">
    Closures werden serialisiert und an Kindprozesse übergeben.
    Objekte, die nicht serialisierbar sind (Datenbankverbindungen, Datei-Handles, Ressourcen usw.), können nicht von außerhalb der Closure eingefangen werden.
    Erstellen Sie die benötigten Objekte innerhalb der Closure neu.

    ```php theme={null}
    // Schlecht: ein Eloquent-Modell von außerhalb einfangen
    $user = User::find(1);
    Concurrency::run([
        fn () => $user->orders()->count(), // Möglicherweise nicht serialisierbar
    ]);

    // Gut: Daten innerhalb der Closure laden
    $userId = 1;
    Concurrency::run([
        fn () => Order::where('user_id', $userId)->count(),
    ]);
    ```
  </Accordion>

  <Accordion title="Overhead des process-Treibers">
    Der `process`-Treiber ist mit dem Aufwand für das Starten eines Kindprozesses verbunden. Ist die Verarbeitung selbst sehr kurz (wenige Millisekunden), bringt die nebenläufige Ausführung möglicherweise keinen Vorteil.
    Ihre Stärke spielt sie bei Arbeiten von 100 ms und mehr aus – etwa HTTP-Anfragen oder Datenbank-Aggregationen.
  </Accordion>

  <Accordion title="Einschränkung des fork-Treibers">
    Der `fork`-Treiber forkt einen PHP-Prozess und kann daher nicht innerhalb eines Web-Requests (FPM oder Apache) genutzt werden.
    Verwenden Sie ihn ausschließlich in Artisan-Befehlen oder Queue-Workern.
  </Accordion>

  <Accordion title="Umgang mit Ausnahmen">
    Tritt während der nebenläufigen Ausführung eine Ausnahme auf, wirft `run()` diese erneut.
    Wenn Sie möchten, dass andere Verarbeitungen weiterlaufen, obwohl eine Closure fehlschlägt, fangen Sie die Ausnahme innerhalb der Closure mit `try/catch` ab.

    ```php theme={null}
    Concurrency::run([
        function () {
            try {
                return Http::get('https://api.example.com/data')->json();
            } catch (\Exception $e) {
                return null; // Bei Fehler null zurückgeben
            }
        },
    ]);
    ```
  </Accordion>
</AccordionGroup>

## Nächste Schritte

<Card title="Queues und Jobs" icon="layer-group" href="/de/queues">
  Erfahren Sie, wie Sie Arbeit asynchron im Hintergrund über Queues und Jobs verarbeiten.
</Card>


## Related topics

- [Laravel Octane](/de/octane.md)
- [Laravel Horizon](/de/horizon.md)
- [HTTP-Requests](/de/requests.md)
- [Collections](/de/collections.md)
- [WebSocket (Jetstream / Firehose)](/de/packages/laravel-bluesky/websocket.md)
