Tinkerwell 4 is out now! Get the most popular PHP Scratchpad application. Learn more

Go back to Blog

The new Process facade in Laravel 10

Marcel Pociot

Laravel 10's new Process helper

Did you ever need to invoke an external program from within your Laravel/PHP application? Chances are that you have used the great Symfony Process component for this.

It handles all the logic related to running processes as well as knowing about their output, errors, and execution state.

So for example if you would like to call "git status" from your PHP script, you could do this with Symfony's process component like this:

use Symfony\Component\Process\Process;

$process = new Process(['git', 'status']);
$process->run();

echo $process->getOutput();
Example in Tinkerwell

With Laravel 10 we now get some nice syntax sugar on top of this component, to make it behave more consistent to the existing Laravel API.

So lets take a look at how the same call looks like using Laravel 10s new Process facade.

$result = Process::run('git status');

echo $result->output();
Example in Tinkerwell

If you prefer to use the array syntax to build all elements of your process string, you can do this as well.

$result = Process::run(['git', 'status']);

echo $result->output();

Inspecting process results

Besides getting the output of a given process invocation, you can also retrieve more information about the process result in general. This includes the command that was executed, a boolean indicating if it was successful or failed as well as the exit code. All of this can be retrieved from the ProcessResult object that the run method returns.

$result = Process::run('git status');

$result->command();
$result->successful();
$result->failed();
$result->exitCode();
Example in Tinkerwell with Magic Comments

Error handling

By default, the Process facade does not throw any exceptions when your processes fail. In order to retrieve the output of an error, you may use the errorOutput method on the process result:

$result = Process::run('i-dont-exist');
echo $result->errorOutput();
Example in Tinkerwell

If you would prefer to have an exception thrown, you can instruct the Process facade to do so, by using the throw or throwIf methods.

$result = Process::run('i-dont-exist')->throw();

$result = Process::run('i-dont-exist')->throwIf(app()->runningInConsole());

Additionally, you may pass a closure to both of these methods, which will be invoked with the process result and the exceptions, prior to throwing it.

$result = Process::run('i-dont-exist')->throw(function ($result, $exception) {
    echo "An exception happened";
});

Process customizations

Working Directory

Just like the Symfony process component, you can also make a lot of adjustments to the process using Laravels new process facade.

By default, all processes start within the current working directory of your PHP script. This could be the public/index.php for any HTTP based scripts, as well as your artisan command/application base path. In order to change the current working directory, you can use the path method:

Process::path('/Users/marcelpociot/Code')->run('git status')->output();

Timeouts

The default timeout of all processes is 60 seconds. If a process takes up more time, a Illuminate\Process\Exceptions\ProcessTimedOutException will be thrown.

To change the default timeout, you may use the timeout method and pass the number of seconds the process may run. If you want to configure a timeout only for when the process does not return any output, you may use the idleTimeout method. And if you want your process to not have any timeout at all, you can configure the process using the forever method.

// The process may take a maximum of 120 seconds
Process::timeout(120)->run('git status');

// The process may take a maximum of 120 seconds
// Additionally, it will throw an exception if the process does not return any output for 10 seconds
Process::timeout(120)->idleTimeout(10)->run('git status');

// The process will run forever
Process::forever()->run('git status');

Environment variables

Processes invoked via the Process facade inherit all of the environment variables of your system by default. You can add additional environment variables using the env method:

Process::forever()
    ->env(['BASE_PATH' => __DIR__])
    ->run('echo $BASE_PATH')
    ->output();

There might be situations where you do not want to pass inherited environment variables to processes. By defining them as false, they won't be accessible within the invoked process:

Process::forever()
    ->env(['APP_KEY' => false])
    ->run('echo $APP_KEY')
    ->output();
Example in Tinkerwell

Asserting output

Another really cool feature of Laravel 10s new Process facade is the ability to quickly check if the output of a process contains a specific substring. For example, you might want to run "git status" in order to figure out if there are any changes in your codebase that need to be committed.

You can make use of the seeInOutput method to do this. this method returns a boolean whether the string was found in the output or not:

$noChanges = Process::run('git status')->seeInOutput('nothing to commit')

Streaming output

So far, we have used the output and errorOutput methods of the Process facade, which returns the whole buffered output at once. There might be a situation where you want to retrieve the output as it happens - so basically stream it from the process to PHP.

To achieve this with the new process facade, you can pass a callable to the run method, which will retrieve the output as it happens. In addition to the output itself, you will also retrieve the output type - which could be one of stdout or stderr depending on whether or not the output happened in the error output.

Process::run('git status', function ($type, $output) {
    echo $output;
});

Ignoring output

If you are not interested in retrieving the output of your process at all, you may use the quietly method to prevent the output from being buffered. This can highly reduce memory usage, if your process outputs a lot of data.

Process::quietly()->run('bash import.sh');

Process pools - running multiple processes

Laravel's new process facade also makes it really easy to run multiple processes in parallel.

Lets say that you need to run multiple bash scripts at once and you need to wait for all of them to finish, before you can continue with your script.

Laravel allows you to define so called "process pools".

$pool = Process::pool(function (Pool $pool) {
    $pool->as('first')->path(__DIR__)->command('ls -la');
    $pool->as('second')->path(app_path())->command('ls -la');
    $pool->as('third')->path(storage_path())->command('ls -la');
})->start(function (string $type, string $output, string $key) {
    // ...
});
 
$results = $pool->wait();

By giving names to each of our processes within the pool, we can then access their results like this:

echo $results['first']->output();
Example in Tinkerwell

Testing processes

Like other parts of the Laravel framework, the process facade is extremely easy to test.

Just as you can fake events, mails, notifications, etc. you can also fake process calls by calling the fake method on the process facade.

Process::fake();

Process::run('git status');

Process::assertRan('git status');
Example in Tinkerwell

This call is going to fake all of the processes that you would invoke through the new process facade. Sometimes you might not want to fake all processes, but only a specific subset of processes.

You can do this, by passing an array to the fake method. The key is the command that you want to fake, and the value can be a string representing the return value of this faked command.

Process::fake([
    'git *' => 'nothing to commit'
]);

echo Process::run('git status')->output();

Process::assertRan('git status');

Having fixed commands in the fake array might not always be useful as you might create dynamic commands. You can also use the * character as a placeholder:

Process::fake([
    'git *' => 'nothing to commit'
]);

echo Process::run('git status')->output();
echo Process::run('git log')->output();
Example in Tinkerwell

In addition to only returning the faked process output, you can also return a fake instance of a process result. This allows you to define the exit code, output, error output, etc. on the faked process calls:

Process::fake([
    'git *' => Process::result(
        output: 'Test output',
        errorOutput: 'Test error output',
        exitCode: 1,
    ),
]);

Process::run('git status')->output();
Process::run('git add')->errorOutput();
Process::run('git log')->exitCode();
Example in Tinkerwell

Digging deeper

As you can see, the new process facade helper in Laravel 10 is a fantastic addition to running and, most importantly, testing external processes from within your Laravel application.

If you want to learn more about the new process facade and all of its functionality, check out the official documentation.

Tinkerwell: The PHP Scratchpad

Write and debug your applications in an editor designed for fast iterations.

Learn more