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

# Exécuter des processus externes dans un package et Process::fake

> Avec la facade Process de Laravel 13, explique les arguments des commandes externes, les timeouts, les exceptions d'échec, les fakes dans les tests et la correspondance des commandes en tableau.

Lorsqu'un package appelle des commandes externes comme `ffmpeg` ou `git`, les différences d'environnement de l'application qui l'utilise et les échecs deviennent directement le comportement du package. Au lieu d'appeler directement `shell_exec()` ou `exec()`, exécutez les commandes via la **facade Process** et remplacez-les par `Process::fake()` dans les tests.

> [!NOTE]
>
> Cette page cible Laravel 13 et vérifie l'implémentation de Framework `v13.42.0`. Elle suppose connus les [processus](https://kawax.biz/fr/processes.md) et les [tests de packages](https://kawax.biz/fr/advanced/package-testing.md). La correspondance des commandes en tableau traitée ici repose sur la conversion des arguments par Symfony Process, que Process utilise en interne.

## Pourquoi éviter l'exécution directe

Les appels directs de fonctions ne peuvent pas être remplacés dans les tests, et chaque appelant gère différemment les timeouts et les codes de sortie. En passant par la facade Process, vous unifiez l'enregistrement des exécutions, les fakes, les timeouts et les exceptions en cas d'échec.

```mermaid theme={null}
flowchart LR
    A["Classe wrapper<br>du package"] --> B["Process facade<br>PendingProcess"]
    B --> C{"Fake<br>enregistré ?"}
    C -->|Oui| D["FakeProcessResult<br>aucune commande externe lancée"]
    C -->|Non| E["Lancer la commande externe"]
    E --> F["ProcessResult"]
    D --> F
    F --> G["throw() transforme les échecs en exceptions"]
```

## Passer les commandes sous forme de tableau

Une commande sous forme de chaîne est interprétée par `sh`. Si elle contient une saisie utilisateur, des problèmes de guillemets et des risques d'injection apparaissent. À l'intérieur d'un package, passez les arguments sous forme de tableau.

```php theme={null}
<?php

namespace Vendor\Package\Support;

use Illuminate\Support\Facades\Process;

final class VideoConverter
{
    public function toMp4(string $source, string $target): void
    {
        Process::timeout(120)
            ->run(['ffmpeg', '-y', '-i', $source, $target])
            ->throw();
    }
}
```

Le timeout par défaut de `PendingProcess` est de `60` secondes et le timeout d'inactivité vaut `null`. Pour les conversions longues, indiquez explicitement `timeout()`, et pour les traitements dont la sortie peut s'arrêter, ajoutez `idleTimeout()`.

## Exceptions et messages en cas d'échec

`ProcessResult::throw()` lève une `ProcessFailedException` lorsque le code de sortie n'indique pas un succès. Le message contient la commande exécutée, le code de sortie, ainsi que la sortie standard et la sortie d'erreur.

> [!WARNING]
>
> Si vous placez des secrets dans les arguments de la commande, ils se retrouvent dans le message de `ProcessFailedException` et peuvent apparaître dans les logs ou à l'écran. Ne passez pas les tokens en arguments. Transmettez-les avec `env()` ou écrivez-les dans le fichier de configuration de la commande.

Les timeouts sont représentés par `ProcessTimedOutException` et les timeouts d'inactivité par `ProcessIdleTimedOutException`. Documentez pour les utilisateurs du package quelles exceptions vous levez et ce qu'ils peuvent attendre en les interceptant.

## Utiliser des fakes dans les tests

Dans les tests, remplacez les commandes externes par `Process::fake()`. `Process::result()` permet de définir la sortie standard, la sortie d'erreur et le code de sortie.

```php theme={null}
<?php

namespace Vendor\Package\Tests\Feature;

use Illuminate\Contracts\Process\ProcessResult;
use Illuminate\Process\Exceptions\ProcessFailedException;
use Illuminate\Process\PendingProcess;
use Illuminate\Support\Facades\Process;
use Vendor\Package\Support\VideoConverter;
use Vendor\Package\Tests\TestCase;

class VideoConverterTest extends TestCase
{
    public function test_it_runs_ffmpeg_with_the_expected_arguments(): void
    {
        Process::fake([
            '*' => Process::result(output: 'converted'),
        ]);

        (new VideoConverter)->toMp4('/tmp/in.mov', '/tmp/out.mp4');

        Process::assertRan(
            fn (PendingProcess $process, ProcessResult $result) => $process->command === ['ffmpeg', '-y', '-i', '/tmp/in.mov', '/tmp/out.mp4']
        );
    }

    public function test_it_fails_when_ffmpeg_exits_with_an_error(): void
    {
        Process::fake([
            '*' => Process::result(errorOutput: 'Invalid data', exitCode: 1),
        ]);

        $this->expectException(ProcessFailedException::class);

        (new VideoConverter)->toMp4('/tmp/in.mov', '/tmp/out.mp4');
    }
}
```

