Run Commands in Laravel Using Processes

June 21, 2023
Written by
Kenneth Ekandem
Contributor
Opinions expressed by Twilio contributors are their own
Reviewed by

Run Commands in Laravel Using Processes

Laravel built a facade around Symfony's highly efficient Process component which allows commands to be executed outside the Laravel environment.

Processes allow shell commands like grep and commands that call on external services like GitHub to be run asynchronously without potentially breaking a specific flow of execution owing to the synchronous nature of PHP. This happens in real time, with callbacks and exception handling.

Let’s examine what that means with this basic code below.

use Illuminate\Support\Facades\Process;
 
$result = Process::run('ls -la');
 
return $result->output();

In the above code, the Process facade is called and used to run the Linux ls command which lists all the files in a directory, including hidden files, which is then printed.

Prerequisites

  1. Knowledge of PHP
  2. PHP 8.2
  3. Composer installed globally
  4. Postman or curl
  5. Prior experience with Laravel and some command-line experience would be helpful but are not required

Use Cases

Running CLI commands: CLI commands are text-based user interfaces used to run commands and interact with computer files and functions. Processes allow CLI commands like the example given above to be run in the Laravel application.

Asynchronous commands: Processes can run asynchronously without interrupting other functions on the Laravel application. Furthermore, output from an asynchronous process can still be output without breaking the process.

Piping multiple processes: Piping multiple commands can come in handy when you want a later command to run when a previous one has been completed. With processes, multiple commands like the one below, for example, can be run and executed synchronously.

$result = Process::pipe(function (Pipe $pipe) {
  $pipe->command('ls -la');
  $pipe->command('grep -i "PHP"');
});

As observed, the first command to run is the ls -la command that lists out the files in the directory and then the grep -i PHP command to check and fetch files that contain the text: "php".

Concurrent functions: Concurrent tasks can be managed simultaneously using a process. This allows multiple commands to be run simultaneously using the custom function pool. With the pool() function, commands are run as standalone and run asynchronously.

Below is an example of multiple scripts being started to import files.

use Illuminate\Process\Pool;
use Illuminate\Support\Facades\Process;
 
$pool = Process::pool(function (Pool $pool) {
    $pool->path(__DIR__)->command('bash import-1.sh');
    $pool->path(__DIR__)->command('bash import-2.sh');
    $pool->path(__DIR__)->command('bash import-3.sh');
})->start(function (string $type, string $output, int $key) {
    // ...
});
 
$results = $pool->wait(); 

Testing: Processes like all Laravel packages can access Faker to run command tests in the Laravel environment. Using fake processes can instruct Laravel to use dummy data to start a command and return the data.

Scaffold a Laravel application

To test out the capabilities of Processes in Laravel, we will first have to install and set up a Laravel application and then install the Symfony Process package in the project. Create a new project using the below command.

composer create-project laravel/laravel laravel_processes

When the installation is complete, move into the folder and start the application with the following commands.

cd laravel_processes
php artisan serve

Your application should be running at http://localhost:8000

Next, in a new terminal session, install the Symfony process package using the below command

composer require symfony/process

Add process support

By default, starting a process requires the run() function to start synchronous functions on the Laravel framework while the start() function is reserved for asynchronous functions. During the course of this tutorial we will break that down, for the meantime we will create a basic function using a test controller.

Create a controller

The controller will contain functions that, when initiated, will start the process functions embedded in them. To generate a controller, run the below command.

php artisan make:controller ProcessController

This will create a file in app/Http/Controllers/ProcessController.php. Open the file and replace the existing code with the code below.

<?php

namespace App\Http\Controllers;

use Illuminate\Http\Request;
use Illuminate\Support\Facades\Process;

class ProcessController extends Controller
{
    public function test() {
      $result = Process::run('ls -la');
      return $result->output();
    }
}

Create a route

Now that a controller has been created to invoke the process function, we can create a route to initiate the controller. To do that, go to routes/api.php and add a new GET route by adding the following code to the end of the file.

Route::get("/test", [App\Http\Controllers\ProcessController::class, 'test']);

Test the application

Testing can be done using curl by running the below command.

curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://127.0.0.1:8000/api/test

You should see output similar to the example below.

HTTP/1.1 200 OK
Host: 127.0.0.1:8001
Date: Fri, 16 Jun 2023 12:10:53 GMT
Connection: close
X-Powered-By: PHP/8.1.18
Content-Type: text/html; charset=UTF-8
Cache-Control: no-cache, private
Date: Fri, 16 Jun 2023 12:10:53 GMT
X-RateLimit-Limit: 60
X-RateLimit-Remaining: 59
Access-Control-Allow-Origin: *

total 20
drwxrwxr-x  2 settermjd settermjd 4096 Jun  1 18:12 .
drwxrwxr-x 13 settermjd settermjd 4096 Jun 16 14:08 ..
-rw-rw-r--  1 settermjd settermjd  603 Jun  1 18:12 .htaccess
-rw-rw-r--  1 settermjd settermjd        0 Jun  1 18:12 favicon.ico
-rw-rw-r--  1 settermjd settermjd 1710 Jun  1 18:12 index.php
-rw-rw-r--  1 settermjd settermjd   24 Jun  1 18:12 robots.txt

Handle errors

With processes, errors can be logged back into Laravel. These errors can then be handled internally or broadcast to the application user. This is efficient because external commands can be run and their results can affect the flow of activity in a Laravel application or the error is just logged.

In the below instance, we will try to initiate a process that will fail. This will output the error response received from the CLI.

In app/Http/Controllers/ProcessController.php, create a new function named errorResponse(). Then include the function errorOuput() in the doomed-to-fail process, by defining it as follows.

public function error() {
  $result = Process::run("i-dont-exit");
  return $result->errorOutput();
}

Next, add a route to test the error response in routes/api.php

Route::get('/error-handling', [ProcessController::class, 'error']);

Then proceed to test the function using curl.

curl -i -H "Accept: application/json" -H "Content-Type: application/json" -X GET http://127.0.0.1:8000/api/error-handling

In the above code, errorOutput() was used to output the error gotten back from the process. A result like the one below should be the feedback you get from the terminal.

bash
sh: i-dont-exist: command not found

Postman can also be used to run this test.

That's how to run commands in Laravel using processes

In this article, we went through what is a process in Laravel, its use cases, and how to run and handle external commands and return errors. You can look in-depth into Symfony other components that allow efficient programming on Laravel. I hope you use processes to run checks on your CLI commands to get appropriate data for better programming.

Kenneth Ekandem is a full-stack developer from Nigeria currently in the blockchain space, but interested in learning everything computer science has to offer. He'd love to go to space one day and own his own vlogging channel to teach the next generation of programmers.