Job Queues
Sometimes you have tasks that are better run asynchronously. For example, you might need to perform some expensive processing or a slow api call that would take too long for a client facing web request.
Forme has a basic no frills job queue system for exactly this situation. This is nowhere near as full featured as libraries like Bernard (although there's nothing to stop you implementing that within Forme) or Laravel's Queues, but it works fine for straight forward use cases.
The Job Class
Jobs live in app/Jobs
, should implement Forme\Framework\Jobs\JobInterface
, and need the Forme\Framework\Jobs\Queueable
trait.
They're very similar to a controller, in that they have a public handle
method, which will get called when it's next up in the queue.
<?php
declare(strict_types=1);
namespace NameSpace\CoolestPlugin\Jobs;
use Forme\Framework\Jobs\Queueable;
use Forme\Framework\Jobs\JobInterface;
final class FooBarJob implements JobInterface
{
use Queueable;
public function handle(array $args = []): ?string
{
// do something expensive here
return "This will show up in the log file";
}
}
Dispatching Jobs
To dispatch a job (in other words to add it to the queue to run as soon as possible), you have two options. You can either do this via the general Queue
class, or via an instance of the specific Job
class.
// via Forme\Framework\Jobs\Queue
// in this case, you need to pass an associative array with the class name and the arguments to pass in to the handle method
$queue->dispatch([
'class' => 'NameSpace\\CoolestPlugin\\Jobs\\FooBarJob',
'arguments' => ['foo' => 'bar']
]);
// via the specific job, e.g. NameSpace\CoolestPlugin\Jobs\FooBarJob
// you need to pass an array of arguments for the handle method
$fooBarJob->dispatch(['foo' => 'bar']);
Scheduling Jobs
To schedule a job, the process is similar, but you need to call the schedule
method and pass in an additional next
argument.
// via Forme\Framework\Jobs\Queue
// in this case, you need to pass an associative array with the class name and the arguments to pass in to the handle method
$queue->schedule([
'class' => 'NameSpace\\CoolestPlugin\\Jobs\\FooBarJob',
'arguments' => ['foo' => 'bar'],
'next' => '1 hour' // something that carbon can parse, this will run after 1 hour has passed
]);
// via the specific job, e.g. NameSpace\CoolestPlugin\Jobs\FooBarJob
// same as queue, but you don't need to pass the class name
$fooBarJob->schedule([
'arguments' => ['foo' => 'bar'],
'next' => '1 hour' // something that carbon can parse, this will run after 1 hour has passed
]);
Recurring Jobs
For recurring jobs, you also need to call the schedule
method but you pass in the frequency
argument.
// via Forme\Framework\Jobs\Queue
// in this case, you need to pass an associative array with the class name and the arguments to pass in to the handle method
$queue->schedule([
'class' => 'NameSpace\\CoolestPlugin\\Jobs\\FooBarJob',
'arguments' => ['foo' => 'bar'],
'next' => 'now', // something that carbon can parse, the first dispatch will run asap
'frequency' => '1 week' // again something that carbon can parse, this will run once a week on the same day at the same time
]);
// via the specific job, e.g. NameSpace\CoolestPlugin\Jobs\FooBarJob
// same as queue, but you don't need to pass the class name
$fooBarJob->schedule([
'arguments' => ['foo' => 'bar'],
'next' => 'now', // something that carbon can parse, the first dispatch will run asap
'frequency' => '1 week' // again something that carbon can parse, this will run once a week on the same day at the same time
]);
TIP
You can stop a recurring job with the stop
method. Take a look below.
Starting (Unique) Recurring Jobs
You might need to start recurring jobs while ensuring that they are unique. For that you can use the start
method.
Starting a job is very similar to scheduling, frequency
is always required since by definition these are always recurring jobs. If a recurring job with the same class name already exists, nothing will happen.
// via Forme\Framework\Jobs\Queue
// in this case, you need to pass an associative array with the class name and the arguments to pass in to the handle method
$queue->start([
'class' => 'NameSpace\\CoolestPlugin\\Jobs\\FooBarJob',
'arguments' => ['foo' => 'bar'],
'next' => 'now', // something that carbon can parse, the first dispatch will run asap
'frequency' => '1 week' // again something that carbon can parse, this will run once a week on the same day at the same time
]);
// via the specific job, e.g. NameSpace\CoolestPlugin\Jobs\FooBarJob
// same as queue, but you don't need to pass the class name
$fooBarJob->start([
'arguments' => ['foo' => 'bar'],
'next' => 'now', // something that carbon can parse, the first dispatch will run asap
'frequency' => '1 week' // again something that carbon can parse, this will run once a week on the same day at the same time
]);
WARNING
The uniqueness is based on a combination of the classname and the arguments, which has its own limitations. If you find that you need some other way of referencing the job, for example by an arbitrary id or some kind of content hash, you will have to implement that yourself.
Stopping Recurring Jobs
To stop a recurring job, call the stop
method.
// via Forme\Framework\Jobs\Queue
// in this case, you need to pass an associative array with the class name and optionally the arguments
$queue->stop([
'class' => 'NameSpace\\CoolestPlugin\\Jobs\\FooBarJob',
'arguments' => ['foo' => 'bar'], // optional
]);
// via the specific job, e.g. NameSpace\CoolestPlugin\Jobs\FooBarJob
// same as queue, but you don't need to pass in the class name. You can optionall pass in the original arguments if you need to use them to identify the job
$fooBarJob->stop();
$fooBarJob->stop(['foo' => 'bar']); // optional arguments
TIP
The stop method will delete the first job it finds with that combination of class name and arguments. If you have multiple jobs with the same class name and arguments, you will need to call stop
multiple times.
Named queues
Sometimes you want to have multiple queues and be able to reference them by name. You can pass in a queue name to any of the above commands.
// dispatch
$queue->dispatch([
'class' => 'NameSpace\\CoolestPlugin\\Jobs\\FooBarJob',
'arguments' => ['foo' => 'bar'],
'queue_name' => 'baz',
]);
$fooBarJob->dispatch(['foo' => 'bar'], 'baz');
//schedule
$queue->schedule([
'class' => 'NameSpace\\CoolestPlugin\\Jobs\\FooBarJob',
'arguments' => ['foo' => 'bar'],
'next' => 'now',
'frequency' => '1 week',
'queue_name' => 'baz',
]);
$fooBarJob->schedule([
'arguments' => ['foo' => 'bar'],
'next' => 'now',
'frequency' => '1 week',
'queue_name' => 'baz',
]);
//start
$queue->start([
'class' => 'NameSpace\\CoolestPlugin\\Jobs\\FooBarJob',
'arguments' => ['foo' => 'bar'],
'next' => 'now',
'frequency' => '1 week',
'queue_name' => 'baz',
]);
// stop
$fooBarJob->start([
'arguments' => ['foo' => 'bar'],
'next' => 'now',
'frequency' => '1 week',
'queue_name' => 'baz',
]);
//stop
$queue->stop([
'class' => 'NameSpace\\CoolestPlugin\\Jobs\\FooBarJob',
'arguments' => ['foo' => 'bar'],
'queue_name' => 'baz',
]);
$fooBarJob->stop(queueName: 'baz'); // you must use `queueName` explicitly if not passing in the arguments
$fooBarJob->stop(['foo' => 'bar'], 'baz');
The queue:run command
You can use the queue:run
wrangle command to run the next job in the queue.
# default queue
php wrangle queue:run
# named queue
php wrangle queue:run baz
Forme will check if there are any jobs that haven't either started running or been completed and are scheduled to run, pick the next one, and run it.
Setting up Cron
In order for this to happen automatically, you'll need to set up a cron job to regularly run the queue:run
command.
* * * * * cd /path-to-your-project && php wrangle queue:run >> /dev/null 2>&1
You would normally set this to run once a minute as above, but it depends what is relevant for your app.
Code Generation
You can use the cli to generate a new Job.
forme make job FooBar
This will create a boilerplate file app/Jobs/FooBarJob.php
for you.