En passant une closure à `assertRan()`, vous pouvez vérifier `command`, `timeout`, `environment` et d'autres propriétés de `PendingProcess`. Les commandes en tableau sont comparées à `$process->command` par égalité exacte, de sorte que les tests détectent les changements d'ordre ou de valeur des arguments.

## Clés de fake pour les commandes en tableau

En interne, Process compare les clés de `fake()` au résultat de `getCommandline()` avec `Str::is()`. Les commandes en tableau sont converties en chaîne entre guillemets pour le shell, donc un motif de chaîne comme `'ffmpeg*'` ne correspond pas. Pour tester des commandes en tableau, utilisez l'une des approches suivantes.

| Méthode | Quand l'utiliser | Point d'attention |
| - | - | - |
| La clé `'*'` | Renvoyer le même résultat quelle que soit la commande | Difficile de remarquer qu'une autre commande a été appelée |
| `Process::assertRan()` | Vérifier les arguments après l'appel | Préparer la réponse du fake séparément |
| Fake avec closure | Faire varier le résultat selon les arguments | La logique se complique, limitez donc l'objectif du test |

## Empêcher les commandes externes inattendues

Pour détecter les commandes externes qui ne devraient pas s'exécuter dans les tests, utilisez `preventStrayProcesses()`. Les commandes qui ne correspondent à aucun fake lèvent une `RuntimeException` et ne sont jamais réellement lancées.

```php theme={null}
protected function setUp(): void
{
    parent::setUp();

    Process::preventStrayProcesses();
}
```

## Si vous utilisez une Factory injectée

Si le constructeur de votre package reçoit `Illuminate\Process\Factory`, vérifiez que l'état de `Process::fake()` atteint bien l'instance injectée. La facade met en cache l'instance résolue, et `swap()` remplace l'instance dans le conteneur. En revanche, vous ne pouvez pas compter sur un enregistrement explicite par le framework pour que `Factory` soit partagée dans le conteneur : vérifiez-le donc dans un test.

Si le fake n'atteint pas l'instance injectée, remplacez explicitement la Factory dans le test ou concevez le package pour qu'il passe par la facade `Process`. Dans les deux cas, vérifier avec `assertRan()` que l'appel a été enregistré permet de détecter un remplacement oublié.

## Vérifications à ajouter pour la maintenance à long terme

| Vérification | Objectif |
| - | - |
| Test qui fige l'ordre des arguments du tableau | Remarquer un changement de position de `-y` ou de l'entrée et de la sortie |
| Vérifier l'exception et le message en cas d'échec | Préserver la gestion des erreurs des utilisateurs |
| Vérifier les valeurs par défaut de `timeout()` et `idleTimeout()` | Traiter un changement de valeur par défaut comme un changement de comportement pour les utilisateurs |
| Activer `preventStrayProcesses()` | Bloquer dans les tests le lancement de commandes externes imprévues |
| Vérifier qu'aucun secret n'est passé en argument | Éviter les fuites dans les messages d'exception et les logs |

## Sources primaires consultées

* [Documentation officielle de Laravel : processus](https://github.com/laravel/docs/blob/13.x/processes.md)
* [Documentation officielle de Laravel : Process::fake et assertRan](https://github.com/laravel/docs/blob/13.x/processes.md#faking-processes)
* [Process Factory : fake et assertRan](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Process/Factory.php)
* [PendingProcess : timeouts par défaut et correspondance des fakes](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Process/PendingProcess.php)
* [ProcessResult : comportement de throw()](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Process/ProcessResult.php)
* [ProcessFailedException : structure du message](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Process/Exceptions/ProcessFailedException.php)
* [Facade : cache et swap()](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Support/Facades/Facade.php)


## Related topics

- [Sujets avancés](https://kawax.biz/fr/advanced/index.md)
- [Client HTTP et tests dans un package](https://kawax.biz/fr/advanced/package-http-client.md)
- [Enregistrer des drivers Storage dans un package et les résoudre à nouveau](https://kawax.biz/fr/advanced/package-filesystem-drivers.md)
- [Enregistrer et tester des transports de mail dans un package](https://kawax.biz/fr/advanced/package-mail-transports.md)
- [Enregistrement et maintenance des tâches planifiées d'un package](https://kawax.biz/fr/advanced/package-scheduling.md)


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.