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

# Concurrence

> Utilisez la façade Concurrency de Laravel 13 pour exécuter plusieurs traitements en parallèle et améliorer les performances.

## Qu'est-ce que la concurrence

Exécuter séquentiellement plusieurs traitements indépendants (appels d'API externes, agrégations DB…) additionne les durées.
En les **exécutant en parallèle**, la durée totale se réduit à celle du plus long.

La façade `Concurrency` de Laravel fournit une API simple pour cela.

<Info>
  Introduite avec Laravel 11, elle reste disponible sous Laravel 13.
  Le driver par défaut utilise des sous-processus PHP, sans package additionnel.
</Info>

## Fonctionnement

`Concurrency` sérialise chaque closure et l'envoie à une commande Artisan interne exécutée dans un processus séparé.
Les valeurs de retour sont sérialisées et renvoyées au processus parent.

Trois drivers :

| Driver    | Description                                             |
| --------- | ------------------------------------------------------- |
| `process` | Par défaut. Sous-processus PHP. Fonctionne en Web       |
| `fork`    | Nécessite `spatie/fork`. Uniquement en CLI, plus rapide |
| `sync`    | Séquentiel. Utile pour les tests                        |

## Utilisation

### `Concurrency::run()`

Passez un tableau de closures. Elles s'exécutent en parallèle, et vous récupérez leurs retours dans le même ordre.

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

Destructuration du tableau pour affecter chaque résultat.

### Choisir un driver

Avec `driver()` :

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

Pour changer le driver par défaut, publiez la configuration.

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

## Utiliser le driver `fork`

Plus rapide que `process`, mais uniquement en environnement CLI (Artisan, workers).
Non utilisable dans une requête web.

Installez `spatie/fork` d'abord.

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

<Warning>
  `fork` ne fonctionne pas dans une requête web. Utilisez-le dans une commande Artisan ou un worker de queue.
</Warning>

## Ignorer les résultats : `Concurrency::defer()`

Si les résultats n'importent pas et que le traitement peut s'exécuter après la réponse HTTP, utilisez `defer()`.

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

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

L'exécution démarre après l'envoi de la réponse, en parallèle.

<Tip>
  Idéal pour les métriques, les warmups de cache, etc., quand l'utilisateur n'a pas à attendre.
</Tip>

## Exemple : appeler plusieurs API en parallèle

Dashboard e-commerce interrogeant l'inventaire, les ventes et l'expédition.

### Séquentiel (avant)

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

public function dashboard(): array
{
    // Chaque requête attend la précédente (~3 s au total)
    $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');
}
```

### Parallèle (après)

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

public function dashboard(): array
{
    // Les 3 requêtes en parallèle (durée = la plus lente)
    [$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');
}
```

Si chaque API met 1 s : \~3 s en séquentiel, \~1 s en parallèle.

### Plusieurs agrégations DB en parallèle

```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>
  Chaque sous-processus ouvre sa propre connexion DB. Attention au nombre max de connexions concurrentes.
</Tip>

## Configuration pour les tests

Le driver `sync` exécute séquentiellement — pas de coût de sous-processus, tests plus rapides.

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

public function test_dashboard_returns_correct_data(): void
{
    Concurrency::fake();

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

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

Ou dans `.env.testing` :

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

## Précautions

<AccordionGroup>
  <Accordion title="Limitations des closures">
    Les closures sont sérialisées puis passées au sous-processus.
    Vous ne pouvez pas capturer d'objets non sérialisables (connexions DB, ressources).
    Recréez les objets nécessaires à l'intérieur de la closure.

    ```php theme={null}
    // NG : capture un modèle Eloquent
    $user = User::find(1);
    Concurrency::run([
        fn () => $user->orders()->count(), // Peut ne pas être sérialisable
    ]);

    // OK : reconstruit à l'intérieur
    $userId = 1;
    Concurrency::run([
        fn () => Order::where('user_id', $userId)->count(),
    ]);
    ```
  </Accordion>

  <Accordion title="Overhead de `process`">
    Le démarrage des sous-processus a un coût. Pour des traitements très rapides (\< qq ms), la parallélisation peut être plus lente.
    Elle est efficace pour des tâches > 100 ms (HTTP, agrégations).
  </Accordion>

  <Accordion title="Limite de `fork`">
    `fork` clone le processus PHP : impossible en web (FPM, Apache).
    Utilisez-le en Artisan / worker.
  </Accordion>

  <Accordion title="Gestion des exceptions">
    Toute exception se propage via `run()`. Pour laisser les autres closures s'exécuter, gérez `try/catch` dans chaque closure.

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

## Prochaines étapes

<Card title="Files d'attente et jobs" icon="layer-group" href="/fr/queues">
  Découvrez comment exécuter des traitements en arrière-plan.
</Card>


## Related topics

- [Concurrence](/fr/packages/laravel-copilot-sdk/concurrency.md)
- [Client HTTP](/fr/http-client.md)
