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

# Ejecución de procesos externos en paquetes y Process::fake

> Con la facade Process de Laravel 13 se explican los argumentos de comandos externos, los timeouts, las excepciones de fallo, los fakes en pruebas y la coincidencia de comandos en array.

Cuando un paquete llama a comandos externos como `ffmpeg` o `git`, las diferencias de entorno de la aplicación que lo usa y los fallos se convierten directamente en el comportamiento del paquete. En lugar de llamar directamente a `shell_exec()` o `exec()`, ejecuta los comandos con la **facade Process** y sustitúyelos con `Process::fake()` en las pruebas.

> [!NOTE]
>
> Esta página se centra en Laravel 13 y revisa la implementación de Framework `v13.42.0`. Da por conocidos los [procesos](https://kawax.biz/es/processes.md) y las [pruebas de paquetes](https://kawax.biz/es/advanced/package-testing.md). La coincidencia de comandos en array que se trata aquí se basa en cómo Symfony Process, que Process usa internamente, convierte los argumentos.

## Por qué evitar la ejecución directa

Las llamadas directas a funciones no se pueden sustituir en las pruebas, y cada llamador gestiona los timeouts y los códigos de salida de forma distinta. Si pasas por la facade Process, unificas el registro de ejecuciones, los fakes, los timeouts y las excepciones en caso de fallo.

```mermaid theme={null}
flowchart LR
    A["Clase envoltorio<br>del paquete"] --> B["Process facade<br>PendingProcess"]
    B --> C{"¿Fake<br>registrado?"}
    C -->|Sí| D["FakeProcessResult<br>no se inicia ningún comando externo"]
    C -->|No| E["Iniciar el comando externo"]
    E --> F["ProcessResult"]
    D --> F
    F --> G["throw() convierte los fallos en excepciones"]
```

## Pasa los comandos como array

Un comando en forma de string pasa por la interpretación de `sh`, así que si incluye datos del usuario aparecen problemas con las comillas y riesgo de inyección. Dentro del paquete, pasa los argumentos como array.

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

El timeout por defecto de `PendingProcess` es de `60` segundos y el timeout de inactividad es `null`. Para conversiones largas indica `timeout()` de forma explícita y, en procesos cuya salida puede detenerse, combínalo con `idleTimeout()`.

## Excepciones y mensajes en caso de fallo

`ProcessResult::throw()` lanza `ProcessFailedException` cuando el código de salida no indica éxito. El mensaje incluye el comando ejecutado, el código de salida y las salidas estándar y de error.

> [!WARNING]
>
> Si pones secretos en los argumentos del comando, quedarán incluidos en el mensaje de `ProcessFailedException` y pueden aparecer en logs o en pantalla. No pases tokens como argumentos. Pásalos con `env()` o escríbelos en el archivo de configuración del comando.

Los timeouts se representan con `ProcessTimedOutException` y los timeouts de inactividad con `ProcessIdleTimedOutException`. Documenta para los usuarios del paquete qué excepciones lanzas y qué pueden esperar al capturarlas.

## Usa fakes en las pruebas

En las pruebas, sustituye los comandos externos con `Process::fake()`. `Process::result()` permite indicar la salida estándar, la salida de error y el código de salida.

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

Si pasas una closure a `assertRan()`, puedes comprobar `command`, `timeout`, `environment` y otras propiedades de `PendingProcess`. Los comandos en array se comparan con `$process->command` por coincidencia exacta, así que las pruebas detectan cambios en el orden o en los valores de los argumentos.

## Claves de fake para comandos en array

Internamente, Process compara las claves de `fake()` con el resultado de `getCommandline()` usando `Str::is()`. Los comandos en array se convierten en un string con comillas para la shell, así que un patrón de string como `'ffmpeg*'` no coincide. En las pruebas de comandos en array, usa una de estas opciones.

| Método | Cuándo usarlo | Precaución |
| - | - | - |
| La clave `'*'` | Devolver el mismo resultado sea cual sea el comando | Cuesta notar que se ha llamado a otro comando |
| `Process::assertRan()` | Verificar los argumentos después de la llamada | Prepara la respuesta del fake por separado |
| Fake con closure | Cambiar el resultado según los argumentos | La lógica se complica, así que acota el objetivo de la prueba |

## Evita comandos externos inesperados

Para detectar comandos externos que no deberían ejecutarse en las pruebas, usa `preventStrayProcesses()`. Los comandos que no coinciden con ningún fake lanzan `RuntimeException` y no llegan a iniciarse.

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

    Process::preventStrayProcesses();
}
```

## Si usas una Factory inyectada

Si el constructor del paquete recibe `Illuminate\Process\Factory`, comprueba que el estado de `Process::fake()` llega hasta la instancia inyectada. La facade guarda en caché la instancia resuelta y `swap()` reemplaza la instancia en el contenedor. En cambio, no puedes confiar en que el framework registre explícitamente `Factory` como compartida en el contenedor, así que compruébalo en una prueba.

Si el fake no llega a la instancia inyectada, sustituye la Factory de forma explícita en la prueba o diseña el paquete para que pase por la facade `Process`. En ambos casos, comprobar con `assertRan()` que la llamada quedó registrada te permite detectar una sustitución olvidada.

## Comprobaciones para el mantenimiento a largo plazo

| Comprobación | Objetivo |
| - | - |
| Prueba que fija el orden de los argumentos del array | Notar cuando cambia la posición de `-y` o de la entrada y la salida |
| Comprobar la excepción y el mensaje en caso de fallo | Mantener sin cambios la gestión de errores de los usuarios |
| Comprobar los valores por defecto de `timeout()` e `idleTimeout()` | Tratar un cambio de valores por defecto como un cambio de comportamiento para los usuarios |
| Activar `preventStrayProcesses()` | Detener en las pruebas el inicio de comandos externos no previstos |
| Comprobar que no se pasan secretos como argumentos | Evitar fugas en mensajes de excepción y logs |

## Fuentes primarias consultadas

* [Documentación oficial de Laravel: procesos](https://github.com/laravel/docs/blob/13.x/processes.md)
* [Documentación oficial de Laravel: Process::fake y assertRan](https://github.com/laravel/docs/blob/13.x/processes.md#faking-processes)
* [Process Factory: fake y assertRan](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Process/Factory.php)
* [PendingProcess: timeouts por defecto y coincidencia de fakes](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Process/PendingProcess.php)
* [ProcessResult: comportamiento de throw()](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Process/ProcessResult.php)
* [ProcessFailedException: estructura del mensaje](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Process/Exceptions/ProcessFailedException.php)
* [Facade: caché y swap()](https://github.com/laravel/framework/blob/v13.42.0/src/Illuminate/Support/Facades/Facade.php)


## Related topics

- [Temas avanzados](https://kawax.biz/es/advanced/index.md)
- [Jobs en cola de paquetes y compatibilidad al actualizar](https://kawax.biz/es/advanced/package-queue-jobs.md)
- [Registro y caché de listeners de eventos en paquetes](https://kawax.biz/es/advanced/package-events.md)
- [Sobrescritura y actualización de vistas de paquetes](https://kawax.biz/es/advanced/package-views.md)
- [Registro y pruebas de transportes de correo en paquetes](https://kawax.biz/es/advanced/package-mail-transports.md)


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