diff --git a/src/queue/src/DeferredQueue.php b/src/queue/src/DeferredQueue.php index b29bb5fa6..1457747ae 100644 --- a/src/queue/src/DeferredQueue.php +++ b/src/queue/src/DeferredQueue.php @@ -4,6 +4,9 @@ namespace Hypervel\Queue; +use DateInterval; +use DateTimeInterface; +use Hypervel\Coordinator\Timer; use Hypervel\Engine\Coroutine; use Throwable; @@ -16,6 +19,18 @@ class DeferredQueue extends SyncQueue */ protected $exceptionCallback; + /** + * Create a new deferred queue instance. + */ + public function __construct( + protected ?bool $dispatchAfterCommit = false, + protected ?Timer $timer = null + ) { + if (! $this->timer) { + $this->timer = new Timer; + } + } + /** * Push a new job onto the queue. */ @@ -35,6 +50,17 @@ public function push(object|string $job, mixed $data = '', ?string $queue = null return null; } + /** + * Push a new job onto the queue after (n) seconds. + */ + public function later(DateInterval|DateTimeInterface|int $delay, object|string $job, mixed $data = '', ?string $queue = null): mixed + { + return $this->timer->after( + (float) $this->secondsUntil($delay), + fn () => $this->deferJob($job, $data, $queue) + ); + } + /** * Set the exception callback for the deferred queue. */ diff --git a/tests/Queue/QueueDeferredQueueTest.php b/tests/Queue/QueueDeferredQueueTest.php index 2ed5b356b..8d7e874e2 100644 --- a/tests/Queue/QueueDeferredQueueTest.php +++ b/tests/Queue/QueueDeferredQueueTest.php @@ -4,15 +4,18 @@ namespace Hypervel\Tests\Queue; +use DateInterval; use Exception; use Hypervel\Container\Container; use Hypervel\Contracts\Events\Dispatcher; use Hypervel\Contracts\Queue\QueueableEntity; use Hypervel\Contracts\Queue\ShouldQueueAfterCommit; +use Hypervel\Coordinator\Timer; use Hypervel\Database\DatabaseTransactionsManager; use Hypervel\Queue\DeferredQueue; use Hypervel\Queue\InteractsWithQueue; use Hypervel\Queue\Jobs\SyncJob; +use Hypervel\Support\Carbon; use Hypervel\Tests\TestCase; use Mockery as m; @@ -90,6 +93,79 @@ public function testItAddsATransactionCallbackForInterfaceBasedAfterCommitJobs() run(fn () => $deferred->push(new DeferredQueueAfterCommitInterfaceJob)); } + public function testLaterSchedulesJobWithDelay() + { + $timer = m::mock(Timer::class); + $timer->shouldReceive('after') + ->once() + ->with(5.0, m::type('Closure')) + ->andReturnUsing(function ($delay, $callback) { + $callback(); + return 1; + }); + + $deferred = new DeferredQueue(timer: $timer); + $deferred->setConnectionName('deferred'); + $deferred->setContainer($this->getContainer()); + + unset($_SERVER['__deferred.later.test']); + + run(fn () => $deferred->later(5, DeferredQueueLaterTestHandler::class, ['foo' => 'bar'])); + + $this->assertInstanceOf(SyncJob::class, $_SERVER['__deferred.later.test'][0]); + $this->assertEquals(['foo' => 'bar'], $_SERVER['__deferred.later.test'][1]); + } + + public function testLaterWithDateInterval() + { + $timer = m::mock(Timer::class); + $timer->shouldReceive('after') + ->once() + ->with(10.0, m::type('Closure')) + ->andReturnUsing(function ($delay, $callback) { + $callback(); + return 1; + }); + + $deferred = new DeferredQueue(timer: $timer); + $deferred->setConnectionName('deferred'); + $deferred->setContainer($this->getContainer()); + + unset($_SERVER['__deferred.later.interval.test']); + + run(fn () => $deferred->later(new DateInterval('PT10S'), DeferredQueueLaterIntervalTestHandler::class, ['baz' => 'qux'])); + + $this->assertInstanceOf(SyncJob::class, $_SERVER['__deferred.later.interval.test'][0]); + $this->assertEquals(['baz' => 'qux'], $_SERVER['__deferred.later.interval.test'][1]); + } + + public function testLaterWithDateTime() + { + Carbon::setTestNow('2024-01-01 12:00:00'); + + $timer = m::mock(Timer::class); + $timer->shouldReceive('after') + ->once() + ->with(15.0, m::type('Closure')) + ->andReturnUsing(function ($delay, $callback) { + $callback(); + return 1; + }); + + $deferred = new DeferredQueue(timer: $timer); + $deferred->setConnectionName('deferred'); + $deferred->setContainer($this->getContainer()); + + unset($_SERVER['__deferred.later.datetime.test']); + + run(fn () => $deferred->later(Carbon::parse('2024-01-01 12:00:15'), DeferredQueueLaterDateTimeTestHandler::class, ['test' => 'data'])); + + $this->assertInstanceOf(SyncJob::class, $_SERVER['__deferred.later.datetime.test'][0]); + $this->assertEquals(['test' => 'data'], $_SERVER['__deferred.later.datetime.test'][1]); + + Carbon::setTestNow(); + } + protected function getContainer(): Container { return new Container; @@ -154,3 +230,27 @@ public function handle() { } } + +class DeferredQueueLaterTestHandler +{ + public function fire(SyncJob $job, mixed $data): void + { + $_SERVER['__deferred.later.test'] = func_get_args(); + } +} + +class DeferredQueueLaterIntervalTestHandler +{ + public function fire(SyncJob $job, mixed $data): void + { + $_SERVER['__deferred.later.interval.test'] = func_get_args(); + } +} + +class DeferredQueueLaterDateTimeTestHandler +{ + public function fire(SyncJob $job, mixed $data): void + { + $_SERVER['__deferred.later.datetime.test'] = func_get_args(); + } +}