Skip to content

Commit

Permalink
feat(Queue): Add ability to dispatch job in testable way
Browse files Browse the repository at this point in the history
  • Loading branch information
pionl committed Feb 28, 2024
1 parent a2f1d4d commit 046a9e3
Show file tree
Hide file tree
Showing 30 changed files with 1,150 additions and 14 deletions.
16 changes: 2 additions & 14 deletions src/Console/Jobs/AbstractUniqueJob.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,29 +4,17 @@

namespace LaraStrict\Console\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldBeUnique;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;
use LaraStrict\Queue\Jobs\Job;

abstract class AbstractUniqueJob implements ShouldQueue, ShouldBeUnique
abstract class AbstractUniqueJob extends Job implements ShouldBeUnique
{
use Queueable;
use InteractsWithQueue;

public int $tries = 30;

public int $uniqueFor = 10;

public int $maxExceptions = 1;

public function __construct()
{
if ($this->queue === null) {
$this->queue = 'default';
}
}

abstract public function uniqueId(): string;

/**
Expand Down
2 changes: 2 additions & 0 deletions src/Core/LaraStrictServiceProvider.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
use LaraStrict\Providers\Actions\RunAppServiceProviderPipesAction;
use LaraStrict\Providers\Pipes\PreventLazyLoadingPipe;
use LaraStrict\Providers\Pipes\SetFactoryResolvingProviderPipe;
use LaraStrict\Queue\QueueServiceProvider;
use LaraStrict\Testing\TestServiceProvider;

class LaraStrictServiceProvider extends AbstractBaseServiceProvider
Expand All @@ -42,6 +43,7 @@ public function register(): void
$this->app->register(TestServiceProvider::class);
$this->app->register(DockerServiceProvider::class);
$this->app->register(LogServiceProvider::class);
$this->app->register(QueueServiceProvider::class);

$this->app->singleton(ImplementsService::class, ImplementsService::class);

Expand Down
11 changes: 11 additions & 0 deletions src/Exceptions/Exceptions/LogicException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Exceptions\Exceptions;

use LogicException as BaseException;

final class LogicException extends BaseException
{
}
38 changes: 38 additions & 0 deletions src/Queue/Actions/DispatchChainJobsAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Queue\Actions;

use LaraStrict\Queue\Contracts\DispatchChainJobsActionContract;
use LaraStrict\Queue\Contracts\DispatchJobActionContract;

class DispatchChainJobsAction implements DispatchChainJobsActionContract
{
public function __construct(
private readonly DispatchJobActionContract $dispatchJobAction
) {
}

public function execute(array $jobs): bool
{
if ($jobs === []) {
return false;
}

if (count($jobs) === 1) {
return $this->dispatchJobAction->execute(reset($jobs));
}

$mainJob = array_shift($jobs);

// After main job is done, chain our jobs
$mainJob->chain($jobs);

// Set the chain queue
$mainJob->chainQueue = $mainJob->queue;

// Dispatch chained job
return $this->dispatchJobAction->execute($mainJob);
}
}
19 changes: 19 additions & 0 deletions src/Queue/Actions/DispatchJobAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Queue\Actions;

use LaraStrict\Queue\Contracts\DispatchJobActionContract;
use LaraStrict\Queue\Jobs\Job;

final class DispatchJobAction implements DispatchJobActionContract
{
public function execute(Job $job): bool
{
// Laravel requires PendingDispatch instance to be used for dispatching jobs with ShouldBeUnique :(
dispatch($job);

return true;
}
}
36 changes: 36 additions & 0 deletions src/Queue/Actions/RunJobAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Queue\Actions;

use Illuminate\Console\Command;
use Illuminate\Contracts\Container\Container;
use LaraStrict\Queue\Contracts\RunJobActionContract;
use LaraStrict\Queue\Exceptions\MethodInJobIsNotDefinedException;
use LaraStrict\Queue\Interfaces\UsesCommandInterface;
use LaraStrict\Queue\Jobs\Job;

class RunJobAction implements RunJobActionContract
{
public function __construct(
private readonly Container $container
) {
}

public function execute(Job $job, ?Command $command = null, string $method = null): mixed
{
if ($command !== null && $job instanceof UsesCommandInterface) {
$job->setCommand($command);
}

$method ??= 'handle';
$call = [$job, $method];

if (is_callable($call) === false) {
throw new MethodInJobIsNotDefinedException($method, $job::class);
}

return $this->container->call($call);
}
}
49 changes: 49 additions & 0 deletions src/Queue/Actions/RunOrQueueJobAction.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Queue\Actions;

use Closure;
use Illuminate\Console\Command;
use LaraStrict\Queue\Contracts\DispatchJobActionContract;
use LaraStrict\Queue\Contracts\RunJobActionContract;
use LaraStrict\Queue\Contracts\RunOrQueueJobActionContract;
use LaraStrict\Queue\Jobs\Job;

class RunOrQueueJobAction implements RunOrQueueJobActionContract
{
public function __construct(
private readonly RunJobActionContract $runJobAction,
private readonly DispatchJobActionContract $dispatchJobAction
) {
}

/**
* Allows to run or queue job based on the command option queue parameter or not providing a $command.
*
* @param Closure(Job):void|null $setupBeforeRun
* @param bool|null $shouldQueue You can force to queue the job even if the command option queue is set.
*
* @return mixed If the job is queued, it returns null, otherwise it returns the result of the job execution.
*/
public function execute(
Job $job,
?Command $command = null,
?Closure $setupBeforeRun = null,
?bool $shouldQueue = null,
): mixed {
if ($shouldQueue === true
|| $shouldQueue === null && ($command === null || $command->option('queue') === true)) {
$this->dispatchJobAction->execute($job);

return null;
}

if ($setupBeforeRun !== null) {
$setupBeforeRun($job);
}

return $this->runJobAction->execute(job: $job, command: $command);
}
}
22 changes: 22 additions & 0 deletions src/Queue/Concerns/UsesCommand.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Queue\Concerns;

