There are some approaches how to execute queue:work
but I found them useless. Here is a solution for most shared hostings that:
- does not require additional route
- does not require remotely visiting website
- does not require shell access
- does not require cron access
- requires
bash
withflock
on server (for single execution protection) - requires PHP
exec
function available - requires shell
php
command (php-cli
installed on server) - runs in the background so makes no website slowdowns
- can be set to execute not more than once between minimum time span of $runEverySec seconds
1. Let's create new Middleware:
$ php artisan make:middleware QueueWorkMiddleware
2. Use it as global middleware in bootstrap/app.php
:
...
->withMiddleware(function (Middleware $middleware) {
$middleware->append(App\Http\Middleware\QueueWorkMiddleware::class);
})
...
3. Put contents to App/Middleware/QueueWorkMiddleware.php
:
<?php
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Symfony\Component\HttpFoundation\Response;
class QueueWorkMiddleware
{
// lock file that prevents too many executions
public $lockFile = 'queue.lock';
// log from queue command
public $logFile = 'queue.log';
// log from background exec command
public $execLogFile = 'exec.log';
// pid of executed command
public $pidFile = 'queue.pid';
// php command path
public $phpExec = '/usr/bin/php';
// queue:work command
public $queueCmd = 'artisan queue:work --stop-when-empty';
// minimum time in seconds between executions
public $runEverySec = 10;
/**
* Handle an incoming request.
*
* @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next
*/
public function handle(Request $request, Closure $next): Response
{
// get pid and lock file names
$pidFile = base_path()."/".$this->pidFile;
$lockFile = base_path()."/".$this->lockFile;
if(
// if there is no lock file and
!file_exists($lockFile) &&
// there is no pid file (queue was never executed before)
// or time between pidfile modification is more or equal $runEverySec
(!file_exists($pidFile) || (time()-filemtime(base_path()."/{$this->pidFile}") >= $this->runEverySec))
)
{
// do the work
$this->work();
}
return $next($request);
}
public function work() {
// file names
$basePath = base_path();
$lockFile = "{$basePath}/{$this->lockFile}";
$logFile = "{$basePath}/{$this->logFile}";
$execLogFile = "{$basePath}/{$this->execLogFile}";
$pidFile = base_path()."/".$this->pidFile;
// main queue command and lock file removal
$cmd = "{ {$this->phpExec} {$this->queueCmd} > {$logFile} 2>&1; rm {$lockFile}; }";
// go to base path and run command by flock (this guarantees single execution only!)
$cmd = "cd {$basePath} && flock -n {$lockFile} --command '{$cmd}'";
// execute command in background
exec(sprintf("%s > {$execLogFile} 2>&1 & echo $! >> %s", $cmd, $pidFile));
return true;
}
}
Top comments (0)