From d390154df80d8aa27e8eeaf1af1373bb07d54c7a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yann=20Eugon=C3=A9?= Date: Thu, 16 Apr 2026 18:53:39 +0200 Subject: [PATCH 1/3] Convert BatchStatus from a class with int constants to a PHP 8.1 backed enum - BatchStatus is now an enum with cases: Pending, Running, Stopped, Completed, Abandoned, Failed - JobExecution::setStatus() now accepts BatchStatus instead of int - ExceptionEvent::getStatus/setStatus now use BatchStatus - QueryBuilder::statuses() now accepts BatchStatus[] instead of int[] - Query::statuses() now returns BatchStatus[] - FilesystemJobExecutionStorage uses in_array() instead of isOneOf() - Serializers use ->value and BatchStatus::from() for int mapping - DoctrineDBAL storage maps enum values to int for SQL - Twig status templates use ->name instead of class constants - JobFilterType uses EnumType for status choices, JobFilter::$statuses is BatchStatus[] - All tests updated to use new enum cases - Remove @internal from Query constructor --- .../src/DoctrineDBALJobExecutionStorage.php | 3 +- .../src/JobExecutionRowNormalizer.php | 6 +- .../DoctrineDBALJobExecutionStorageTest.php | 20 +++--- .../src/RunCommandJobLauncher.php | 2 +- .../src/RunJobCommand.php | 4 +- .../tests/RunCommandJobLauncherTest.php | 2 +- .../views/bootstrap4/_status.html.twig | 16 ++--- .../Resources/views/sonata/_status.html.twig | 16 ++--- .../src/UserInterface/Form/JobFilter.php | 3 +- .../src/UserInterface/Form/JobFilterType.php | 16 ++--- .../Controller/JobControllerTest.php | 36 +++++----- .../UserInterface/Form/JobFilterTypeTest.php | 16 ++--- .../src/DispatchMessageJobLauncher.php | 4 +- .../tests/DispatchMessageJobLauncherTest.php | 6 +- src/batch/src/BatchStatus.php | 65 +++---------------- src/batch/src/Event/ExceptionEvent.php | 6 +- src/batch/src/Job/JobExecutor.php | 4 +- src/batch/src/Job/JobWithChildJobs.php | 4 +- src/batch/src/JobExecution.php | 8 +-- .../Serializer/JsonJobExecutionSerializer.php | 4 +- .../Storage/FilesystemJobExecutionStorage.php | 2 +- .../ListableJobExecutionStorageInterface.php | 2 +- src/batch/src/Storage/Query.php | 8 +-- src/batch/src/Storage/QueryBuilder.php | 24 ++----- .../QueryableJobExecutionStorageInterface.php | 2 +- src/batch/tests/BatchStatusTest.php | 22 +++---- src/batch/tests/Job/JobExecutorTest.php | 10 +-- src/batch/tests/Job/JobWithChildJobsTest.php | 18 ++--- src/batch/tests/JobExecutionTest.php | 10 +-- .../tests/Launcher/SimpleJobLauncherTest.php | 2 +- .../Serializer/fixtures/fulfilled.object.php | 6 +- .../FilesystemJobExecutionStorageTest.php | 2 +- src/batch/tests/Storage/QueryBuilderTest.php | 24 ++----- tests/integration/DummyJobTest.php | 2 +- tests/integration/FailingDummyJobTest.php | 2 +- tests/integration/JobTestCase.php | 2 +- .../integration/JobWithDummyChildrenTest.php | 6 +- .../JobWithDummyItemChildrenTest.php | 4 +- .../JobWithFailingDummyChidlrenTest.php | 6 +- 39 files changed, 159 insertions(+), 236 deletions(-) diff --git a/src/batch-doctrine-dbal/src/DoctrineDBALJobExecutionStorage.php b/src/batch-doctrine-dbal/src/DoctrineDBALJobExecutionStorage.php index 76a0dbb1..153a683a 100644 --- a/src/batch-doctrine-dbal/src/DoctrineDBALJobExecutionStorage.php +++ b/src/batch-doctrine-dbal/src/DoctrineDBALJobExecutionStorage.php @@ -19,6 +19,7 @@ use Doctrine\DBAL\Types\Types; use Doctrine\Persistence\ConnectionRegistry; use Generator; +use Yokai\Batch\BatchStatus; use Yokai\Batch\Exception\CannotRemoveJobExecutionException; use Yokai\Batch\Factory\JobExecutionLoggerFactoryInterface; use Yokai\Batch\Exception\CannotStoreJobExecutionException; @@ -374,7 +375,7 @@ private function addWheres(Query $query, QueryBuilder $qb): array $statuses = $query->statuses(); if ($statuses !== []) { $qb->andWhere($qb->expr()->in('status', ':statuses')); - $queryParameters['statuses'] = $statuses; + $queryParameters['statuses'] = \array_map(fn(BatchStatus $s) => $s->value, $statuses); $queryTypes['statuses'] = ArrayParameterType::INTEGER; } diff --git a/src/batch-doctrine-dbal/src/JobExecutionRowNormalizer.php b/src/batch-doctrine-dbal/src/JobExecutionRowNormalizer.php index c9ab65ff..37db2200 100644 --- a/src/batch-doctrine-dbal/src/JobExecutionRowNormalizer.php +++ b/src/batch-doctrine-dbal/src/JobExecutionRowNormalizer.php @@ -64,7 +64,7 @@ public function toRow(JobExecution $jobExecution): array return [ 'id' => $jobExecution->getId(), 'job_name' => $jobExecution->getJobName(), - 'status' => $jobExecution->getStatus()->getValue(), + 'status' => $jobExecution->getStatus()->value, 'parameters' => \iterator_to_array($jobExecution->getParameters()), 'start_time' => $jobExecution->getStartTime(), 'end_time' => $jobExecution->getEndTime(), @@ -85,7 +85,7 @@ public function toRow(JobExecution $jobExecution): array public function fromRow(array $data, JobExecution|null $parent = null): JobExecution { $name = $data['job_name']; - $status = new BatchStatus((int)$data['status']); + $status = BatchStatus::from((int)$data['status']); $parameters = new JobParameters($this->jsonFromString($data['parameters'])); $summary = new Summary($this->jsonFromString($data['summary'])); @@ -137,7 +137,7 @@ private function toChildRow(JobExecution $jobExecution): array { return [ 'job_name' => $jobExecution->getJobName(), - 'status' => $jobExecution->getStatus()->getValue(), + 'status' => $jobExecution->getStatus()->value, 'parameters' => \iterator_to_array($jobExecution->getParameters()), 'start_time' => $this->toDateString($jobExecution->getStartTime()), 'end_time' => $this->toDateString($jobExecution->getEndTime()), diff --git a/src/batch-doctrine-dbal/tests/DoctrineDBALJobExecutionStorageTest.php b/src/batch-doctrine-dbal/tests/DoctrineDBALJobExecutionStorageTest.php index 88019772..48c5ca3b 100644 --- a/src/batch-doctrine-dbal/tests/DoctrineDBALJobExecutionStorageTest.php +++ b/src/batch-doctrine-dbal/tests/DoctrineDBALJobExecutionStorageTest.php @@ -108,7 +108,7 @@ public function testStoreInsert(): void $storage = $this->createStorage(); $storage->setup(); - $export = JobExecution::createRoot('123', 'export', new BatchStatus(BatchStatus::RUNNING)); + $export = JobExecution::createRoot('123', 'export', BatchStatus::Running); $export->setStartTime(new DateTimeImmutable('2021-09-23 11:05:00')); $export->setLaunchedAt(new DateTimeImmutable('2021-09-23 11:04:55')); $export->addChildExecution($extract = JobExecution::createChild($export, 'extract')); @@ -124,7 +124,7 @@ public function testStoreInsert(): void self::assertSame('2021-09-23 11:05:00', $retrievedExport->getStartTime()->format('Y-m-d H:i:s')); self::assertNull($retrievedExport->getEndTime()); self::assertSame('2021-09-23 11:04:55', $retrievedExport->getLaunchedAt()->format('Y-m-d H:i:s')); - self::assertSame(BatchStatus::RUNNING, $retrievedExport->getStatus()->getValue()); + self::assertSame(BatchStatus::Running, $retrievedExport->getStatus()); $retrievedExtract = $retrievedExport->getChildExecution('extract'); self::assertNotNull($retrievedExtract); self::assertSame('2021-09-23 11:05:01', $retrievedExtract->getStartTime()->format('Y-m-d H:i:s')); @@ -146,13 +146,13 @@ public function testStoreUpdate(): void $storage = $this->createStorage(); $storage->setup(); $storage->store($execution = JobExecution::createRoot('123', 'export')); - $execution->setStatus(BatchStatus::COMPLETED); + $execution->setStatus(BatchStatus::Completed); $storage->store($execution); $retrievedExecution = $storage->retrieve('export', '123'); self::assertSame('export', $retrievedExecution->getJobName()); self::assertSame('123', $retrievedExecution->getId()); - self::assertSame(BatchStatus::COMPLETED, $retrievedExecution->getStatus()->getValue()); + self::assertSame(BatchStatus::Completed, $retrievedExecution->getStatus()); } public function testStoreFailing(): void @@ -228,7 +228,7 @@ public function testRetrieveInvalid(array $data, Throwable $error): void $data['id'] = '123'; $data['job_name'] = 'export'; - $data['status'] ??= BatchStatus::COMPLETED; + $data['status'] ??= BatchStatus::Completed->value; $data['parameters'] ??= '[]'; $data['summary'] ??= '[]'; $data['failures'] ??= '[]'; @@ -315,7 +315,7 @@ public static function queries(): Generator ]; yield 'Filter statuses' => [ (new QueryBuilder()) - ->statuses([BatchStatus::FAILED]), + ->statuses([BatchStatus::Failed]), [ ['import', '456'], ], @@ -473,19 +473,19 @@ private static function assertExecutions(array $expectedCouples, iterable $execu private function loadFixtures(DoctrineDBALJobExecutionStorage $storage): void { // completed export started at 2019-07-01 13:00 and ended at 2019-07-01 13:30 - $completedExport = JobExecution::createRoot('123', 'export', new BatchStatus(BatchStatus::COMPLETED)); + $completedExport = JobExecution::createRoot('123', 'export', BatchStatus::Completed); $completedExport->setStartTime(\DateTimeImmutable::createFromFormat(DATE_ISO8601, '2019-07-01T13:00:00+0200')); $completedExport->setEndTime(\DateTimeImmutable::createFromFormat(DATE_ISO8601, '2019-07-01T13:30:00+0200')); $storage->store($completedExport); // failed import started at 2019-07-01 17:30 and ended at 2019-07-01 18:30 - $failedImport = JobExecution::createRoot('456', 'import', new BatchStatus(BatchStatus::FAILED)); + $failedImport = JobExecution::createRoot('456', 'import', BatchStatus::Failed); $failedImport->setStartTime(\DateTimeImmutable::createFromFormat(DATE_ISO8601, '2019-07-01T17:30:00+0200')); $failedImport->setEndTime(\DateTimeImmutable::createFromFormat(DATE_ISO8601, '2019-07-01T18:30:00+0200')); $storage->store($failedImport); // running import started at 2019-06-30 22:00 and not ended - $runningImport = JobExecution::createRoot('789', 'import', new BatchStatus(BatchStatus::RUNNING)); + $runningImport = JobExecution::createRoot('789', 'import', BatchStatus::Running); $runningImport->setStartTime(\DateTimeImmutable::createFromFormat(DATE_ISO8601, '2019-06-30T22:00:00+0200')); $runningImport->getLogger()->debug('Importing things'); $runningImport->getLogger()->info('Thing imported'); @@ -493,7 +493,7 @@ private function loadFixtures(DoctrineDBALJobExecutionStorage $storage): void $storage->store($runningImport); // pending import not started and not ended - $pendingImport = JobExecution::createRoot('987', 'import', new BatchStatus(BatchStatus::PENDING)); + $pendingImport = JobExecution::createRoot('987', 'import', BatchStatus::Pending); $storage->store($pendingImport); } } diff --git a/src/batch-symfony-console/src/RunCommandJobLauncher.php b/src/batch-symfony-console/src/RunCommandJobLauncher.php index c07b0213..7f09891f 100644 --- a/src/batch-symfony-console/src/RunCommandJobLauncher.php +++ b/src/batch-symfony-console/src/RunCommandJobLauncher.php @@ -32,7 +32,7 @@ public function launch(string $name, array $configuration = []): JobExecution { $jobExecution = $this->jobExecutionFactory->create($name, $configuration); $configuration['_id'] ??= $jobExecution->getId(); - $jobExecution->setStatus(BatchStatus::PENDING); + $jobExecution->setStatus(BatchStatus::Pending); $this->jobExecutionStorage->store($jobExecution); $this->commandRunner->runAsync( diff --git a/src/batch-symfony-console/src/RunJobCommand.php b/src/batch-symfony-console/src/RunJobCommand.php index 3cb884ab..46623d99 100644 --- a/src/batch-symfony-console/src/RunJobCommand.php +++ b/src/batch-symfony-console/src/RunJobCommand.php @@ -61,7 +61,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int private function guessExecutionExitCode(JobExecution $jobExecution): int { - if ($jobExecution->getStatus()->is(BatchStatus::COMPLETED)) { + if ($jobExecution->getStatus() === BatchStatus::Completed) { if ($jobExecution->getAllWarnings() === []) { return self::EXIT_SUCCESS_CODE; } @@ -75,7 +75,7 @@ private function guessExecutionExitCode(JobExecution $jobExecution): int private function outputExecution(JobExecution $jobExecution, OutputInterface $output): void { $jobName = $jobExecution->getJobName(); - if ($jobExecution->getStatus()->is(BatchStatus::COMPLETED)) { + if ($jobExecution->getStatus() === BatchStatus::Completed) { $warnings = $jobExecution->getAllWarnings(); if ($warnings !== []) { foreach ($warnings as $warning) { diff --git a/src/batch-symfony-console/tests/RunCommandJobLauncherTest.php b/src/batch-symfony-console/tests/RunCommandJobLauncherTest.php index 94c9d5d2..070ffe70 100644 --- a/src/batch-symfony-console/tests/RunCommandJobLauncherTest.php +++ b/src/batch-symfony-console/tests/RunCommandJobLauncherTest.php @@ -46,7 +46,7 @@ public function testLaunch(): void self::assertSame('testing', $jobExecutionFromStorage->getJobName()); self::assertSame('123456789', $jobExecutionFromStorage->getId()); - self::assertSame(BatchStatus::PENDING, $jobExecutionFromStorage->getStatus()->getValue()); + self::assertSame(BatchStatus::Pending, $jobExecutionFromStorage->getStatus()); self::assertSame(['bar'], $jobExecutionFromStorage->getParameters()->get('foo')); } } diff --git a/src/batch-symfony-framework/src/Resources/views/bootstrap4/_status.html.twig b/src/batch-symfony-framework/src/Resources/views/bootstrap4/_status.html.twig index f9b17122..95632487 100644 --- a/src/batch-symfony-framework/src/Resources/views/bootstrap4/_status.html.twig +++ b/src/batch-symfony-framework/src/Resources/views/bootstrap4/_status.html.twig @@ -2,13 +2,13 @@ {# @var execution \Yokai\Batch\JobExecution #} {% set classMap = { - (constant('PENDING', execution.status)): 'light', - (constant('RUNNING', execution.status)): 'warning', - (constant('STOPPED', execution.status)): 'info', - (constant('COMPLETED', execution.status)): 'success', - (constant('ABANDONED', execution.status)): 'info', - (constant('FAILED', execution.status)): 'danger', + 'Pending': 'light', + 'Running': 'warning', + 'Stopped': 'info', + 'Completed': 'success', + 'Abandoned': 'info', + 'Failed': 'danger', } %} - - {{ ('job.status.'~execution.status|lower)|trans }} + + {{ ('job.status.'~execution.status.name|lower)|trans }} diff --git a/src/batch-symfony-framework/src/Resources/views/sonata/_status.html.twig b/src/batch-symfony-framework/src/Resources/views/sonata/_status.html.twig index 91efa65d..5430b4d7 100644 --- a/src/batch-symfony-framework/src/Resources/views/sonata/_status.html.twig +++ b/src/batch-symfony-framework/src/Resources/views/sonata/_status.html.twig @@ -2,13 +2,13 @@ {# @var execution \Yokai\Batch\JobExecution #} {% set classMap = { - (constant('PENDING', execution.status)): 'default', - (constant('RUNNING', execution.status)): 'warning', - (constant('STOPPED', execution.status)): 'info', - (constant('COMPLETED', execution.status)): 'success', - (constant('ABANDONED', execution.status)): 'info', - (constant('FAILED', execution.status)): 'danger', + 'Pending': 'default', + 'Running': 'warning', + 'Stopped': 'info', + 'Completed': 'success', + 'Abandoned': 'info', + 'Failed': 'danger', } %} - - {{ ('job.status.'~execution.status|lower)|trans }} + + {{ ('job.status.'~execution.status.name|lower)|trans }} diff --git a/src/batch-symfony-framework/src/UserInterface/Form/JobFilter.php b/src/batch-symfony-framework/src/UserInterface/Form/JobFilter.php index ca1610ab..c05cd1d4 100644 --- a/src/batch-symfony-framework/src/UserInterface/Form/JobFilter.php +++ b/src/batch-symfony-framework/src/UserInterface/Form/JobFilter.php @@ -4,6 +4,7 @@ namespace Yokai\Batch\Bridge\Symfony\Framework\UserInterface\Form; +use Yokai\Batch\BatchStatus; use Yokai\Batch\Storage\Query; /** @@ -18,7 +19,7 @@ public function __construct( */ public array $jobs = [], /** - * @var array + * @var array */ public array $statuses = [], ) { diff --git a/src/batch-symfony-framework/src/UserInterface/Form/JobFilterType.php b/src/batch-symfony-framework/src/UserInterface/Form/JobFilterType.php index d3f9ea62..a27a5cbb 100644 --- a/src/batch-symfony-framework/src/UserInterface/Form/JobFilterType.php +++ b/src/batch-symfony-framework/src/UserInterface/Form/JobFilterType.php @@ -6,6 +6,7 @@ use Symfony\Component\Form\AbstractType; use Symfony\Component\Form\Extension\Core\Type\ChoiceType; +use Symfony\Component\Form\Extension\Core\Type\EnumType; use Symfony\Component\Form\FormBuilderInterface; use Symfony\Component\OptionsResolver\OptionsResolver; use Yokai\Batch\BatchStatus; @@ -15,15 +16,6 @@ */ final class JobFilterType extends AbstractType { - private const STATUSES = [ - 'pending' => BatchStatus::PENDING, - 'running' => BatchStatus::RUNNING, - 'stopped' => BatchStatus::STOPPED, - 'completed' => BatchStatus::COMPLETED, - 'abandoned' => BatchStatus::ABANDONED, - 'failed' => BatchStatus::FAILED, - ]; - public function __construct( /** * @var array @@ -47,11 +39,11 @@ public function buildForm(FormBuilderInterface $builder, array $options): void ); $builder->add( 'statuses', - ChoiceType::class, + EnumType::class, [ + 'class' => BatchStatus::class, 'label' => 'job.field.status', - 'choice_label' => fn($choice, string $key, $value) => \sprintf('job.status.%s', $key), - 'choices' => self::STATUSES, + 'choice_label' => fn(BatchStatus $case) => \sprintf('job.status.%s', \strtolower($case->name)), 'required' => false, 'multiple' => true, ], diff --git a/src/batch-symfony-framework/tests/UserInterface/Controller/JobControllerTest.php b/src/batch-symfony-framework/tests/UserInterface/Controller/JobControllerTest.php index a82399ec..0f287f9f 100644 --- a/src/batch-symfony-framework/tests/UserInterface/Controller/JobControllerTest.php +++ b/src/batch-symfony-framework/tests/UserInterface/Controller/JobControllerTest.php @@ -163,9 +163,9 @@ function () { ]; yield [ function () { - self::fixtures(6, ['status' => BatchStatus::PENDING]); - self::fixtures(4, ['status' => BatchStatus::RUNNING]); - self::fixtures(10, ['status' => BatchStatus::COMPLETED]); + self::fixtures(6, ['status' => BatchStatus::Pending]); + self::fixtures(4, ['status' => BatchStatus::Running]); + self::fixtures(10, ['status' => BatchStatus::Completed]); }, Request::create('/jobs?filter[statuses][]=1'), $formFactory, @@ -177,9 +177,9 @@ function () { ]; yield [ function () { - self::fixtures(30, ['jobName' => 'export', 'status' => BatchStatus::PENDING]); + self::fixtures(30, ['jobName' => 'export', 'status' => BatchStatus::Pending]); self::fixtures(5, ['jobName' => 'import']); - self::fixtures(5, ['status' => BatchStatus::COMPLETED]); + self::fixtures(5, ['status' => BatchStatus::Completed]); }, Request::create('/jobs?filter[jobs][]=export&filter[statuses][]=1&page=2'), $formFactory, @@ -233,7 +233,7 @@ public static function view(): \Generator $exportExecution = JobExecution::createRoot( '64edbe399b58e', 'export', - new BatchStatus(BatchStatus::COMPLETED), + BatchStatus::Completed, new JobParameters(['type' => 'complete']), new Summary(['count' => 156]), ); @@ -247,21 +247,21 @@ public static function view(): \Generator JobExecution::createChild( $exportExecution, 'download', - new BatchStatus(BatchStatus::COMPLETED), + BatchStatus::Completed, ), ); $exportExecution->addChildExecution( JobExecution::createChild( $exportExecution, 'transform', - new BatchStatus(BatchStatus::RUNNING), + BatchStatus::Running, ), ); $exportExecution->addChildExecution( JobExecution::createChild( $exportExecution, 'upload', - new BatchStatus(BatchStatus::PENDING), + BatchStatus::Pending, ), ); self::$storage->store($exportExecution); @@ -526,12 +526,12 @@ private static function fixtures(int $count, array $attributes = []): void [ 'id' => \uniqid(), 'jobName' => \array_rand(\array_flip(['export', 'import'])), - 'status' => \array_rand(\array_flip([ - BatchStatus::PENDING, - BatchStatus::RUNNING, - BatchStatus::COMPLETED, - BatchStatus::FAILED, - ])), + 'status' => BatchStatus::from(\array_rand(\array_flip([ + BatchStatus::Pending->value, + BatchStatus::Running->value, + BatchStatus::Completed->value, + BatchStatus::Failed->value, + ]))), 'startTime' => $start = (new \DateTimeImmutable())->setTimestamp(\random_int(0, \time() - 10)), 'endTime' => (new \DateTimeImmutable())->setTimestamp( \random_int($start->getTimestamp(), \time() - 10), @@ -543,11 +543,11 @@ private static function fixtures(int $count, array $attributes = []): void $execution = JobExecution::createRoot( $values['id'], $values['jobName'], - new BatchStatus($values['status']), + $values['status'], ); - if (!$execution->getStatus()->is(BatchStatus::PENDING)) { + if ($execution->getStatus() !== BatchStatus::Pending) { $execution->setStartTime($values['startTime']); - if (!$execution->getStatus()->is(BatchStatus::RUNNING)) { + if ($execution->getStatus() !== BatchStatus::Running) { $execution->setEndTime($values['endTime']); } } diff --git a/src/batch-symfony-framework/tests/UserInterface/Form/JobFilterTypeTest.php b/src/batch-symfony-framework/tests/UserInterface/Form/JobFilterTypeTest.php index 35b0b995..ac9497be 100644 --- a/src/batch-symfony-framework/tests/UserInterface/Form/JobFilterTypeTest.php +++ b/src/batch-symfony-framework/tests/UserInterface/Form/JobFilterTypeTest.php @@ -40,12 +40,12 @@ public function testBuild(): void ); self::assertSame( [ - 'job.status.pending' => BatchStatus::PENDING, - 'job.status.running' => BatchStatus::RUNNING, - 'job.status.stopped' => BatchStatus::STOPPED, - 'job.status.completed' => BatchStatus::COMPLETED, - 'job.status.abandoned' => BatchStatus::ABANDONED, - 'job.status.failed' => BatchStatus::FAILED, + 'job.status.pending' => BatchStatus::Pending, + 'job.status.running' => BatchStatus::Running, + 'job.status.stopped' => BatchStatus::Stopped, + 'job.status.completed' => BatchStatus::Completed, + 'job.status.abandoned' => BatchStatus::Abandoned, + 'job.status.failed' => BatchStatus::Failed, ], $choices($view->children['statuses']), ); @@ -79,8 +79,8 @@ public static function submit(): \Generator ]; yield [ - ['jobs' => ['export'], 'statuses' => [BatchStatus::PENDING]], - new JobFilter(['export'], [BatchStatus::PENDING]), + ['jobs' => ['export'], 'statuses' => [BatchStatus::Pending->value]], + new JobFilter(['export'], [BatchStatus::Pending]), true, ]; diff --git a/src/batch-symfony-messenger/src/DispatchMessageJobLauncher.php b/src/batch-symfony-messenger/src/DispatchMessageJobLauncher.php index 997e0ecc..f6e517c4 100644 --- a/src/batch-symfony-messenger/src/DispatchMessageJobLauncher.php +++ b/src/batch-symfony-messenger/src/DispatchMessageJobLauncher.php @@ -33,7 +33,7 @@ public function launch(string $name, array $configuration = []): JobExecution // guarantee job execution exists if message bus transport is asynchronous $jobExecution = $this->jobExecutionFactory->create($name, $configuration); $configuration['_id'] ??= $jobExecution->getId(); - $jobExecution->setStatus(BatchStatus::PENDING); + $jobExecution->setStatus(BatchStatus::Pending); $this->jobExecutionStorage->store($jobExecution); $message = new LaunchJobMessage($name, $configuration); @@ -49,7 +49,7 @@ public function launch(string $name, array $configuration = []): JobExecution $this->messageBus->dispatch($message); } catch (ExceptionInterface $exception) { // if a messenger exception occurs, it will be converted to job failure - $jobExecution->setStatus(BatchStatus::FAILED); + $jobExecution->setStatus(BatchStatus::Failed); $jobExecution->addFailureException($exception); $this->jobExecutionStorage->store($jobExecution); diff --git a/src/batch-symfony-messenger/tests/DispatchMessageJobLauncherTest.php b/src/batch-symfony-messenger/tests/DispatchMessageJobLauncherTest.php index f1b2bd68..77133786 100644 --- a/src/batch-symfony-messenger/tests/DispatchMessageJobLauncherTest.php +++ b/src/batch-symfony-messenger/tests/DispatchMessageJobLauncherTest.php @@ -43,7 +43,7 @@ public function testLaunch(): void self::assertSame('testing', $jobExecutionFromStorage->getJobName()); self::assertSame('123456789', $jobExecutionFromStorage->getId()); - self::assertSame(BatchStatus::PENDING, $jobExecutionFromStorage->getStatus()->getValue()); + self::assertSame(BatchStatus::Pending, $jobExecutionFromStorage->getStatus()); self::assertSame(['bar'], $jobExecutionFromStorage->getParameters()->get('foo')); self::assertJobWasTriggered($messageBus, 'testing', ['_id' => '123456789', 'foo' => ['bar']]); } @@ -68,7 +68,7 @@ public function testLaunchWithNoId(): void self::assertSame('testing', $jobExecutionFromStorage->getJobName()); self::assertSame('123456789', $jobExecutionFromStorage->getId()); - self::assertSame(BatchStatus::PENDING, $jobExecutionFromStorage->getStatus()->getValue()); + self::assertSame(BatchStatus::Pending, $jobExecutionFromStorage->getStatus()); self::assertJobWasTriggered($messageBus, 'testing', ['_id' => '123456789']); } @@ -91,7 +91,7 @@ public function testLaunchAndMessengerFail(): void self::assertSame($jobExecutionFromLauncher, $jobExecutionFromStorage); self::assertSame('testing', $jobExecutionFromStorage->getJobName()); - self::assertSame(BatchStatus::FAILED, $jobExecutionFromStorage->getStatus()->getValue()); + self::assertSame(BatchStatus::Failed, $jobExecutionFromStorage->getStatus()); self::assertCount(1, $jobExecutionFromStorage->getFailures()); $failure = $jobExecutionFromStorage->getFailures()[0]; self::assertSame(TransportException::class, $failure->getClass()); diff --git a/src/batch/src/BatchStatus.php b/src/batch/src/BatchStatus.php index dac2c55a..19786996 100644 --- a/src/batch/src/BatchStatus.php +++ b/src/batch/src/BatchStatus.php @@ -10,94 +10,49 @@ /** * The status of a job execution. */ -final readonly class BatchStatus implements \Stringable +enum BatchStatus: int { /** * The job execution has not started yet. * This is usually because you are using an asynchronous {@see JobLauncherInterface}. */ - public const PENDING = 1; + case Pending = 1; /** * The job execution has started and is not finished. */ - public const RUNNING = 2; + case Running = 2; /** * Something has stopped the job execution. * (not used yet) */ - public const STOPPED = 3; + case Stopped = 3; /** * The job execution has finished without error. */ - public const COMPLETED = 4; + case Completed = 4; /** * The job execution was not and won't be executed. * This is usually because you had a {@see JobWithChildJobs} * and one of your siblings job execution has failed. */ - public const ABANDONED = 5; + case Abandoned = 5; /** * An error occurred during the job execution. * Try finding more information in {@see JobExecution::$failures}. */ - public const FAILED = 6; - - private const LABELS = [ - self::PENDING => 'PENDING', - self::RUNNING => 'RUNNING', - self::STOPPED => 'STOPPED', - self::COMPLETED => 'COMPLETED', - self::ABANDONED => 'ABANDONED', - self::FAILED => 'FAILED', - ]; - - public function __construct( - private int $value, - ) { - } - - /** - * The status label. - */ - public function __toString(): string - { - return self::LABELS[$this->value] ?? 'UNKNOWN'; - } - - public function getValue(): int - { - return $this->value; - } - - /** - * Compare status value to another. - */ - public function is(int $value): bool - { - return $this->value === $value; - } - - /** - * Compare status value to some others. - * - * @param int[] $values - */ - public function isOneOf(array $values): bool - { - return \in_array($this->value, $values, true); - } + case Failed = 6; /** * Executed and not succeed. */ public function isUnsuccessful(): bool { - return $this->isOneOf([self::ABANDONED, self::STOPPED, self::FAILED]); + return \in_array($this, [self::Abandoned, self::Stopped, self::Failed], true); } /** @@ -105,7 +60,7 @@ public function isUnsuccessful(): bool */ public function isSuccessful(): bool { - return $this->is(self::COMPLETED); + return $this === self::Completed; } /** @@ -113,6 +68,6 @@ public function isSuccessful(): bool */ public function isExecutable(): bool { - return $this->is(self::PENDING); + return $this === self::Pending; } } diff --git a/src/batch/src/Event/ExceptionEvent.php b/src/batch/src/Event/ExceptionEvent.php index f1c5ef4b..a9a18241 100644 --- a/src/batch/src/Event/ExceptionEvent.php +++ b/src/batch/src/Event/ExceptionEvent.php @@ -18,7 +18,7 @@ */ final class ExceptionEvent extends JobEvent { - private int $status = BatchStatus::FAILED; + private BatchStatus $status = BatchStatus::Failed; public function __construct( JobExecution $execution, @@ -27,12 +27,12 @@ public function __construct( parent::__construct($execution); } - public function getStatus(): int + public function getStatus(): BatchStatus { return $this->status; } - public function setStatus(int $status): void + public function setStatus(BatchStatus $status): void { $this->status = $status; } diff --git a/src/batch/src/Job/JobExecutor.php b/src/batch/src/Job/JobExecutor.php index 25c2a821..f058eb43 100644 --- a/src/batch/src/Job/JobExecutor.php +++ b/src/batch/src/Job/JobExecutor.php @@ -54,12 +54,12 @@ public function execute(JobExecution $jobExecution): void // JobExecution needs to be stored before job is actually executed, // right after setting start time and status. $jobExecution->setStartTime(new DateTimeImmutable()); - $jobExecution->setStatus(BatchStatus::RUNNING); + $jobExecution->setStatus(BatchStatus::Running); $this->jobExecutionStorage->store($rootExecution); $this->eventDispatcher?->dispatch(new PreExecuteEvent($jobExecution)); - $status = BatchStatus::COMPLETED; + $status = BatchStatus::Completed; try { $this->jobRegistry->get($name)->execute($jobExecution); diff --git a/src/batch/src/Job/JobWithChildJobs.php b/src/batch/src/Job/JobWithChildJobs.php index 7a3d6276..6b52c618 100644 --- a/src/batch/src/Job/JobWithChildJobs.php +++ b/src/batch/src/Job/JobWithChildJobs.php @@ -52,7 +52,7 @@ final public function execute(JobExecution $jobExecution): void // If the job was marked as unsuccessful, the child will not be executed, and marked as abandoned if ($jobExecution->getStatus()->isUnsuccessful()) { - $childExecution->setStatus(BatchStatus::ABANDONED); + $childExecution->setStatus(BatchStatus::Abandoned); $logger->warning('Child job will not be executed', ['job' => $jobName]); continue; @@ -63,7 +63,7 @@ final public function execute(JobExecution $jobExecution): void // Check if the child executed successfully, replicate the status to the job otherwise if ($childExecution->getStatus()->isUnsuccessful()) { - $jobExecution->setStatus($childExecution->getStatus()->getValue()); + $jobExecution->setStatus($childExecution->getStatus()); $logger->error('Child job did not executed successfully', ['job' => $jobName]); } else { $logger->info('Child job executed successfully', ['job' => $jobName]); diff --git a/src/batch/src/JobExecution.php b/src/batch/src/JobExecution.php index 6bd19b62..889efd8d 100644 --- a/src/batch/src/JobExecution.php +++ b/src/batch/src/JobExecution.php @@ -114,7 +114,7 @@ private function __construct( $this->parentExecution = $parentExecution; $this->id = $id; $this->jobName = $jobName; - $this->status = $status ?: new BatchStatus(BatchStatus::PENDING); + $this->status = $status ?? BatchStatus::Pending; $this->parameters = $parameters ?: new JobParameters(); $this->summary = $summary ?: new Summary(); $this->logger = $logger ?? $parentExecution?->getLogger() ?? new InMemoryJobExecutionLogger(); @@ -162,10 +162,10 @@ public function getStatus(): BatchStatus return $this->status; } - public function setStatus(int $status): void + public function setStatus(BatchStatus $status): void { - if ($status > $this->status->getValue()) { - $this->status = new BatchStatus($status); + if ($status->value > $this->status->value) { + $this->status = $status; } } diff --git a/src/batch/src/Serializer/JsonJobExecutionSerializer.php b/src/batch/src/Serializer/JsonJobExecutionSerializer.php index ee6c1702..10085909 100644 --- a/src/batch/src/Serializer/JsonJobExecutionSerializer.php +++ b/src/batch/src/Serializer/JsonJobExecutionSerializer.php @@ -102,7 +102,7 @@ private function toArray(JobExecution $jobExecution): array return [ 'id' => $jobExecution->getId(), 'jobName' => $jobExecution->getJobName(), - 'status' => $jobExecution->getStatus()->getValue(), + 'status' => $jobExecution->getStatus()->value, 'parameters' => \iterator_to_array($jobExecution->getParameters()), 'startTime' => $this->dateToString($jobExecution->getStartTime()), 'endTime' => $this->dateToString($jobExecution->getEndTime()), @@ -121,7 +121,7 @@ private function toArray(JobExecution $jobExecution): array private function fromArray(array $jobExecutionData, JobExecution|null $parentExecution = null): JobExecution { $name = $jobExecutionData['jobName']; - $status = new BatchStatus($jobExecutionData['status']); + $status = BatchStatus::from($jobExecutionData['status']); $parameters = new JobParameters($jobExecutionData['parameters']); $summary = new Summary($jobExecutionData['summary']); diff --git a/src/batch/src/Storage/FilesystemJobExecutionStorage.php b/src/batch/src/Storage/FilesystemJobExecutionStorage.php index 93cf2625..d55baa4d 100644 --- a/src/batch/src/Storage/FilesystemJobExecutionStorage.php +++ b/src/batch/src/Storage/FilesystemJobExecutionStorage.php @@ -155,7 +155,7 @@ private function rawQuery(Query $query): array } $statuses = $query->statuses(); - if ($statuses !== [] && !$execution->getStatus()->isOneOf($statuses)) { + if ($statuses !== [] && !\in_array($execution->getStatus(), $statuses, true)) { continue; } diff --git a/src/batch/src/Storage/ListableJobExecutionStorageInterface.php b/src/batch/src/Storage/ListableJobExecutionStorageInterface.php index 6f8d0fae..5b03bd24 100644 --- a/src/batch/src/Storage/ListableJobExecutionStorageInterface.php +++ b/src/batch/src/Storage/ListableJobExecutionStorageInterface.php @@ -14,7 +14,7 @@ interface ListableJobExecutionStorageInterface extends JobExecutionStorageInterf /** * List all job executions that are for the given job. * - * @return iterable|JobExecution[] + * @return iterable */ public function list(string $jobName): iterable; } diff --git a/src/batch/src/Storage/Query.php b/src/batch/src/Storage/Query.php index f51d4b0f..f1886ed2 100644 --- a/src/batch/src/Storage/Query.php +++ b/src/batch/src/Storage/Query.php @@ -4,6 +4,7 @@ namespace Yokai\Batch\Storage; +use Yokai\Batch\BatchStatus; use Yokai\Batch\JobExecution; /** @@ -17,9 +18,6 @@ public const SORT_BY_END_ASC = 'end_asc'; public const SORT_BY_END_DESC = 'end_desc'; - /** - * @internal Do not use directly, use {@see QueryBuilder} instead. - */ public function __construct( /** * @var string[] @@ -30,7 +28,7 @@ public function __construct( */ private array $ids, /** - * @var int[] + * @var BatchStatus[] */ private array $statuses, private TimeFilter|null $startTime, @@ -58,7 +56,7 @@ public function ids(): array } /** - * @return int[] + * @return BatchStatus[] */ public function statuses(): array { diff --git a/src/batch/src/Storage/QueryBuilder.php b/src/batch/src/Storage/QueryBuilder.php index 146b54f9..cd061bc9 100644 --- a/src/batch/src/Storage/QueryBuilder.php +++ b/src/batch/src/Storage/QueryBuilder.php @@ -16,7 +16,7 @@ * (new QueryBuilder()) * ->ids(['123', '456']) * ->jobs(['export', 'import']) - * ->statuses([BatchStatus::RUNNING, BatchStatus::COMPLETED]) + * ->statuses([BatchStatus::Running, BatchStatus::Completed]) * ->startTime(new \DateTimeImmutable('2023-07-07 15:18'), new \DateTime('2023-07-07 16:30')) * ->endTime(new \DateTimeImmutable('2023-07-07 15:18'), new \DateTime('2023-07-07 16:30')) * ->sort(Query::SORT_BY_END_DESC) @@ -28,7 +28,7 @@ * $builder = new QueryBuilder(); * $builder->ids(['123', '456']); * $builder->jobs(['export', 'import']); - * $builder->statuses([BatchStatus::RUNNING, BatchStatus::COMPLETED]); + * $builder->statuses([BatchStatus::Running, BatchStatus::Completed]); * $builder->startTime(new \DateTimeImmutable('2023-07-07 15:18'), new \DateTime('2023-07-07 16:30')); * $builder->endTime(new \DateTimeImmutable('2023-07-07 15:18'), new \DateTime('2023-07-07 16:30')); * $builder->sort(Query::SORT_BY_END_DESC); @@ -44,15 +44,6 @@ final class QueryBuilder Query::SORT_BY_END_DESC, ]; - private const STATUSES_ENUM = [ - BatchStatus::PENDING, - BatchStatus::RUNNING, - BatchStatus::STOPPED, - BatchStatus::COMPLETED, - BatchStatus::ABANDONED, - BatchStatus::FAILED, - ]; - /** * @var string[] */ @@ -64,7 +55,7 @@ final class QueryBuilder private array $ids = []; /** - * @var int[] + * @var BatchStatus[] */ private array $statuses = []; @@ -119,18 +110,17 @@ public function ids(array $ids): self /** * Filter executions that are on given status. * - * @param int[] $statuses Any of {@see BatchStatus::*} + * @param BatchStatus[] $statuses */ public function statuses(array $statuses): self { - $statuses = \array_unique($statuses); foreach ($statuses as $status) { - if (!\in_array($status, self::STATUSES_ENUM, true)) { - throw UnexpectedValueException::enum(self::STATUSES_ENUM, $status); + if (!$status instanceof BatchStatus) { + throw UnexpectedValueException::type(BatchStatus::class, $status); } } - $this->statuses = $statuses; + $this->statuses = \array_unique($statuses, \SORT_REGULAR); return $this; } diff --git a/src/batch/src/Storage/QueryableJobExecutionStorageInterface.php b/src/batch/src/Storage/QueryableJobExecutionStorageInterface.php index 60a72c6a..db612d7e 100644 --- a/src/batch/src/Storage/QueryableJobExecutionStorageInterface.php +++ b/src/batch/src/Storage/QueryableJobExecutionStorageInterface.php @@ -14,7 +14,7 @@ interface QueryableJobExecutionStorageInterface extends ListableJobExecutionStor /** * Execute query against stored job executions, and return the matching list. * - * @return iterable|JobExecution[] + * @return iterable */ public function query(Query $query): iterable; diff --git a/src/batch/tests/BatchStatusTest.php b/src/batch/tests/BatchStatusTest.php index ae9c1f9f..e95871d0 100644 --- a/src/batch/tests/BatchStatusTest.php +++ b/src/batch/tests/BatchStatusTest.php @@ -11,23 +11,19 @@ final class BatchStatusTest extends TestCase { #[DataProvider('statuses')] - public function testStatus(int $value, string $label, bool $unsucessful): void + public function testStatus(BatchStatus $status, string $label, bool $unsuccessful): void { - $status = new BatchStatus($value); - self::assertSame($label, (string)$status); - self::assertSame($value, $status->getValue()); - self::assertTrue($status->is($value)); - self::assertTrue($status->isOneOf([$value])); - self::assertSame($unsucessful, $status->isUnsuccessful()); + self::assertSame($label, $status->name); + self::assertSame($unsuccessful, $status->isUnsuccessful()); } public static function statuses(): \Generator { - yield 'completed' => [BatchStatus::COMPLETED, 'COMPLETED', false]; - yield 'pending' => [BatchStatus::PENDING, 'PENDING', false]; - yield 'running' => [BatchStatus::RUNNING, 'RUNNING', false]; - yield 'stopped' => [BatchStatus::STOPPED, 'STOPPED', true]; - yield 'abandoned' => [BatchStatus::ABANDONED, 'ABANDONED', true]; - yield 'failed' => [BatchStatus::FAILED, 'FAILED', true]; + yield 'completed' => [BatchStatus::Completed, 'Completed', false]; + yield 'pending' => [BatchStatus::Pending, 'Pending', false]; + yield 'running' => [BatchStatus::Running, 'Running', false]; + yield 'stopped' => [BatchStatus::Stopped, 'Stopped', true]; + yield 'abandoned' => [BatchStatus::Abandoned, 'Abandoned', true]; + yield 'failed' => [BatchStatus::Failed, 'Failed', true]; } } diff --git a/src/batch/tests/Job/JobExecutorTest.php b/src/batch/tests/Job/JobExecutorTest.php index 80aefbda..70a49a08 100644 --- a/src/batch/tests/Job/JobExecutorTest.php +++ b/src/batch/tests/Job/JobExecutorTest.php @@ -53,7 +53,7 @@ public function testLaunch(): void self::assertNotNull($execution->getStartTime()); self::assertNotNull($execution->getEndTime()); - self::assertSame(BatchStatus::COMPLETED, $execution->getStatus()->getValue()); + self::assertSame(BatchStatus::Completed, $execution->getStatus()); self::assertSame('FOO', $execution->getSummary()->get('foo')); $logs = $execution->getLogger()->getLogsContent(); self::assertStringContainsString('DEBUG: Starting job', $logs); @@ -78,7 +78,7 @@ public function testLaunchJobCatchErrors(Throwable $error): void self::assertNotNull($execution->getStartTime()); self::assertNotNull($execution->getEndTime()); - self::assertSame(BatchStatus::FAILED, $execution->getStatus()->getValue()); + self::assertSame(BatchStatus::Failed, $execution->getStatus()); self::assertSame($error::class, $execution->getFailures()[0]->getClass()); self::assertSame($error->getMessage(), $execution->getFailures()[0]->getMessage()); $logs = $execution->getLogger()->getLogsContent(); @@ -103,7 +103,7 @@ public function testLaunchErrorWithStatusListener(): void ExceptionEvent::class, function (ExceptionEvent $event) use ($exception) { Assert::assertSame($exception, $event->getException()); - $event->setStatus(BatchStatus::COMPLETED); + $event->setStatus(BatchStatus::Completed); }, ); @@ -111,7 +111,7 @@ function (ExceptionEvent $event) use ($exception) { self::assertNotNull($execution->getStartTime()); self::assertNotNull($execution->getEndTime()); - self::assertSame(BatchStatus::COMPLETED, $execution->getStatus()->getValue()); + self::assertSame(BatchStatus::Completed, $execution->getStatus()); $logs = $execution->getLogger()->getLogsContent(); self::assertStringContainsString('DEBUG: Starting job', $logs); self::assertStringContainsString('INFO: Job executed successfully', $logs); @@ -127,7 +127,7 @@ public function testLaunchJobNotExecutable(): void $this->job->expects($this->never()) ->method('execute'); - $execution = JobExecution::createRoot('123', 'test.job_executor', new BatchStatus(BatchStatus::COMPLETED)); + $execution = JobExecution::createRoot('123', 'test.job_executor', BatchStatus::Completed); $this->executor->execute($execution); $logs = $execution->getLogger()->getLogsContent(); diff --git a/src/batch/tests/Job/JobWithChildJobsTest.php b/src/batch/tests/Job/JobWithChildJobsTest.php index d5478e18..b9ed584b 100644 --- a/src/batch/tests/Job/JobWithChildJobsTest.php +++ b/src/batch/tests/Job/JobWithChildJobsTest.php @@ -39,15 +39,15 @@ public function execute(JobExecution $jobExecution): void }, ]); - self::assertStatusSame(BatchStatus::COMPLETED, $execution); + self::assertStatusSame(BatchStatus::Completed, $execution); self::assertCount(2, $execution->getChildExecutions()); self::assertNotNull($import = $execution->getChildExecution('import')); - self::assertStatusSame(BatchStatus::COMPLETED, $import); + self::assertStatusSame(BatchStatus::Completed, $import); self::assertTrue($import->getSummary()->get('executed')); self::assertLogsContains('DEBUG: Starting child job {"job":"import"}', $execution); self::assertLogsContains('INFO: Child job executed successfully {"job":"import"}', $execution); self::assertNotNull($report = $execution->getChildExecution('report')); - self::assertStatusSame(BatchStatus::COMPLETED, $report); + self::assertStatusSame(BatchStatus::Completed, $report); self::assertTrue($report->getSummary()->get('executed')); self::assertLogsContains('DEBUG: Starting child job {"job":"report"}', $execution); self::assertLogsContains('INFO: Child job executed successfully {"job":"report"}', $execution); @@ -58,7 +58,7 @@ public function testNoChildren(): void $execution = $this->execute([ ]); - self::assertStatusSame(BatchStatus::COMPLETED, $execution); + self::assertStatusSame(BatchStatus::Completed, $execution); self::assertCount(0, $execution->getChildExecutions()); self::assertLogsNotContains('Child job executed successfully', $execution); self::assertLogsNotContains('Child job did not executed successfully', $execution); @@ -81,13 +81,13 @@ public function execute(JobExecution $jobExecution): void }, ]); - self::assertStatusSame(BatchStatus::FAILED, $execution); + self::assertStatusSame(BatchStatus::Failed, $execution); self::assertCount(2, $execution->getChildExecutions()); self::assertNotNull($import = $execution->getChildExecution('import')); - self::assertStatusSame(BatchStatus::FAILED, $import); + self::assertStatusSame(BatchStatus::Failed, $import); self::assertLogsContains('ERROR: Child job did not executed successfully {"job":"import"', $execution); self::assertNotNull($report = $execution->getChildExecution('report')); - self::assertStatusSame(BatchStatus::ABANDONED, $report); + self::assertStatusSame(BatchStatus::Abandoned, $report); self::assertLogsContains('WARNING: Child job will not be executed {"job":"report"}', $execution); } @@ -105,9 +105,9 @@ private function execute(array $children): JobExecution return $execution; } - private static function assertStatusSame(int $expected, JobExecution $execution): void + private static function assertStatusSame(BatchStatus $expected, JobExecution $execution): void { - self::assertSame($expected, $execution->getStatus()->getValue()); + self::assertSame($expected, $execution->getStatus()); } private static function assertLogsContains(string $expected, JobExecution $execution): void diff --git a/src/batch/tests/JobExecutionTest.php b/src/batch/tests/JobExecutionTest.php index e36224f2..6d3fc044 100644 --- a/src/batch/tests/JobExecutionTest.php +++ b/src/batch/tests/JobExecutionTest.php @@ -21,7 +21,7 @@ public function testConstruct(): void $fullJobExecution = JobExecution::createChild( $parent = JobExecution::createRoot('123456789', 'parent'), 'export', - $status = new BatchStatus(BatchStatus::STOPPED), + $status = BatchStatus::Stopped, $parameters = new JobParameters(), $summary = new Summary(), ); @@ -44,7 +44,7 @@ public function testConstruct(): void self::assertSame($minimalJobExecution, $minimalJobExecution->getRootExecution()); self::assertSame('987654321', $minimalJobExecution->getId()); self::assertSame('import', $minimalJobExecution->getJobName()); - self::assertSame(BatchStatus::PENDING, $minimalJobExecution->getStatus()->getValue()); + self::assertSame(BatchStatus::Pending, $minimalJobExecution->getStatus()); self::assertNotSame($parameters, $minimalJobExecution->getParameters()); self::assertInstanceOf(JobParameters::class, $minimalJobExecution->getParameters()); self::assertNotSame($summary, $minimalJobExecution->getSummary()); @@ -144,9 +144,9 @@ public function testLaunchedAtIsImmutable(): void public function testChangeStatus(): void { $jobExecution = JobExecution::createRoot('123456789', 'export'); - self::assertTrue($jobExecution->getStatus()->is(BatchStatus::PENDING)); - $jobExecution->setStatus(BatchStatus::COMPLETED); - self::assertTrue($jobExecution->getStatus()->is(BatchStatus::COMPLETED)); + self::assertSame(BatchStatus::Pending, $jobExecution->getStatus()); + $jobExecution->setStatus(BatchStatus::Completed); + self::assertSame(BatchStatus::Completed, $jobExecution->getStatus()); } public function testManipulatesFailures(): void diff --git a/src/batch/tests/Launcher/SimpleJobLauncherTest.php b/src/batch/tests/Launcher/SimpleJobLauncherTest.php index fb147ac3..43a955f3 100644 --- a/src/batch/tests/Launcher/SimpleJobLauncherTest.php +++ b/src/batch/tests/Launcher/SimpleJobLauncherTest.php @@ -42,7 +42,7 @@ public function test(): void $execution = $launcher->launch('phpunit'); self::assertSame('phpunit', $execution->getJobName()); self::assertSame('123', $execution->getId()); - self::assertSame(BatchStatus::COMPLETED, $execution->getStatus()->getValue()); + self::assertSame(BatchStatus::Completed, $execution->getStatus()); self::assertSame($execution, $jobExecutionStorage->retrieve('phpunit', '123')); } } diff --git a/src/batch/tests/Serializer/fixtures/fulfilled.object.php b/src/batch/tests/Serializer/fixtures/fulfilled.object.php index 737b452e..5f9dec40 100644 --- a/src/batch/tests/Serializer/fixtures/fulfilled.object.php +++ b/src/batch/tests/Serializer/fixtures/fulfilled.object.php @@ -25,7 +25,7 @@ $jobExecution = JobExecution::createRoot( '123456789', 'export', - new BatchStatus(BatchStatus::FAILED), + BatchStatus::Failed, new JobParameters($hash), new Summary($hash), new InMemoryJobExecutionLogger( @@ -46,7 +46,7 @@ $prepareChildExecution = JobExecution::createChild( $jobExecution, 'prepare', - new BatchStatus(BatchStatus::COMPLETED), + BatchStatus::Completed, null, new Summary($hash), ), @@ -58,7 +58,7 @@ $exportChildExecution = JobExecution::createChild( $jobExecution, 'export', - new BatchStatus(BatchStatus::FAILED), + BatchStatus::Failed, null, new Summary($hash), ), diff --git a/src/batch/tests/Storage/FilesystemJobExecutionStorageTest.php b/src/batch/tests/Storage/FilesystemJobExecutionStorageTest.php index 61723470..f3aa2c5a 100644 --- a/src/batch/tests/Storage/FilesystemJobExecutionStorageTest.php +++ b/src/batch/tests/Storage/FilesystemJobExecutionStorageTest.php @@ -178,7 +178,7 @@ public static function query(): \Generator ]; yield 'Filter statuses' => [ (new QueryBuilder()) - ->statuses([BatchStatus::FAILED]), + ->statuses([BatchStatus::Failed]), [ ['list', '20210910'], ], diff --git a/src/batch/tests/Storage/QueryBuilderTest.php b/src/batch/tests/Storage/QueryBuilderTest.php index 5432cc5c..80b6b04a 100644 --- a/src/batch/tests/Storage/QueryBuilderTest.php +++ b/src/batch/tests/Storage/QueryBuilderTest.php @@ -54,11 +54,11 @@ public static function valid(): \Generator new Query($jobNames, ['id1', 'id2', 'id3'], $statuses, $startTime, $endTime, $sortBy, $limit, $offset), ]; yield 'Query job statuses' => [ - fn() => (new QueryBuilder())->statuses([BatchStatus::ABANDONED, BatchStatus::STOPPED]), + fn() => (new QueryBuilder())->statuses([BatchStatus::Abandoned, BatchStatus::Stopped]), new Query( jobs: $jobNames, ids: $ids, - statuses: [BatchStatus::ABANDONED, BatchStatus::STOPPED], + statuses: [BatchStatus::Abandoned, BatchStatus::Stopped], startTime: $startTime, endTime: $endTime, sort: $sortBy, @@ -152,7 +152,7 @@ public static function valid(): \Generator fn() => (new QueryBuilder()) ->ids(['123', '456']) ->jobs(['export', 'import']) - ->statuses([BatchStatus::RUNNING, BatchStatus::COMPLETED]) + ->statuses([BatchStatus::Running, BatchStatus::Completed]) ->startTime($startTimeFrom, $startTimeTo) ->endTime($endTimeFrom, $endTimeTo) ->sort(Query::SORT_BY_END_DESC) @@ -160,7 +160,7 @@ public static function valid(): \Generator new Query( jobs: ['export', 'import'], ids: ['123', '456'], - statuses: [BatchStatus::RUNNING, BatchStatus::COMPLETED], + statuses: [BatchStatus::Running, BatchStatus::Completed], startTime: new TimeFilter($startTimeFrom, $startTimeTo), endTime: new TimeFilter($endTimeFrom, $endTimeTo), sort: Query::SORT_BY_END_DESC, @@ -187,19 +187,9 @@ public static function invalid(): \Generator fn() => (new QueryBuilder())->ids(['string', 666]), UnexpectedValueException::type('string', 666), ]; - yield 'QueryBuilder::statuses expect BatchStatus::* constant array' => [ - fn() => (new QueryBuilder())->statuses([BatchStatus::FAILED, 666]), - UnexpectedValueException::enum( - [ - BatchStatus::PENDING, - BatchStatus::RUNNING, - BatchStatus::STOPPED, - BatchStatus::COMPLETED, - BatchStatus::ABANDONED, - BatchStatus::FAILED, - ], - 666, - ), + yield 'QueryBuilder::statuses expect BatchStatus array' => [ + fn() => (new QueryBuilder())->statuses([BatchStatus::Failed, 666]), + UnexpectedValueException::type(BatchStatus::class, 666), ]; yield 'QueryBuilder::sort expect any Query::SORT_*' => [ fn() => (new QueryBuilder())->sort('wrong'), diff --git a/tests/integration/DummyJobTest.php b/tests/integration/DummyJobTest.php index 5f9574aa..32672175 100644 --- a/tests/integration/DummyJobTest.php +++ b/tests/integration/DummyJobTest.php @@ -32,7 +32,7 @@ protected function assertAgainstExecution( ): void { parent::assertAgainstExecution($jobExecutionStorage, $jobExecution); - self::assertSame(BatchStatus::COMPLETED, $jobExecution->getStatus()->getValue()); + self::assertSame(BatchStatus::Completed, $jobExecution->getStatus()); self::assertTrue($jobExecution->getSummary()->get('done')); } } diff --git a/tests/integration/FailingDummyJobTest.php b/tests/integration/FailingDummyJobTest.php index bb22a97c..bc82c479 100644 --- a/tests/integration/FailingDummyJobTest.php +++ b/tests/integration/FailingDummyJobTest.php @@ -36,7 +36,7 @@ protected function assertAgainstExecution( ): void { parent::assertAgainstExecution($jobExecutionStorage, $jobExecution); - self::assertTrue($jobExecution->getStatus()->is(BatchStatus::FAILED)); + self::assertTrue($jobExecution->getStatus() === BatchStatus::Failed); self::assertCount(2, $jobExecution->getFailures()); self::assertCount(1, $jobExecution->getWarnings()); } diff --git a/tests/integration/JobTestCase.php b/tests/integration/JobTestCase.php index 7e771c8b..59acca0e 100644 --- a/tests/integration/JobTestCase.php +++ b/tests/integration/JobTestCase.php @@ -130,7 +130,7 @@ private static function storages(): \Iterator private static function compareStatuses(BatchStatus $expected, BatchStatus $actual): void { - self::assertSame($expected->getValue(), $actual->getValue()); + self::assertSame($expected, $actual); } private static function compareDates(null|\DateTimeInterface $expected, null|\DateTimeInterface $actual): void diff --git a/tests/integration/JobWithDummyChildrenTest.php b/tests/integration/JobWithDummyChildrenTest.php index 09a5be25..b1e874d6 100644 --- a/tests/integration/JobWithDummyChildrenTest.php +++ b/tests/integration/JobWithDummyChildrenTest.php @@ -45,14 +45,14 @@ protected function assertAgainstExecution( ): void { parent::assertAgainstExecution($jobExecutionStorage, $jobExecution); - self::assertSame(BatchStatus::COMPLETED, $jobExecution->getStatus()->getValue()); + self::assertSame(BatchStatus::Completed, $jobExecution->getStatus()); $prepareChildExecution = $jobExecution->getChildExecution('prepare'); - self::assertSame(BatchStatus::COMPLETED, $prepareChildExecution->getStatus()->getValue()); + self::assertSame(BatchStatus::Completed, $prepareChildExecution->getStatus()); self::assertTrue($prepareChildExecution->getSummary()->get('done')); $doChildExecution = $jobExecution->getChildExecution('do'); - self::assertSame(BatchStatus::COMPLETED, $doChildExecution->getStatus()->getValue()); + self::assertSame(BatchStatus::Completed, $doChildExecution->getStatus()); self::assertTrue($doChildExecution->getSummary()->get('done')); } } diff --git a/tests/integration/JobWithDummyItemChildrenTest.php b/tests/integration/JobWithDummyItemChildrenTest.php index 6936c410..7089f4f1 100644 --- a/tests/integration/JobWithDummyItemChildrenTest.php +++ b/tests/integration/JobWithDummyItemChildrenTest.php @@ -75,9 +75,9 @@ protected function assertAgainstExecution( ): void { parent::assertAgainstExecution($jobExecutionStorage, $jobExecution); - self::assertSame(BatchStatus::COMPLETED, $jobExecution->getStatus()->getValue()); + self::assertSame(BatchStatus::Completed, $jobExecution->getStatus()); foreach ($jobExecution->getChildExecutions() as $childExecution) { - self::assertSame(BatchStatus::COMPLETED, $childExecution->getStatus()->getValue()); + self::assertSame(BatchStatus::Completed, $childExecution->getStatus()); } $output = self::OUTPUT_FILE; diff --git a/tests/integration/JobWithFailingDummyChidlrenTest.php b/tests/integration/JobWithFailingDummyChidlrenTest.php index 18e7cd79..f9318bc2 100644 --- a/tests/integration/JobWithFailingDummyChidlrenTest.php +++ b/tests/integration/JobWithFailingDummyChidlrenTest.php @@ -46,13 +46,13 @@ protected function assertAgainstExecution( ): void { parent::assertAgainstExecution($jobExecutionStorage, $jobExecution); - self::assertSame(BatchStatus::FAILED, $jobExecution->getStatus()->getValue()); + self::assertSame(BatchStatus::Failed, $jobExecution->getStatus()); $prepareChildExecution = $jobExecution->getChildExecution('prepare'); - self::assertSame(BatchStatus::FAILED, $prepareChildExecution->getStatus()->getValue()); + self::assertSame(BatchStatus::Failed, $prepareChildExecution->getStatus()); $doChildExecution = $jobExecution->getChildExecution('do'); - self::assertSame(BatchStatus::ABANDONED, $doChildExecution->getStatus()->getValue()); + self::assertSame(BatchStatus::Abandoned, $doChildExecution->getStatus()); self::assertNull($doChildExecution->getSummary()->get('done')); } } From 506fc96cdb63034fbcc9563182c3c759d06b535c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yann=20Eugon=C3=A9?= Date: Thu, 16 Apr 2026 19:17:15 +0200 Subject: [PATCH 2/3] Convert Query sort constants to SortDirection backed enum - Add SortDirection enum (StartAsc, StartDesc, EndAsc, EndDesc) - Remove SORT_BY_* constants from Query - QueryBuilder::sort() now accepts SortDirection instead of string - Query::sort() now returns SortDirection|null - FilesystemJobExecutionStorage and DoctrineDBALJobExecutionStorage use enum cases in match/switch - JobController parses sort from request using SortDirection::tryFrom() - All tests updated --- .../src/DoctrineDBALJobExecutionStorage.php | 25 ++++++------------- .../DoctrineDBALJobExecutionStorageTest.php | 9 ++++--- .../Controller/JobController.php | 17 +++++++------ .../Storage/FilesystemJobExecutionStorage.php | 8 +++--- src/batch/src/Storage/Query.php | 9 ++----- src/batch/src/Storage/QueryBuilder.php | 21 +++------------- src/batch/src/Storage/SortDirection.php | 16 ++++++++++++ .../FilesystemJobExecutionStorageTest.php | 9 ++++--- src/batch/tests/Storage/QueryBuilderTest.php | 21 ++++------------ 9 files changed, 58 insertions(+), 77 deletions(-) create mode 100644 src/batch/src/Storage/SortDirection.php diff --git a/src/batch-doctrine-dbal/src/DoctrineDBALJobExecutionStorage.php b/src/batch-doctrine-dbal/src/DoctrineDBALJobExecutionStorage.php index 153a683a..6e12fa51 100644 --- a/src/batch-doctrine-dbal/src/DoctrineDBALJobExecutionStorage.php +++ b/src/batch-doctrine-dbal/src/DoctrineDBALJobExecutionStorage.php @@ -20,6 +20,7 @@ use Doctrine\Persistence\ConnectionRegistry; use Generator; use Yokai\Batch\BatchStatus; +use Yokai\Batch\Storage\SortDirection; use Yokai\Batch\Exception\CannotRemoveJobExecutionException; use Yokai\Batch\Factory\JobExecutionLoggerFactoryInterface; use Yokai\Batch\Exception\CannotStoreJobExecutionException; @@ -155,23 +156,13 @@ public function query(Query $query): iterable [$queryParameters, $queryTypes] = $this->addWheres($query, $qb); - switch ($query->sort()) { - case Query::SORT_BY_START_ASC: - $qb->orderBy('start_time', 'asc'); - break; - - case Query::SORT_BY_START_DESC: - $qb->orderBy('start_time', 'desc'); - break; - - case Query::SORT_BY_END_ASC: - $qb->orderBy('end_time', 'asc'); - break; - - case Query::SORT_BY_END_DESC: - $qb->orderBy('end_time', 'desc'); - break; - } + match ($query->sort()) { + SortDirection::StartAsc => $qb->orderBy('start_time', 'asc'), + SortDirection::StartDesc => $qb->orderBy('start_time', 'desc'), + SortDirection::EndAsc => $qb->orderBy('end_time', 'asc'), + SortDirection::EndDesc => $qb->orderBy('end_time', 'desc'), + default => null, + }; $qb->setMaxResults($query->limit()); $qb->setFirstResult($query->offset()); diff --git a/src/batch-doctrine-dbal/tests/DoctrineDBALJobExecutionStorageTest.php b/src/batch-doctrine-dbal/tests/DoctrineDBALJobExecutionStorageTest.php index 48c5ca3b..015d9102 100644 --- a/src/batch-doctrine-dbal/tests/DoctrineDBALJobExecutionStorageTest.php +++ b/src/batch-doctrine-dbal/tests/DoctrineDBALJobExecutionStorageTest.php @@ -21,6 +21,7 @@ use Yokai\Batch\JobExecution; use Yokai\Batch\Storage\Query; use Yokai\Batch\Storage\QueryBuilder; +use Yokai\Batch\Storage\SortDirection; use Yokai\Batch\Test\Storage\JobExecutionStorageTestTrait; use Yokai\Batch\Warning; @@ -322,7 +323,7 @@ public static function queries(): Generator ]; yield 'Order by start ASC' => [ (new QueryBuilder()) - ->sort(Query::SORT_BY_START_ASC), + ->sort(SortDirection::StartAsc), [ ['import', '987'], ['import', '789'], @@ -332,7 +333,7 @@ public static function queries(): Generator ]; yield 'Order by start DESC' => [ (new QueryBuilder()) - ->sort(Query::SORT_BY_START_DESC), + ->sort(SortDirection::StartDesc), [ ['import', '456'], ['export', '123'], @@ -342,7 +343,7 @@ public static function queries(): Generator ]; yield 'Order by end ASC' => [ (new QueryBuilder()) - ->sort(Query::SORT_BY_END_ASC), + ->sort(SortDirection::EndAsc), [ ['import', '789'], ['import', '987'], @@ -352,7 +353,7 @@ public static function queries(): Generator ]; yield 'Order by end DESC' => [ (new QueryBuilder()) - ->sort(Query::SORT_BY_END_DESC), + ->sort(SortDirection::EndDesc), [ ['import', '456'], ['export', '123'], diff --git a/src/batch-symfony-framework/src/UserInterface/Controller/JobController.php b/src/batch-symfony-framework/src/UserInterface/Controller/JobController.php index 85c2dc9f..1659906e 100644 --- a/src/batch-symfony-framework/src/UserInterface/Controller/JobController.php +++ b/src/batch-symfony-framework/src/UserInterface/Controller/JobController.php @@ -20,9 +20,9 @@ use Yokai\Batch\Bridge\Symfony\Framework\UserInterface\Templating\TemplatingInterface; use Yokai\Batch\Exception\JobExecutionNotFoundException; use Yokai\Batch\JobExecution; -use Yokai\Batch\Storage\Query; use Yokai\Batch\Storage\QueryableJobExecutionStorageInterface; use Yokai\Batch\Storage\QueryBuilder; +use Yokai\Batch\Storage\SortDirection; /** * Controller handling HTTP layer of user interface. @@ -47,7 +47,7 @@ public function list(Request $request): Response $this->security->denyAccessUnlessGrantedList(); $page = $request->query->getInt('page', 1); - $sort = (string)$request->query->get('sort', Query::SORT_BY_START_DESC); + $sort = (string)$request->query->get('sort', SortDirection::StartDesc->value); $query = new QueryBuilder(); @@ -84,6 +84,7 @@ public function list(Request $request): Response try { $query->limit($pageSize, $pageSize * ($page - 1)); + $sort = SortDirection::from($sort); $query->sort($sort); } catch (Throwable $exception) { throw new BadRequestHttpException(previous: $exception); @@ -98,17 +99,17 @@ public function list(Request $request): Response // prepare sort variable for view $sort = [ 'parameter' => 'sort', - 'current' => $sort, - 'desc' => \in_array($sort, [Query::SORT_BY_START_DESC, Query::SORT_BY_END_DESC], true), + 'current' => $sort->value, + 'desc' => \in_array($sort, [SortDirection::StartDesc, SortDirection::EndDesc], true), // sort by execution start info 'start' => [ - 'switch' => $sort === Query::SORT_BY_START_DESC ? Query::SORT_BY_START_ASC : Query::SORT_BY_START_DESC, - 'sorted' => \in_array($sort, [Query::SORT_BY_START_ASC, Query::SORT_BY_START_DESC], true), + 'switch' => ($sort === SortDirection::StartDesc ? SortDirection::StartAsc : SortDirection::StartDesc)->value, + 'sorted' => \in_array($sort, [SortDirection::StartAsc, SortDirection::StartDesc], true), ], // sort by execution end info 'end' => [ - 'switch' => $sort === Query::SORT_BY_END_DESC ? Query::SORT_BY_END_ASC : Query::SORT_BY_END_DESC, - 'sorted' => \in_array($sort, [Query::SORT_BY_END_ASC, Query::SORT_BY_END_DESC], true), + 'switch' => ($sort === SortDirection::EndDesc ? SortDirection::EndAsc : SortDirection::EndDesc)->value, + 'sorted' => \in_array($sort, [SortDirection::EndAsc, SortDirection::EndDesc], true), ], ]; // prepare pagination variable for view diff --git a/src/batch/src/Storage/FilesystemJobExecutionStorage.php b/src/batch/src/Storage/FilesystemJobExecutionStorage.php index d55baa4d..078fdb0a 100644 --- a/src/batch/src/Storage/FilesystemJobExecutionStorage.php +++ b/src/batch/src/Storage/FilesystemJobExecutionStorage.php @@ -94,10 +94,10 @@ public function query(Query $query): iterable $jobExecutions = $this->rawQuery($query); $order = match ($query->sort()) { - Query::SORT_BY_START_ASC => static fn(JobExecution $left, JobExecution $right): int => $left->getStartTime() <=> $right->getStartTime(), - Query::SORT_BY_START_DESC => static fn(JobExecution $left, JobExecution $right): int => $right->getStartTime() <=> $left->getStartTime(), - Query::SORT_BY_END_ASC => static fn(JobExecution $left, JobExecution $right): int => $left->getEndTime() <=> $right->getEndTime(), - Query::SORT_BY_END_DESC => static fn(JobExecution $left, JobExecution $right): int => $right->getEndTime() <=> $left->getEndTime(), + SortDirection::StartAsc => static fn(JobExecution $left, JobExecution $right): int => $left->getStartTime() <=> $right->getStartTime(), + SortDirection::StartDesc => static fn(JobExecution $left, JobExecution $right): int => $right->getStartTime() <=> $left->getStartTime(), + SortDirection::EndAsc => static fn(JobExecution $left, JobExecution $right): int => $left->getEndTime() <=> $right->getEndTime(), + SortDirection::EndDesc => static fn(JobExecution $left, JobExecution $right): int => $right->getEndTime() <=> $left->getEndTime(), default => null, }; diff --git a/src/batch/src/Storage/Query.php b/src/batch/src/Storage/Query.php index f1886ed2..84c94ea3 100644 --- a/src/batch/src/Storage/Query.php +++ b/src/batch/src/Storage/Query.php @@ -13,11 +13,6 @@ */ final readonly class Query { - public const SORT_BY_START_ASC = 'start_asc'; - public const SORT_BY_START_DESC = 'start_desc'; - public const SORT_BY_END_ASC = 'end_asc'; - public const SORT_BY_END_DESC = 'end_desc'; - public function __construct( /** * @var string[] @@ -33,7 +28,7 @@ public function __construct( private array $statuses, private TimeFilter|null $startTime, private TimeFilter|null $endTime, - private string|null $sort, + private SortDirection|null $sort, private int $limit, private int $offset, ) { @@ -73,7 +68,7 @@ public function endTime(): TimeFilter|null return $this->endTime; } - public function sort(): string|null + public function sort(): SortDirection|null { return $this->sort; } diff --git a/src/batch/src/Storage/QueryBuilder.php b/src/batch/src/Storage/QueryBuilder.php index cd061bc9..c4b1003c 100644 --- a/src/batch/src/Storage/QueryBuilder.php +++ b/src/batch/src/Storage/QueryBuilder.php @@ -19,7 +19,7 @@ * ->statuses([BatchStatus::Running, BatchStatus::Completed]) * ->startTime(new \DateTimeImmutable('2023-07-07 15:18'), new \DateTime('2023-07-07 16:30')) * ->endTime(new \DateTimeImmutable('2023-07-07 15:18'), new \DateTime('2023-07-07 16:30')) - * ->sort(Query::SORT_BY_END_DESC) + * ->sort(SortDirection::EndDesc) * ->limit(6, 12) * ->getQuery(); * @@ -31,19 +31,12 @@ * $builder->statuses([BatchStatus::Running, BatchStatus::Completed]); * $builder->startTime(new \DateTimeImmutable('2023-07-07 15:18'), new \DateTime('2023-07-07 16:30')); * $builder->endTime(new \DateTimeImmutable('2023-07-07 15:18'), new \DateTime('2023-07-07 16:30')); - * $builder->sort(Query::SORT_BY_END_DESC); + * $builder->sort(SortDirection::EndDesc); * $builder->limit(6, 12); * $builder->getQuery(); */ final class QueryBuilder { - private const SORTS_ENUM = [ - Query::SORT_BY_START_ASC, - Query::SORT_BY_START_DESC, - Query::SORT_BY_END_ASC, - Query::SORT_BY_END_DESC, - ]; - /** * @var string[] */ @@ -63,7 +56,7 @@ final class QueryBuilder private TimeFilter|null $endTime = null; - private string|null $sortBy = null; + private SortDirection|null $sortBy = null; private int $limit = 10; @@ -165,15 +158,9 @@ public function endTime(DateTimeInterface|null $from, DateTimeInterface|null $to /** * Sort executions. - * - * @param string $by One of {@see QueryBuilder::SORT_BY_*} */ - public function sort(string $by): self + public function sort(SortDirection $by): self { - if (!\in_array($by, self::SORTS_ENUM, true)) { - throw UnexpectedValueException::enum(self::SORTS_ENUM, $by); - } - $this->sortBy = $by; return $this; diff --git a/src/batch/src/Storage/SortDirection.php b/src/batch/src/Storage/SortDirection.php new file mode 100644 index 00000000..eedb9bf0 --- /dev/null +++ b/src/batch/src/Storage/SortDirection.php @@ -0,0 +1,16 @@ + [ (new QueryBuilder()) - ->sort(Query::SORT_BY_START_ASC), + ->sort(SortDirection::StartAsc), [ ['list', '20210910'], ['list', '20210915'], @@ -196,7 +197,7 @@ public static function query(): \Generator ]; yield 'Order by start DESC' => [ (new QueryBuilder()) - ->sort(Query::SORT_BY_START_DESC), + ->sort(SortDirection::StartDesc), [ ['export', '20210922'], ['list', '20210920'], @@ -207,7 +208,7 @@ public static function query(): \Generator ]; yield 'Order by end ASC' => [ (new QueryBuilder()) - ->sort(Query::SORT_BY_END_ASC), + ->sort(SortDirection::EndAsc), [ ['list', '20210910'], ['list', '20210915'], @@ -218,7 +219,7 @@ public static function query(): \Generator ]; yield 'Order by end DESC' => [ (new QueryBuilder()) - ->sort(Query::SORT_BY_END_DESC), + ->sort(SortDirection::EndDesc), [ ['export', '20210922'], ['list', '20210920'], diff --git a/src/batch/tests/Storage/QueryBuilderTest.php b/src/batch/tests/Storage/QueryBuilderTest.php index 80b6b04a..942d9b71 100644 --- a/src/batch/tests/Storage/QueryBuilderTest.php +++ b/src/batch/tests/Storage/QueryBuilderTest.php @@ -11,6 +11,7 @@ use Yokai\Batch\Exception\UnexpectedValueException; use Yokai\Batch\Storage\Query; use Yokai\Batch\Storage\QueryBuilder; +use Yokai\Batch\Storage\SortDirection; use Yokai\Batch\Storage\TimeFilter; final class QueryBuilderTest extends TestCase @@ -67,14 +68,14 @@ public static function valid(): \Generator ), ]; yield 'Query with sort' => [ - fn() => (new QueryBuilder())->sort(Query::SORT_BY_START_DESC), + fn() => (new QueryBuilder())->sort(SortDirection::StartDesc), new Query( jobs: $jobNames, ids: $ids, statuses: $statuses, startTime: $startTime, endTime: $endTime, - sort: Query::SORT_BY_START_DESC, + sort: SortDirection::StartDesc, limit: $limit, offset: $offset, ), @@ -155,7 +156,7 @@ public static function valid(): \Generator ->statuses([BatchStatus::Running, BatchStatus::Completed]) ->startTime($startTimeFrom, $startTimeTo) ->endTime($endTimeFrom, $endTimeTo) - ->sort(Query::SORT_BY_END_DESC) + ->sort(SortDirection::EndDesc) ->limit(6, 12), new Query( jobs: ['export', 'import'], @@ -163,7 +164,7 @@ public static function valid(): \Generator statuses: [BatchStatus::Running, BatchStatus::Completed], startTime: new TimeFilter($startTimeFrom, $startTimeTo), endTime: new TimeFilter($endTimeFrom, $endTimeTo), - sort: Query::SORT_BY_END_DESC, + sort: SortDirection::EndDesc, limit: 6, offset: 12, ), @@ -191,18 +192,6 @@ public static function invalid(): \Generator fn() => (new QueryBuilder())->statuses([BatchStatus::Failed, 666]), UnexpectedValueException::type(BatchStatus::class, 666), ]; - yield 'QueryBuilder::sort expect any Query::SORT_*' => [ - fn() => (new QueryBuilder())->sort('wrong'), - UnexpectedValueException::enum( - [ - Query::SORT_BY_START_ASC, - Query::SORT_BY_START_DESC, - Query::SORT_BY_END_ASC, - Query::SORT_BY_END_DESC, - ], - 'wrong', - ), - ]; yield 'QueryBuilder::limit $limit argument expect positive int' => [ fn() => (new QueryBuilder())->limit(0, 0), UnexpectedValueException::min(1, 0), From 44ab1548c12338e623c2d174d7de2ffce2d66e7e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yann=20Eugon=C3=A9?= Date: Fri, 17 Apr 2026 09:27:43 +0200 Subject: [PATCH 3/3] Add enum changes to upgrade guide --- UPGRADE-1.0.md | 103 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 1 deletion(-) diff --git a/UPGRADE-1.0.md b/UPGRADE-1.0.md index 26c1cf8e..dd4454bc 100644 --- a/UPGRADE-1.0.md +++ b/UPGRADE-1.0.md @@ -214,6 +214,107 @@ composition instead. Extending `final` classes or `readonly` classes is a PHP co --- +### `BatchStatus` is now a backed enum (BREAKING) + +`BatchStatus` has been converted from a `readonly class` with integer constants to a PHP 8.1 +`backed int enum`. + +#### Before / After + +```diff +-use Yokai\Batch\BatchStatus; + +-$status = new BatchStatus(BatchStatus::PENDING); +-$status->getValue(); // int +-(string) $status; // "PENDING" + ++$status = BatchStatus::Pending; ++$status->value; // int (1) ++$status->name; // "Pending" +``` + +#### Removed + +| Removed | Replacement | +|---------|-------------| +| `BatchStatus::PENDING` (int constant) | `BatchStatus::Pending` (enum case) | +| `BatchStatus::RUNNING` | `BatchStatus::Running` | +| `BatchStatus::STOPPED` | `BatchStatus::Stopped` | +| `BatchStatus::COMPLETED` | `BatchStatus::Completed` | +| `BatchStatus::ABANDONED` | `BatchStatus::Abandoned` | +| `BatchStatus::FAILED` | `BatchStatus::Failed` | +| `BatchStatus::__construct(int $value)` | — | +| `BatchStatus::getValue(): int` | `BatchStatus->value` (enum property) | +| `BatchStatus::is(int $value): bool` | `=== BatchStatus::CaseName` | +| `BatchStatus::isOneOf(int ...$values): bool` | `in_array($status, [...], true)` | +| `BatchStatus::__toString()` | `BatchStatus->name` | + +#### Signature changes + +```diff +-JobExecution::setStatus(BatchStatus $status) // was: setStatus(int $value) +-ExceptionEvent::getStatus(): BatchStatus // was: getStatus(): int (via BatchStatus object) +-ExceptionEvent::setStatus(BatchStatus $status) // was: setStatus(int $value) + +// QueryBuilder / Query +-QueryBuilder::statuses(int ...$statuses): self ++QueryBuilder::statuses(BatchStatus ...$statuses): self + +-Query::statuses(): int[] ++Query::statuses(): BatchStatus[] +``` + +#### Usage example + +```php +use Yokai\Batch\Storage\QueryBuilder; +use Yokai\Batch\BatchStatus; + +$storage->purge( + (new QueryBuilder()) + ->statuses(BatchStatus::Failed, BatchStatus::Abandoned) + ->getQuery() +); +``` + +--- + +### `SortDirection` is now a backed enum (BREAKING) + +The `SORT_BY_*` string constants on `Query` have been removed and replaced by the +`SortDirection` backed string enum. + +#### Before / After + +```diff +-use Yokai\Batch\Storage\Query; ++use Yokai\Batch\Storage\SortDirection; + +-(new QueryBuilder())->sort(Query::SORT_BY_END_DESC); ++(new QueryBuilder())->sort(SortDirection::EndDesc); +``` + +#### Removed + +| Removed | Replacement | +|---------|-------------| +| `Query::SORT_BY_START_ASC` | `SortDirection::StartAsc` | +| `Query::SORT_BY_START_DESC` | `SortDirection::StartDesc` | +| `Query::SORT_BY_END_ASC` | `SortDirection::EndAsc` | +| `Query::SORT_BY_END_DESC` | `SortDirection::EndDesc` | + +#### Signature changes + +```diff +-QueryBuilder::sort(string $by): self // threw UnexpectedValueException on invalid values ++QueryBuilder::sort(SortDirection $by): self + +-Query::sort(): string|null ++Query::sort(): SortDirection|null +``` + +--- + ### New method: `QueryableJobExecutionStorageInterface::purge()` (BREAKING for custom implementations) A new method has been added to `QueryableJobExecutionStorageInterface`: @@ -236,7 +337,7 @@ use Yokai\Batch\Storage\QueryBuilder; $storage->purge( (new QueryBuilder()) ->jobs(['import']) - ->statuses([BatchStatus::FAILED]) + ->statuses(BatchStatus::Failed) ->getQuery() ); ```