Table of contents
- Introduction
- Challenge with Shared hosting services
- Processing queues on shared hosting
- Setting up a listener
Introduction
PHP out of the box runs each line of code in a sequential order (from top to bottom). This means until the first operation is done executing the next one will have to wait.
Generally, this is not a problem since most tasks don't take that long to complete, but other operations like sending out an email might noticeably take a bit of time.
Even worse is the fact sending out an email depends on making an external request to a third-party service like an SMTP server and the time to execution might vary and is unpredictable. This means users could be waiting a while to get feedback after a form submission.
Frameworks like Laravel [↗] provide Queues [↗] that allow developers to defer tasks to run in the background.
When a task to be queued is encountered during the processing of a user's request on the main process, the details of the task are stored for later processing on a separate process.
Laravel provides an Artisan [↗] command php artisan queue:work
, this starts a separate PHP process that goes through the stored queues and processes them.
Challenge with Shared hosting services
There are two main challenges with starting and keeping a queue worker alive on shared hosting.
The first challenge is starting the queue in the first place. Depending on your service provider, you might not even have shell access to the server to even run the queue worker command.
The second problem is restarting the queue worker in case it stops running. This is typically done with a system manager like Systemctl [↗]. Being able to run such a utility tool also depends on the level of access you have.
In the next section, we will look at a workaround.
Processing queues on shared hosting
The main challenge with shared hosting when it comes to running queues is the limited terminal access we have.
However, we do not need access to the actual terminal. We can access the terminal from within our application using PHP's built-in shell_exec [↗] function. This function allows us to execute shell commands as part of our code.
So in theory we can run shell_exec("php artisan queue:work")
to run our queues, but we need to modify this a bit.
For one, running the above command will still block our main thread because it waits for the queue to complete. Since we don't need the result we can throw that into the void. shell_exec("php artisan queue:work > /dev/null 2>/dev/null &")
.
There is however another problem. We can never tell when the queue stops running. For this reason, we will want to run the queue right after it is scheduled. We can also run the queue once instead of keeping it alive.
Running a queue
01: ...
02: Mail::to([$submission['email']])->send(new VerificationEmail($submission));
03: shell_exec("php artisan queue:work --once > /dev/null 2>/dev/null &");
we can go a step further and set this up as a global listener event [↗]. This way we don't have to remember to add the shell_exec
line after every queueable task.
Setting up a listener
We can set up a listener event to trigger our shell command whenever a new task is queued.
To do that we need to create a listener: php artisan make:listener JobQueuedListener
.
Open up app/Listeners/JobQueuedListener.php
and replace it with the snippet below.
JobQueuedListener.php
01: <?php
02:
03: namespace App\Listeners;
04:
05: use Illuminate\Contracts\Queue\ShouldQueue;
06: use Illuminate\Queue\InteractsWithQueue;
07: use Illuminate\Queue\Events\JobQueued;
08:
09: class JobQueuedListener
10: {
11: /**
12: * Create the event listener.
13: */
14: public function __construct()
15: {
16: //
17: }
18:
19: /**
20: * Handle the event.
21: */
22: public function handle(JobQueued $event): void
23: {
24: $php = PHP_BINARY;
25: $artisanPath = base_path('artisan');
26: shell_exec("$php $artisanPath queue:work --once > /dev/null 2>/dev/null &");
27: }
28: }
29:
In the snippet above we fetch the exact PHP path line 24 [→] to use. Since we are running on a shared host we can't assume just typing out php
will work.
Now that we have our listener, we need to register it in app/Providers/EventServiceProvider.php
to befired whenever a job is queued, i.e.: Illuminate\Queue\Events\JobQueued
EventServiceProvider.php
01: ...
02: /**
03: * The event listener mappings for the application.
04: *
05: * @var array
06: */
07: protected $listen = [
08: 'Illuminate\Queue\Events\JobQueued' => [
09: 'App\Listeners\JobQueuedListener',
10: ],
11: ];
From this point on, any queued task you create will be run immediately.
01: Mail::to([$submission['email']])->send(new VerificationEmail($submission));
The best part is, by unregistering your custom implementation from app/Providers/EventServiceProvider.php
everything will reset to the default behavior in Laravel.
Also don't forget to set your queued connection (
QUEUE_CONNECTION
) in your .env file to a different driver other thansync
.
Here is another article you might like 😊 Removing "Public" From Laravel URL Routes On Hostinger