Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(Queue): Add ability to dispatch job in testable way #85

Merged
merged 1 commit into from
Feb 28, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading