diff --git a/docs/basic-usage.md b/docs/basic-usage.md index 3204e39..b91ae37 100644 --- a/docs/basic-usage.md +++ b/docs/basic-usage.md @@ -160,6 +160,12 @@ service('queue')->push('emails', 'email', ['message' => 'Email message goes here We will be pushing `email` job to the `emails` queue. +As a result of calling the `push()` method, you will receive a `QueuePushResult` object, which you can inspect if needed. It provides the following information: + +- `getStatus()`: Indicates whether the job was successfully added to the queue. +- `getJobId()`: Returns the ID of the job that was added to the queue. +- `getError()`: Returns any error that occurred if the job was not added. + ### Sending chained jobs to the queue Sending chained jobs is also simple and lets you specify the particular order of the job execution. @@ -172,9 +178,11 @@ service('queue')->chain(function($chain) { }); ``` -In the example above, we will send jobs to the `reports` and `emails` queue. First, we will generate a report for given user with the `generate-report` job, after this, we will send an email with `email` job. +In the example above, we will send jobs to the `reports` and `emails` queues. First, we will generate a report for given user with the `generate-report` job, after this, we will send an email with `email` job. The `email` job will be executed only if the `generate-report` job was successful. +As with the `push()` method, calling the `chain()` method also returns a `QueuePushResult` object. + ### Consuming the queue Since we sent our sample job to queue `emails`, then we need to run the worker with the appropriate queue: diff --git a/src/Handlers/BaseHandler.php b/src/Handlers/BaseHandler.php index 03a6a57..f364802 100644 --- a/src/Handlers/BaseHandler.php +++ b/src/Handlers/BaseHandler.php @@ -22,6 +22,7 @@ use CodeIgniter\Queue\Models\QueueJobFailedModel; use CodeIgniter\Queue\Payloads\ChainBuilder; use CodeIgniter\Queue\Payloads\PayloadMetadata; +use CodeIgniter\Queue\QueuePushResult; use CodeIgniter\Queue\Traits\HasQueueValidation; use ReflectionException; use Throwable; @@ -39,7 +40,7 @@ abstract class BaseHandler abstract public function name(): string; - abstract public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): bool; + abstract public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): QueuePushResult; abstract public function pop(string $queue, array $priorities): ?QueueJob; @@ -153,7 +154,7 @@ public function setPriority(string $priority): static * * @param Closure $callback Chain definition callback */ - public function chain(Closure $callback): bool + public function chain(Closure $callback): QueuePushResult { $chainBuilder = new ChainBuilder($this); $callback($chainBuilder); diff --git a/src/Handlers/DatabaseHandler.php b/src/Handlers/DatabaseHandler.php index 88869b6..403e7cd 100644 --- a/src/Handlers/DatabaseHandler.php +++ b/src/Handlers/DatabaseHandler.php @@ -21,6 +21,7 @@ use CodeIgniter\Queue\Models\QueueJobModel; use CodeIgniter\Queue\Payloads\Payload; use CodeIgniter\Queue\Payloads\PayloadMetadata; +use CodeIgniter\Queue\QueuePushResult; use ReflectionException; use Throwable; @@ -44,10 +45,8 @@ public function name(): string /** * Add job to the queue. - * - * @throws ReflectionException */ - public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): bool + public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): QueuePushResult { $this->validateJobAndPriority($queue, $job); @@ -62,7 +61,17 @@ public function push(string $queue, string $job, array $data, ?PayloadMetadata $ $this->priority = $this->delay = null; - return $this->jobModel->insert($queueJob, false); + try { + $jobId = $this->jobModel->insert($queueJob); + } catch (Throwable $e) { + return QueuePushResult::failure($e->getMessage()); + } + + if ($jobId === 0) { + return QueuePushResult::failure('Failed to insert job into the database.'); + } + + return QueuePushResult::success($jobId); } /** diff --git a/src/Handlers/PredisHandler.php b/src/Handlers/PredisHandler.php index 4b64990..22be7cf 100644 --- a/src/Handlers/PredisHandler.php +++ b/src/Handlers/PredisHandler.php @@ -22,6 +22,7 @@ use CodeIgniter\Queue\Interfaces\QueueInterface; use CodeIgniter\Queue\Payloads\Payload; use CodeIgniter\Queue\Payloads\PayloadMetadata; +use CodeIgniter\Queue\QueuePushResult; use Exception; use Predis\Client; use Throwable; @@ -59,16 +60,17 @@ public function name(): string /** * Add job to the queue. */ - public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): bool + public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): QueuePushResult { $this->validateJobAndPriority($queue, $job); helper('text'); + $jobId = (int) random_string('numeric', 16); $availableAt = Time::now()->addSeconds($this->delay ?? 0); $queueJob = new QueueJob([ - 'id' => random_string('numeric', 16), + 'id' => $jobId, 'queue' => $queue, 'payload' => new Payload($job, $data, $metadata), 'priority' => $this->priority, @@ -77,11 +79,19 @@ public function push(string $queue, string $job, array $data, ?PayloadMetadata $ 'available_at' => $availableAt, ]); - $result = $this->predis->zadd("queues:{$queue}:{$this->priority}", [json_encode($queueJob) => $availableAt->timestamp]); + try { + $result = $this->predis->zadd("queues:{$queue}:{$this->priority}", [json_encode($queueJob) => $availableAt->timestamp]); + } catch (Throwable $e) { + return QueuePushResult::failure('Unexpected Redis error: ' . $e->getMessage()); + } finally { + $this->priority = $this->delay = null; + } $this->priority = $this->delay = null; - return $result > 0; + return $result > 0 + ? QueuePushResult::success($jobId) + : QueuePushResult::failure('Job already exists in the queue.'); } /** diff --git a/src/Handlers/RedisHandler.php b/src/Handlers/RedisHandler.php index dc86d5b..4676436 100644 --- a/src/Handlers/RedisHandler.php +++ b/src/Handlers/RedisHandler.php @@ -22,6 +22,7 @@ use CodeIgniter\Queue\Interfaces\QueueInterface; use CodeIgniter\Queue\Payloads\Payload; use CodeIgniter\Queue\Payloads\PayloadMetadata; +use CodeIgniter\Queue\QueuePushResult; use Redis; use RedisException; use Throwable; @@ -76,16 +77,17 @@ public function name(): string * * @throws RedisException */ - public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): bool + public function push(string $queue, string $job, array $data, ?PayloadMetadata $metadata = null): QueuePushResult { $this->validateJobAndPriority($queue, $job); helper('text'); $availableAt = Time::now()->addSeconds($this->delay ?? 0); + $jobId = (int) random_string('numeric', 16); $queueJob = new QueueJob([ - 'id' => random_string('numeric', 16), + 'id' => $jobId, 'queue' => $queue, 'payload' => new Payload($job, $data, $metadata), 'priority' => $this->priority, @@ -94,11 +96,21 @@ public function push(string $queue, string $job, array $data, ?PayloadMetadata $ 'available_at' => $availableAt, ]); - $result = (int) $this->redis->zAdd("queues:{$queue}:{$this->priority}", $availableAt->timestamp, json_encode($queueJob)); + try { + $result = $this->redis->zAdd("queues:{$queue}:{$this->priority}", $availableAt->timestamp, json_encode($queueJob)); + } catch (Throwable $e) { + return QueuePushResult::failure('Unexpected Redis error: ' . $e->getMessage()); + } finally { + $this->priority = $this->delay = null; + } - $this->priority = $this->delay = null; + if ($result === false) { + return QueuePushResult::failure('Failed to add job to Redis.'); + } - return $result > 0; + return (int) $result > 0 + ? QueuePushResult::success($jobId) + : QueuePushResult::failure('Job already exists in the queue.'); } /** diff --git a/src/Payloads/ChainBuilder.php b/src/Payloads/ChainBuilder.php index 73479d2..772e376 100644 --- a/src/Payloads/ChainBuilder.php +++ b/src/Payloads/ChainBuilder.php @@ -14,6 +14,7 @@ namespace CodeIgniter\Queue\Payloads; use CodeIgniter\Queue\Handlers\BaseHandler; +use CodeIgniter\Queue\QueuePushResult; class ChainBuilder { @@ -44,10 +45,10 @@ public function push(string $queue, string $jobName, array $data = []): ChainEle /** * Dispatch the chain of jobs */ - public function dispatch(): bool + public function dispatch(): QueuePushResult { if ($this->payloads->count() === 0) { - return true; + return QueuePushResult::failure('No jobs to dispatch.'); } $current = $this->payloads->shift(); diff --git a/src/QueuePushResult.php b/src/QueuePushResult.php new file mode 100644 index 0000000..efe7456 --- /dev/null +++ b/src/QueuePushResult.php @@ -0,0 +1,67 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace CodeIgniter\Queue; + +/** + * Represents the result of a queue push operation. + */ +class QueuePushResult +{ + public function __construct( + protected readonly bool $success, + protected readonly ?int $jobId = null, + protected readonly ?string $error = null, + ) { + } + + /** + * Creates a successful push result. + */ + public static function success(int $jobId): self + { + return new self(true, $jobId); + } + + /** + * Creates a failed push result. + */ + public static function failure(?string $error = null): self + { + return new self(false, null, $error); + } + + /** + * Returns whether the push operation was successful. + */ + public function getStatus(): bool + { + return $this->success; + } + + /** + * Returns the job ID if the push was successful, null otherwise. + */ + public function getJobId(): ?int + { + return $this->jobId; + } + + /** + * Returns the error message if the push failed, null otherwise. + */ + public function getError(): ?string + { + return $this->error; + } +} diff --git a/tests/DatabaseHandlerTest.php b/tests/DatabaseHandlerTest.php index 507f917..f189f78 100644 --- a/tests/DatabaseHandlerTest.php +++ b/tests/DatabaseHandlerTest.php @@ -85,7 +85,7 @@ public function testPush(): void $handler = new DatabaseHandler($this->config); $result = $handler->push('queue', 'success', ['key' => 'value']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value'], 'metadata' => []]), @@ -103,7 +103,7 @@ public function testPushWithPriority(): void $handler = new DatabaseHandler($this->config); $result = $handler->setPriority('high')->push('queue', 'success', ['key' => 'value']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode(['job' => 'success', 'data' => ['key' => 'value'], 'metadata' => []]), @@ -122,7 +122,7 @@ public function testPushAndPopWithPriority(): void $handler = new DatabaseHandler($this->config); $result = $handler->push('queue', 'success', ['key1' => 'value1']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode(['job' => 'success', 'data' => ['key1' => 'value1'], 'metadata' => []]), @@ -132,7 +132,7 @@ public function testPushAndPopWithPriority(): void $result = $handler->setPriority('high')->push('queue', 'success', ['key2' => 'value2']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode(['job' => 'success', 'data' => ['key2' => 'value2'], 'metadata' => []]), @@ -161,7 +161,7 @@ public function testPushWithDelay(): void $handler = new DatabaseHandler($this->config); $result = $handler->setDelay(MINUTE)->push('queue-delay', 'success', ['key' => 'value']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $availableAt = 1703859376; @@ -188,7 +188,7 @@ public function testChain(): void ->push('queue', 'success', ['key2' => 'value2']); }); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode([ @@ -221,7 +221,7 @@ public function testChainWithPriorityAndDelay(): void ->setDelay(120); }); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode([ diff --git a/tests/Payloads/ChainBuilderTest.php b/tests/Payloads/ChainBuilderTest.php index 304f5ae..6aa91ba 100644 --- a/tests/Payloads/ChainBuilderTest.php +++ b/tests/Payloads/ChainBuilderTest.php @@ -63,7 +63,7 @@ public function testChainWithSingleJob(): void $chain->push('queue', 'success', ['key' => 'value']); }); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode([ @@ -84,7 +84,7 @@ public function testEmptyChain(): void // No jobs added }); - $this->assertTrue($result); + $this->assertFalse($result->getStatus()); $this->seeInDatabase('queue_jobs', []); } @@ -99,7 +99,7 @@ public function testMultipleDifferentQueues(): void ->push('queue2', 'success', ['key2' => 'value2']); }); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue1', 'payload' => json_encode([ @@ -132,7 +132,7 @@ public function testChainWithManyJobs(): void ->push('queue', 'success', ['key3' => 'value3']); }); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $this->seeInDatabase('queue_jobs', [ 'queue' => 'queue', 'payload' => json_encode([ diff --git a/tests/PredisHandlerTest.php b/tests/PredisHandlerTest.php index 226732b..05d79d0 100644 --- a/tests/PredisHandlerTest.php +++ b/tests/PredisHandlerTest.php @@ -72,7 +72,7 @@ public function testPush(): void $handler = new PredisHandler($this->config); $result = $handler->push('queue', 'success', ['key' => 'value']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(1, $predis->zcard('queues:queue:low')); @@ -92,7 +92,7 @@ public function testPushWithPriority(): void $handler = new PredisHandler($this->config); $result = $handler->setPriority('high')->push('queue', 'success', ['key' => 'value']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(1, $predis->zcard('queues:queue:high')); @@ -114,7 +114,7 @@ public function testPushWithDelay(): void $handler = new PredisHandler($this->config); $result = $handler->setDelay(MINUTE)->push('queue-delay', 'success', ['key' => 'value']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(1, $predis->zcard('queues:queue-delay:default')); @@ -140,7 +140,7 @@ public function testChain(): void ->push('queue', 'success', ['key2' => 'value2']); }); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $predis = self::getPrivateProperty($handler, 'predis'); $this->assertSame(1, $predis->zcard('queues:queue:low')); @@ -180,7 +180,7 @@ public function testChainWithPriorityAndDelay(): void ->setDelay(120); }); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $predis = self::getPrivateProperty($handler, 'predis'); // Should be in high priority queue diff --git a/tests/PushAndPopWithDelayTest.php b/tests/PushAndPopWithDelayTest.php index db49b81..70a85a3 100644 --- a/tests/PushAndPopWithDelayTest.php +++ b/tests/PushAndPopWithDelayTest.php @@ -64,11 +64,11 @@ public function testPushAndPopWithDelay(string $name, string $class): void $handler = new $class($this->config); $result = $handler->setDelay(MINUTE)->push('queue-delay', 'success', ['key1' => 'value1']); - $this->assertTrue($result); + $this->assertNotNull($result); $result = $handler->push('queue-delay', 'success', ['key2' => 'value2']); - $this->assertTrue($result); + $this->assertNotNull($result); if ($name === 'database') { $this->seeInDatabase('queue_jobs', [ diff --git a/tests/QueuePushResultTest.php b/tests/QueuePushResultTest.php new file mode 100644 index 0000000..f5a6dfa --- /dev/null +++ b/tests/QueuePushResultTest.php @@ -0,0 +1,68 @@ + + * + * For the full copyright and license information, please view + * the LICENSE file that was distributed with this source code. + */ + +namespace Tests; + +use CodeIgniter\Queue\QueuePushResult; +use Tests\Support\TestCase; + +/** + * @internal + */ +final class QueuePushResultTest extends TestCase +{ + public function testConstructorSuccess(): void + { + $result = new QueuePushResult(true, 123456, null); + + $this->assertTrue($result->getStatus()); + $this->assertSame(123456, $result->getJobId()); + $this->assertNull($result->getError()); + } + + public function testConstructorFailure(): void + { + $result = new QueuePushResult(false, null, 'Something went wrong'); + + $this->assertFalse($result->getStatus()); + $this->assertNull($result->getJobId()); + $this->assertSame('Something went wrong', $result->getError()); + } + + public function testStaticSuccess(): void + { + $result = QueuePushResult::success(999888); + + $this->assertTrue($result->getStatus()); + $this->assertSame(999888, $result->getJobId()); + $this->assertNull($result->getError()); + } + + public function testStaticFailure(): void + { + $result = QueuePushResult::failure('Redis error'); + + $this->assertFalse($result->getStatus()); + $this->assertNull($result->getJobId()); + $this->assertSame('Redis error', $result->getError()); + } + + public function testStaticFailureWithoutError(): void + { + $result = QueuePushResult::failure(); + + $this->assertFalse($result->getStatus()); + $this->assertNull($result->getJobId()); + $this->assertNull($result->getError()); + } +} diff --git a/tests/RedisHandlerTest.php b/tests/RedisHandlerTest.php index aa71240..7707cd4 100644 --- a/tests/RedisHandlerTest.php +++ b/tests/RedisHandlerTest.php @@ -69,7 +69,7 @@ public function testPush(): void $handler = new RedisHandler($this->config); $result = $handler->push('queue', 'success', ['key' => 'value']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $redis = self::getPrivateProperty($handler, 'redis'); $this->assertSame(1, $redis->zCard('queues:queue:low')); @@ -86,7 +86,7 @@ public function testPushWithPriority(): void $handler = new RedisHandler($this->config); $result = $handler->setPriority('high')->push('queue', 'success', ['key' => 'value']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $redis = self::getPrivateProperty($handler, 'redis'); $this->assertSame(1, $redis->zCard('queues:queue:high')); @@ -108,7 +108,7 @@ public function testPushWithDelay(): void $handler = new RedisHandler($this->config); $result = $handler->setDelay(MINUTE)->push('queue-delay', 'success', ['key' => 'value']); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $redis = self::getPrivateProperty($handler, 'redis'); $this->assertSame(1, $redis->zCard('queues:queue-delay:default')); @@ -134,7 +134,7 @@ public function testChain(): void ->push('queue', 'success', ['key2' => 'value2']); }); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $redis = self::getPrivateProperty($handler, 'redis'); $this->assertSame(1, $redis->zCard('queues:queue:low')); @@ -174,7 +174,7 @@ public function testChainWithPriorityAndDelay(): void ->setDelay(120); }); - $this->assertTrue($result); + $this->assertTrue($result->getStatus()); $redis = self::getPrivateProperty($handler, 'redis'); // Should be in high priority queue