diff --git a/.github/workflows/code-checks.yml b/.github/workflows/code-checks.yml index 4f751c0..d57ddf2 100644 --- a/.github/workflows/code-checks.yml +++ b/.github/workflows/code-checks.yml @@ -11,8 +11,6 @@ jobs: strategy: matrix: php: - - '7.4' - - '8.0' - '8.1' - '8.2' - '8.3' diff --git a/CHANGELOG.md b/CHANGELOG.md index 885c91b..a80d988 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,15 @@ The format is based on [Keep a Changelog](http://keepachangelog.com/) and this project adheres to [Semantic Versioning](http://semver.org/). ## [Unreleased] +### Added +- Type declarations for parameters and return types. +### Changed +- Upgraded `phlib/beanstalk` dependency to require v3. +- Upgraded `phlib/console-process` dependency to require v3 or v4. +- Allow `phlib/console-configuration` v3 dependency. +### Removed +- **BC break**: Removed support for PHP versions <= v8.0 as they are no longer + [actively supported](https://php.net/supported-versions.php) by the PHP project. ## [2.0.0] - 2022-09-14 ### Added diff --git a/composer.json b/composer.json index 439602d..9b7192c 100644 --- a/composer.json +++ b/composer.json @@ -14,13 +14,13 @@ } ], "require": { - "php": "^7.4 || ^8.0", + "php": "^8.1", "ext-json": "*", - "phlib/beanstalk": "^2", + "phlib/beanstalk": "^3", "phlib/db": "^2", - "phlib/console-configuration": "^2", - "phlib/console-process": "^2", + "phlib/console-configuration": "^2 || ^3", + "phlib/console-process": "^3 || ^4", "psr/log": "^1", "aws/aws-sdk-php": "^3.61" diff --git a/src/AwsSqs/JobFactory.php b/src/AwsSqs/JobFactory.php index 94f32c3..fd27095 100644 --- a/src/AwsSqs/JobFactory.php +++ b/src/AwsSqs/JobFactory.php @@ -14,10 +14,7 @@ */ class JobFactory { - /** - * @return string - */ - public static function serializeBody(JobInterface $job) + public static function serializeBody(JobInterface $job): string { return json_encode([ 'queue' => $job->getQueue(), diff --git a/src/AwsSqs/JobQueue.php b/src/AwsSqs/JobQueue.php index 4fce4bf..7709dd3 100644 --- a/src/AwsSqs/JobQueue.php +++ b/src/AwsSqs/JobQueue.php @@ -18,34 +18,21 @@ */ class JobQueue implements JobQueueInterface { - private SqsClient $client; - - private SchedulerInterface $scheduler; - private int $retrieveTimeout = 10; private array $queues = []; - /** - * @var string - */ - private $queuePrefix; - - public function __construct(SqsClient $client, SchedulerInterface $scheduler, $queuePrefix = '') - { - $this->client = $client; - $this->scheduler = $scheduler; - $this->queuePrefix = $queuePrefix; + public function __construct( + private readonly SqsClient $client, + private readonly SchedulerInterface $scheduler, + private string $queuePrefix = '', + ) { } - /** - * @param mixed $data - * @param int|string|null $id - */ public function createJob( string $queue, - $data, - $id = null, + mixed $data, + int|string|null $id, $delay = Job::DEFAULT_DELAY, $priority = Job::DEFAULT_PRIORITY, $ttr = Job::DEFAULT_TTR @@ -160,7 +147,7 @@ private function getQueueUrl($name) return $this->queues[$name]; } - private function determineDeadletterQueue($queue) + private function determineDeadletterQueue(string $queue): string { $name = $this->queuePrefix . $queue; try { @@ -175,7 +162,7 @@ private function determineDeadletterQueue($queue) $targetArn = json_decode($arnJson, true, 512, JSON_THROW_ON_ERROR)['deadLetterTargetArn']; return substr($targetArn, strrpos($targetArn, ':') + 1); - } catch (SqsException $exception) { + } catch (SqsException) { throw new RuntimeException("Specified queue '{$name}' does not have a Redrive Policy"); } } diff --git a/src/Beanstalk/JobQueue.php b/src/Beanstalk/JobQueue.php index 104e568..ac27af4 100644 --- a/src/Beanstalk/JobQueue.php +++ b/src/Beanstalk/JobQueue.php @@ -4,7 +4,8 @@ namespace Phlib\JobQueue\Beanstalk; -use Phlib\Beanstalk\Connection\ConnectionInterface; +use Phlib\Beanstalk\ConnectionInterface; +use Phlib\Beanstalk\Exception\NotFoundException as BeanstalkNotFoundException; use Phlib\JobQueue\Exception\InvalidArgumentException; use Phlib\JobQueue\Job; use Phlib\JobQueue\JobInterface; @@ -16,24 +17,22 @@ */ class JobQueue implements JobQueueInterface { - protected ConnectionInterface $beanstalk; - - protected SchedulerInterface $scheduler; - protected ?int $retrieveTimeout = 5; - public function __construct(ConnectionInterface $beanstalk, SchedulerInterface $scheduler) - { - $this->beanstalk = $beanstalk; - $this->scheduler = $scheduler; + public function __construct( + protected ConnectionInterface $beanstalk, + protected SchedulerInterface $scheduler, + ) { } - /** - * @param mixed $data - * @param int|string|null $id - */ - public function createJob(string $queue, $data, $id, int $delay, int $priority, int $ttr): JobInterface - { + public function createJob( + string $queue, + mixed $data, + int|string|null $id, + int $delay, + int $priority, + int $ttr, + ): JobInterface { return new Job($queue, $data, $id, $delay, $priority, $ttr); } @@ -44,9 +43,8 @@ public function put(JobInterface $job): self return $this; } - $this->beanstalk - ->useTube($job->getQueue()) - ->put(JobFactory::serializeBody($job), $job->getPriority(), $job->getDelay(), $job->getTtr()); + $this->beanstalk->useTube($job->getQueue()); + $this->beanstalk->put(JobFactory::serializeBody($job), $job->getPriority(), $job->getDelay(), $job->getTtr()); return $this; } @@ -74,10 +72,15 @@ public function retrieve(string $queue): ?JobInterface $this->beanstalk->watch($queue); $this->beanstalk->ignore('default'); - $data = $this->beanstalk->reserve($this->retrieveTimeout); - if ($data === null) { + try { + $data = $this->beanstalk->reserve($this->retrieveTimeout); + } catch (BeanstalkNotFoundException $e) { + if ($e->getCode() !== BeanstalkNotFoundException::RESERVE_NO_JOBS_AVAILABLE_CODE) { + throw $e; + } return null; } + return JobFactory::createFromRaw($data); } @@ -97,9 +100,8 @@ public function markAsIncomplete(JobInterface $job): self return $this; } - $this->beanstalk - ->useTube($job->getQueue()) - ->release($job->getId(), $job->getPriority(), $job->getDelay()); + $this->beanstalk->useTube($job->getQueue()); + $this->beanstalk->release($job->getId(), $job->getPriority(), $job->getDelay()); return $this; } diff --git a/src/Command/WorkerCommand.php b/src/Command/WorkerCommand.php index 76a44a9..73afe1a 100644 --- a/src/Command/WorkerCommand.php +++ b/src/Command/WorkerCommand.php @@ -28,6 +28,8 @@ class WorkerCommand extends DaemonCommand implements LoggerAwareInterface protected bool $exitOnException = false; + protected bool $queueContinue = true; + protected function execute(InputInterface $input, OutputInterface $output): int { if ($this->queue === null) { @@ -37,7 +39,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int $jobQueue = $this->getJobQueue(); $logger = $this->getLogger($output); - while ($this->continue && ($job = $this->retrieve($jobQueue, $logger)) instanceof JobInterface) { + while ($this->queueContinue && ($job = $this->retrieve($jobQueue, $logger)) instanceof JobInterface) { try { $logger->info("Retrieved job {$job->getId()} for {$this->queue}"); $workStarted = microtime(true); @@ -74,7 +76,8 @@ private function retrieve(JobQueueInterface $jobQueue, LoggerInterface $logger): return $jobQueue->retrieve($this->queue); } catch (\Exception $e) { $this->logException($logger, "Failed to retrieve job due to error '{$e->getMessage()}'", $e); - $this->continue = false; + $this->queueContinue = false; + $this->shutdown(); throw $e; } } @@ -97,10 +100,10 @@ protected function getLogger(OutputInterface $output): LoggerInterface return $this->logger; } - private function logException(LoggerInterface $logger, $message, \Exception $exception, $job = null): void + private function logException(LoggerInterface $logger, string $message, \Exception $exception, $job = null): void { $context = [ - 'qClass' => get_class($this->getJobQueue()), + 'qClass' => $this->getJobQueue()::class, 'xMessage' => $exception->getMessage(), 'xFile' => $exception->getFile(), 'xLine' => $exception->getLine(), diff --git a/src/Console/MonitorCommand.php b/src/Console/MonitorCommand.php index 8f1a402..cc6271d 100644 --- a/src/Console/MonitorCommand.php +++ b/src/Console/MonitorCommand.php @@ -70,10 +70,10 @@ protected function createJob(array $schedulerJob): JobInterface ); } - protected function createChildOutput(): OutputInterface + protected function createChildOutput(?string $childLogFilename): OutputInterface { - if (empty($this->logFile)) { - return parent::createChildOutput(); + if (!isset($this->logFile) || ($this->logFile === '' || $this->logFile === '0')) { + return parent::createChildOutput($childLogFilename); } return new StreamOutput(fopen($this->logFile, 'ab')); } diff --git a/src/Console/MonitorDependencies.php b/src/Console/MonitorDependencies.php index e92922a..bc09b81 100644 --- a/src/Console/MonitorDependencies.php +++ b/src/Console/MonitorDependencies.php @@ -12,14 +12,10 @@ */ class MonitorDependencies { - protected JobQueueInterface $jobQueue; - - protected SchedulerInterface $scheduler; - - public function __construct(JobQueueInterface $jobQueue, SchedulerInterface $scheduler) - { - $this->jobQueue = $jobQueue; - $this->scheduler = $scheduler; + public function __construct( + protected JobQueueInterface $jobQueue, + protected SchedulerInterface $scheduler, + ) { } public function getJobQueue(): JobQueueInterface diff --git a/src/Exception/JobRuntimeException.php b/src/Exception/JobRuntimeException.php index 1bb0d1a..7c025b3 100644 --- a/src/Exception/JobRuntimeException.php +++ b/src/Exception/JobRuntimeException.php @@ -11,15 +11,13 @@ */ class JobRuntimeException extends RuntimeException { - /** - * @var JobInterface - */ - protected $job; - - public function __construct(?JobInterface $job, string $message, int $code = 0, \Throwable $previous = null) - { + public function __construct( + protected JobInterface $job, + string $message, + int $code = 0, + \Throwable $previous = null, + ) { parent::__construct($message, $code, $previous); - $this->job = $job; } public function hasJob(): bool @@ -27,7 +25,7 @@ public function hasJob(): bool return $this->job !== null; } - public function getJob(): ?JobInterface + public function getJob(): JobInterface { return $this->job; } diff --git a/src/Job.php b/src/Job.php index 685587a..28f5597 100644 --- a/src/Job.php +++ b/src/Job.php @@ -15,48 +15,23 @@ class Job implements JobInterface public const DEFAULT_TTR = 60; - protected string $queue; - - /** - * @var int|string|null - */ - protected $id; - - protected int $delay; - protected int $priority; protected int $ttr; - /** - * @var mixed - */ - protected $body; - - /** - * @param mixed $body - * @param int|string|null $id - */ public function __construct( - string $queue, - $body, - $id = null, - int $delay = self::DEFAULT_DELAY, + protected string $queue, + protected mixed $body, + protected int|string|null $id = null, + protected int $delay = self::DEFAULT_DELAY, int $priority = self::DEFAULT_PRIORITY, int $ttr = self::DEFAULT_TTR ) { - $this->queue = $queue; - $this->body = $body; - $this->id = $id; - $this->delay = $delay; $this->setPriority($priority); $this->setTtr($ttr); } - /** - * @return int|string|null - */ - public function getId() + public function getId(): int|string|null { return $this->id; } @@ -66,10 +41,7 @@ public function getQueue(): string return $this->queue; } - /** - * @return mixed - */ - public function getBody() + public function getBody(): mixed { return $this->body; } @@ -86,7 +58,7 @@ public function getDatetimeDelay(): \DateTimeImmutable public function setDelay(int $value): self { - $this->delay = (int)$value; + $this->delay = $value; return $this; } diff --git a/src/JobQueueInterface.php b/src/JobQueueInterface.php index 8a6c50b..704f7f1 100644 --- a/src/JobQueueInterface.php +++ b/src/JobQueueInterface.php @@ -9,11 +9,14 @@ */ interface JobQueueInterface { - /** - * @param mixed $data - * @param int|string|null $id - */ - public function createJob(string $queue, $data, $id, int $delay, int $priority, int $ttr): JobInterface; + public function createJob( + string $queue, + mixed $data, + int|string|null $id, + int $delay, + int $priority, + int $ttr, + ): JobInterface; /** * @todo php74 return type self diff --git a/src/Scheduler/DbScheduler.php b/src/Scheduler/DbScheduler.php index 391d95a..605cb42 100644 --- a/src/Scheduler/DbScheduler.php +++ b/src/Scheduler/DbScheduler.php @@ -12,21 +12,11 @@ */ class DbScheduler implements SchedulerInterface { - protected Adapter $adapter; - - protected int $maximumDelay; - - private int $minimumPickup; - - /** - * @param integer $maximumDelay - * @param integer $minimumPickup - */ - public function __construct(Adapter $adapter, $maximumDelay = 300, $minimumPickup = 600) - { - $this->adapter = $adapter; - $this->maximumDelay = $maximumDelay; - $this->minimumPickup = $minimumPickup; + public function __construct( + protected Adapter $adapter, + protected int $maximumDelay = 300, + private readonly int $minimumPickup = 600, + ) { } public function shouldBeScheduled($delay): bool @@ -47,10 +37,7 @@ public function store(JobInterface $job): bool ]); } - /** - * @return array|false - */ - public function retrieve() + public function retrieve(): array|false { $sql = ' UPDATE scheduled_queue SET @@ -88,10 +75,7 @@ public function retrieve() ]; } - /** - * @param int|string $jobId - */ - public function remove($jobId): bool + public function remove(int|string $jobId): bool { $table = $this->adapter->quote()->identifier('scheduled_queue'); $sql = "DELETE FROM {$table} WHERE id = ?"; diff --git a/src/Scheduler/SchedulerInterface.php b/src/Scheduler/SchedulerInterface.php index 74d75c6..26328de 100644 --- a/src/Scheduler/SchedulerInterface.php +++ b/src/Scheduler/SchedulerInterface.php @@ -15,13 +15,7 @@ public function shouldBeScheduled(int $delay): bool; public function store(JobInterface $job): bool; - /** - * @return array|false - */ - public function retrieve(); + public function retrieve(): array|false; - /** - * @param int|string $jobId - */ - public function remove($jobId): bool; + public function remove(int|string $jobId): bool; } diff --git a/tests/Beanstalk/JobQueueTest.php b/tests/Beanstalk/JobQueueTest.php index 91172a1..352edc6 100644 --- a/tests/Beanstalk/JobQueueTest.php +++ b/tests/Beanstalk/JobQueueTest.php @@ -4,7 +4,8 @@ namespace Phlib\JobQueue\Beanstalk; -use Phlib\Beanstalk\Connection\ConnectionInterface; +use Phlib\Beanstalk\ConnectionInterface; +use Phlib\Beanstalk\Exception\NotFoundException as BeanstalkNotFoundException; use Phlib\JobQueue\Exception\InvalidArgumentException; use Phlib\JobQueue\Exception\JobRuntimeException; use Phlib\JobQueue\Job; @@ -19,15 +20,9 @@ */ class JobQueueTest extends TestCase { - /** - * @var ConnectionInterface|MockObject - */ - private ConnectionInterface $beanstalk; + private ConnectionInterface&MockObject $beanstalk; - /** - * @var SchedulerInterface|MockObject - */ - private SchedulerInterface $scheduler; + private SchedulerInterface&MockObject $scheduler; private JobQueue $jobQueue; @@ -61,8 +56,7 @@ public function testPutForImmediateJobCallsBeanstalk(): void ->method('shouldBeScheduled') ->willReturn(false); $this->beanstalk->expects(static::once()) - ->method('useTube') - ->willReturnSelf(); + ->method('useTube'); $this->beanstalk->expects(static::once()) ->method('put') ->willReturn($jobId); @@ -112,7 +106,10 @@ public function testRetrieveWhenNoJobsAvailable(): void { $this->beanstalk->expects(static::once()) ->method('reserve') - ->willReturn(null); + ->willThrowException(new BeanstalkNotFoundException( + BeanstalkNotFoundException::RESERVE_NO_JOBS_AVAILABLE_MSG, + BeanstalkNotFoundException::RESERVE_NO_JOBS_AVAILABLE_CODE, + )); static::assertNull($this->jobQueue->retrieve('testQueue')); } @@ -183,8 +180,7 @@ public function testMarkAsCompleteDeletesBeanstalkJob(): void $this->beanstalk->expects(static::once()) ->method('delete') - ->with($jobId) - ->willReturn($this->beanstalk); + ->with($jobId); $this->jobQueue->markAsComplete($job); } @@ -204,12 +200,10 @@ public function testMarkAsIncompleteReleasesBeanstalkJobWhenDelayIsMoreImmediate ->willReturn(false); $this->beanstalk->expects(static::once()) - ->method('useTube') - ->willReturnSelf(); + ->method('useTube'); $this->beanstalk->expects(static::once()) ->method('release') - ->with($jobId) - ->willReturn($this->beanstalk); + ->with($jobId); $this->jobQueue->markAsIncomplete($job); } @@ -242,8 +236,7 @@ public function testMarkAsErrorBuriesBeanstalkJob(): void ->willReturn($jobId); $this->beanstalk->expects(static::once()) ->method('bury') - ->with($jobId) - ->willReturn($this->beanstalk); + ->with($jobId); $this->jobQueue->markAsError($job); } diff --git a/tests/Command/WorkerCommandMock.php b/tests/Command/WorkerCommandMock.php index c0a3ebb..572e159 100644 --- a/tests/Command/WorkerCommandMock.php +++ b/tests/Command/WorkerCommandMock.php @@ -16,32 +16,24 @@ class WorkerCommandMock extends WorkerCommand { protected string $queue = 'mockQueue'; - private JobQueueInterface $jobQueue; - - private bool $runOnce; - protected bool $exitOnException = true; - public function __construct(JobQueueInterface $jobQueue, $runOnce = true) - { + public function __construct( + private readonly JobQueueInterface $jobQueue, + private readonly bool $runOnce = true, + ) { parent::__construct(); - $this->jobQueue = $jobQueue; - $this->runOnce = $runOnce; } protected function work(JobInterface $job, InputInterface $input, OutputInterface $output): int { if ($this->runOnce) { - $this->continue = false; + $this->queueContinue = false; + $this->shutdown(); } return 0; } - public function shouldContinue(bool $state): void - { - $this->continue = $state; - } - protected function getJobQueue(): JobQueueInterface { return $this->jobQueue; diff --git a/tests/Command/WorkerCommandTest.php b/tests/Command/WorkerCommandTest.php index 682aa28..40c7e3e 100644 --- a/tests/Command/WorkerCommandTest.php +++ b/tests/Command/WorkerCommandTest.php @@ -19,20 +19,11 @@ */ class WorkerCommandTest extends TestCase { - /** - * @var JobQueueInterface|MockObject - */ - private JobQueueInterface $jobQueue; - - /** - * @var InputInterface|MockObject - */ - private InputInterface $input; - - /** - * @var OutputInterface|MockObject - */ - private OutputInterface $output; + private JobQueueInterface&MockObject $jobQueue; + + private InputInterface&MockObject $input; + + private OutputInterface&MockObject $output; protected function setUp(): void { diff --git a/tests/Scheduler/DbSchedulerTest.php b/tests/Scheduler/DbSchedulerTest.php index 2f689b4..1f0257d 100644 --- a/tests/Scheduler/DbSchedulerTest.php +++ b/tests/Scheduler/DbSchedulerTest.php @@ -14,15 +14,9 @@ */ class DbSchedulerTest extends TestCase { - /** - * @var Adapter|MockObject - */ - private Adapter $adapter; - - /** - * @var Adapter\QuoteHandler|MockObject - */ - private Adapter\QuoteHandler $quote; + private Adapter&MockObject $adapter; + + private Adapter\QuoteHandler&MockObject $quote; protected function setUp(): void {