use Illuminate\Console\Command;

trait UsesCommand
{
private ?Command $command = null;

public function setCommand(Command $command): void
{
$this->command = $command;
}

public function getCommand(): ?Command
{
return $this->command;
}
}
23 changes: 23 additions & 0 deletions src/Queue/Contracts/DispatchChainJobsActionContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Queue\Contracts;

use LaraStrict\Queue\Jobs\Job;

interface DispatchChainJobsActionContract
{
/**
* Dispatches given chain of jobs (first job is used as main job and the rest are chained to it).
*
* If only one job is given, it will be dispatched directly.
*
* Ensures that the chainQueue is set to the main job's queue.
*
* @param Job[] $jobs
*
* @return bool True if the job was dispatched, false if the job was not dispatched.
*/
public function execute(array $jobs): bool;
}
16 changes: 16 additions & 0 deletions src/Queue/Contracts/DispatchJobActionContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Queue\Contracts;

use LaraStrict\Queue\Jobs\Job;

interface DispatchJobActionContract
{
/**
* Dispatch a job to the queue.
* - Wraps a dispatch() method. It is used to
*/
public function execute(Job $job): bool;
}
19 changes: 19 additions & 0 deletions src/Queue/Contracts/RunJobActionContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Queue\Contracts;

use Illuminate\Console\Command;
use LaraStrict\Queue\Jobs\Job;

interface RunJobActionContract
{
/**
* Runs the job using container->call() method. If the job is an instance of UsesCommandInterface, it will set the
* command. that you can use for advanced output.
*
* @return mixed Returns the result of the job
*/
public function execute(Job $job, ?Command $command = null): mixed;
}
28 changes: 28 additions & 0 deletions src/Queue/Contracts/RunOrQueueJobActionContract.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Queue\Contracts;

use Closure;
use Illuminate\Console\Command;
use LaraStrict\Queue\Jobs\Job;

interface RunOrQueueJobActionContract
{
/**
* Allows to run or queue job based on the command option queue parameter or not providing a $command.
*
* @param Closure(Job):void|null $setupBeforeRun
* @param bool|null $shouldQueue You can force to queue the job even if the command option queue is
* set.
*
* @return mixed If the job is queued, it returns null, otherwise it returns the result of the job execution.
*/
public function execute(
Job $job,
?Command $command = null,
?Closure $setupBeforeRun = null,
?bool $shouldQueue = null
): mixed;
}
23 changes: 23 additions & 0 deletions src/Queue/Exceptions/MethodInJobIsNotDefinedException.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Queue\Exceptions;

use RuntimeException;
use Throwable;

final class MethodInJobIsNotDefinedException extends RuntimeException
{
/**
* @param class-string $jobClass
*/
public function __construct(string $method, string $jobClass, int $code = 0, ?Throwable $previous = null)
{
parent::__construct(sprintf(
'Given job <%s> does not contain desired method <%s>',
$jobClass,
$method
), $code, $previous);
}
}
19 changes: 19 additions & 0 deletions src/Queue/Interfaces/UsesCommandInterface.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Queue\Interfaces;

use Illuminate\Console\Command;

/**
* Implementations of UsesCommand trait
*
* @see \LaraStrict\Queue\Concerns\UsesCommand
*/
interface UsesCommandInterface
{
public function setCommand(Command $command): void;

public function getCommand(): ?Command;
}
24 changes: 24 additions & 0 deletions src/Queue/Jobs/Job.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
<?php

declare(strict_types=1);

namespace LaraStrict\Queue\Jobs;

use Illuminate\Bus\Queueable;
use Illuminate\Contracts\Queue\ShouldQueue;
use Illuminate\Queue\InteractsWithQueue;

abstract class Job implements ShouldQueue
{
use Queueable;
use InteractsWithQueue;

public function __construct()
{
// When queueing a job with schedule then the queue is not set by Laravel, this will
// fix it.
if ($this->queue === null) {
$this->queue = 'default';
}
}
}
Loading

0 comments on commit 046a9e3

Please sign in to comment.