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();
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();
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();
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();
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();
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();
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');
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();
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();
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.