From af8de7d98b4fb77fdffb7bb1bd5bc9a1877db07c Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 00:56:51 +0200 Subject: [PATCH 01/59] Update to PHP 8.4 and new dependencies, and break --- .github/workflows/phpunit.yml | 41 +++++++++++++ .gitignore | 1 + Dockerfile | 39 ++++++++++--- Makefile | 47 +++++++++++++++ compose.yaml | 21 +++++++ composer.json | 58 +++++++++++-------- examples/bootstrap.php | 2 +- phpstan.neon | 3 +- rector.php | 17 ++++++ spec/Recruiter/Acceptance/AssignmentTest.php | 2 +- ...nceTest.php => BaseAcceptanceTestCase.php} | 7 ++- spec/Recruiter/Acceptance/EnduranceTest.php | 4 +- .../Acceptance/FaultToleranceTest.php | 2 +- spec/Recruiter/Acceptance/HooksTest.php | 12 ++-- .../RepeatableJobsAreScheduledTest.php | 6 +- .../Acceptance/SyncronousExecutionTest.php | 2 +- ...rGuaranteedToExitWhenAMemoryLeakOccurs.php | 4 +- ...itWithFailureCodeInCaseOfExceptionTest.php | 2 +- ...WorkerGuaranteedToRetireAfterDeathTest.php | 2 +- .../Acceptance/WorkerRepositoryTest.php | 2 +- ...rkableImplementsFinalizerInterfaceTest.php | 2 +- spec/Recruiter/Job/RepositoryTest.php | 13 +++-- spec/Recruiter/WaitStrategyTest.php | 2 +- spec/Timeless/MongoDateTest.php | 2 +- src/Recruiter/Job.php | 4 +- src/Recruiter/Job/Event.php | 11 ++-- .../RetryPolicy/SelectByException.php | 1 - src/Recruiter/Scheduler.php | 2 +- src/Recruiter/SynchronousExecutionReport.php | 4 +- src/Recruiter/functions.php | 22 +------ 30 files changed, 235 insertions(+), 102 deletions(-) create mode 100644 .github/workflows/phpunit.yml create mode 100644 Makefile create mode 100644 compose.yaml create mode 100644 rector.php rename spec/Recruiter/Acceptance/{BaseAcceptanceTest.php => BaseAcceptanceTestCase.php} (97%) diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml new file mode 100644 index 00000000..9619b698 --- /dev/null +++ b/.github/workflows/phpunit.yml @@ -0,0 +1,41 @@ +name: PHPUnit Tests + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Build the Docker image + run: make build + + - name: Install dependencies + run: make install + + - name: Run test suite (except long ones) + run: make test + + - name: Run test suite (only long ones) + run: make test-long diff --git a/.gitignore b/.gitignore index ff2779ce..62560d1e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .* +!.github composer.phar composer.lock vendor/ diff --git a/Dockerfile b/Dockerfile index 7214a211..1904a3f9 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,18 +1,39 @@ -FROM php:7.2-cli +FROM php:8.4-cli -RUN apt-get update \ - && apt-get install -y mongodb git +# Install system dependencies +RUN apt-get update && apt-get install -y \ + git \ + unzip \ + libssl-dev \ + libcurl4-openssl-dev \ + pkg-config \ + && rm -rf /var/lib/apt/lists/* +# Install MongoDB extension RUN pecl install mongodb \ - && docker-php-ext-install bcmath pdo_mysql mbstring opcache pcntl \ - && docker-php-ext-enable mongodb + && docker-php-ext-enable mongodb \ + && docker-php-ext-install -j$(nproc) \ + bcmath \ + pdo_mysql \ + opcache \ + pcntl -RUN curl -sS https://getcomposer.org/installer | php && mv composer.phar /usr/local/bin/composer && composer global require hirak/prestissimo --no-plugins --no-scripts +# Copy Composer from official image +COPY --from=composer:latest /usr/bin/composer /usr/bin/composer +# Set working directory WORKDIR /app -COPY . /app +# Copy composer files +COPY composer.json composer.lock* ./ -RUN composer install +# Install dependencies including dev dependencies for testing +RUN composer install --optimize-autoloader -ENTRYPOINT /etc/init.d/mongodb start && vendor/bin/phpunit +# Copy application code +COPY . . + +# Set environment variable for Composer +ENV COMPOSER_ALLOW_SUPERUSER=1 + +CMD ["tail", "-f", "/dev/null"] diff --git a/Makefile b/Makefile new file mode 100644 index 00000000..ca10c565 --- /dev/null +++ b/Makefile @@ -0,0 +1,47 @@ +.PHONY: build up down test phpstan rector fix-cs install update shell logs clean + +# Build the Docker image +build: + docker compose build + +# Start the services +up: + docker compose up -d + +# Stop the services +down: + docker compose down + +# Install dependencies +install: + docker compose run --rm php composer install + +# Update dependencies +update: + docker compose run --rm php composer update + +# Run all tests except the long ones +test: up + docker compose exec php vendor/bin/phpunit + +phpstan: up + docker compose exec php vendor/bin/phpstan + +rector: up + docker compose exec php vendor/bin/rector + +fix-cs: up + docker compose exec php vendor/bin/php-cs-fixer fix -v + +# Open a shell in the PHP container +shell: + docker compose exec php bash + +# View logs +logs: + docker compose logs -f php + +# Clean up containers and volumes +clean: + docker compose down -v + docker compose rm -f diff --git a/compose.yaml b/compose.yaml new file mode 100644 index 00000000..463cd86c --- /dev/null +++ b/compose.yaml @@ -0,0 +1,21 @@ +services: + php: + build: . + working_dir: /app + volumes: + - .:/app + environment: + - COMPOSER_ALLOW_SUPERUSER=1 + - MONGODB_URI=mongodb://mongodb:27017/concurrency + depends_on: + - mongodb + + mongodb: + image: mongo:8 + container_name: recruiter_mongodb + restart: unless-stopped + volumes: + - mongodb_data:/data/db + +volumes: + mongodb_data: diff --git a/composer.json b/composer.json index 95fb02e3..85701f2d 100644 --- a/composer.json +++ b/composer.json @@ -1,6 +1,7 @@ { "name": "recruiterphp/recruiter", "description": "Job Queue Manager: high performance, high volume, persistent, fault tolerant. 100% PHP/MongoDB, 100% Awesome", + "license": "MIT", "type": "project", "keywords": [ "job", @@ -13,8 +14,6 @@ "manager", "mongodb" ], - "homepage": "https://github.com/recruiterphp/recruiter", - "license": "MIT", "authors": [ { "name": "gabriele.lana", @@ -25,44 +24,53 @@ "homepage": "https://github.com/recruiterphp/recruiter/graphs/contributors" } ], + "homepage": "https://github.com/recruiterphp/recruiter", "require": { - "php": "~7.2", + "php": "^8.4", "ext-mongodb": ">=1.1", - "alcaeus/mongo-php-adapter": "^1.1", - "recruiterphp/geezer": "^5", - "gabrielelana/byte-units": "~0.1", - "monolog/monolog": ">=1", - "recruiterphp/concurrency": "^3.0", - "psr/log": "^1.0", - "symfony/console": "^4.2", - "symfony/event-dispatcher": "^3.4|^4.0", - "ulrichsg/getopt-php": "~2.1", - "mongodb/mongodb": "^1.4", - "mtdowling/cron-expression": "^1.2" - }, - "suggest": { - "symfony/console": "In order to use Recruiter\\Command\\RecruiterJobCommand." + "dragonmantank/cron-expression": "^3.4", + "gabrielelana/byte-units": "^0.5", + "mongodb/mongodb": "^2.1", + "monolog/monolog": "^3.9", + "psr/log": "^3.0", + "recruiterphp/concurrency": "^4.0", + "recruiterphp/geezer": "^6.0", + "symfony/console": "^7.3", + "symfony/event-dispatcher": "^7.3", + "ulrichsg/getopt-php": "^4.0" }, "require-dev": { - "phpunit/phpunit": "^8", + "dms/phpunit-arraysubset-asserts": "^0.5", + "ergebnis/composer-normalize": "^2.47", + "giorgiosironi/eris": "^1.0", "phpstan/phpstan": "*", - "giorgiosironi/eris": "dev-master", - "dms/phpunit-arraysubset-asserts": "^0.1.0" + "phpunit/phpunit": "^10.0", + "rector/rector": "^2.1", + "ext-pcntl": "*" + }, + "suggest": { + "symfony/console": "In order to use Recruiter\\Command\\RecruiterJobCommand." }, "minimum-stability": "dev", "prefer-stable": true, - "bin": [ - "bin/recruiter" - ], "autoload": { "psr-4": { "Recruiter\\": "src/Recruiter", - "Timeless\\": "src/Timeless", - "Sink\\": "src/Sink" + "Sink\\": "src/Sink", + "Timeless\\": "src/Timeless" }, "files": [ "src/Timeless/functions.php", "src/Recruiter/functions.php" ] + }, + "bin": [ + "bin/recruiter" + ], + "config": { + "allow-plugins": { + "ergebnis/composer-normalize": true + }, + "sort-packages": true } } diff --git a/examples/bootstrap.php b/examples/bootstrap.php index 5e278a1e..c61635fd 100644 --- a/examples/bootstrap.php +++ b/examples/bootstrap.php @@ -1,6 +1,6 @@ getEventDispatcher()->addListener('job.failure.last', function($event) { +$recruiter->getEventDispatcher()->addListener('job.failure.last', function($event): void { error_log("Job definitively failed: " . var_export($event->export(), true)); }); diff --git a/phpstan.neon b/phpstan.neon index 4e803c3c..010afda7 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,8 +2,7 @@ parameters: level: max paths: - src - excludes_analyse: - - %currentWorkingDirectory%/vendor/ + - tests autoload_directories: - %currentWorkingDirectory%/spec ignoreErrors: diff --git a/rector.php b/rector.php new file mode 100644 index 00000000..b2252895 --- /dev/null +++ b/rector.php @@ -0,0 +1,17 @@ +withPaths([ + __DIR__ . '/examples', + __DIR__ . '/spec', + __DIR__ . '/src', + ]) + // uncomment to reach your current PHP version + // ->withPhpSets() + ->withTypeCoverageLevel(0) + ->withDeadCodeLevel(0) + ->withCodeQualityLevel(0); diff --git a/spec/Recruiter/Acceptance/AssignmentTest.php b/spec/Recruiter/Acceptance/AssignmentTest.php index c7f08a3a..e173d909 100644 --- a/spec/Recruiter/Acceptance/AssignmentTest.php +++ b/spec/Recruiter/Acceptance/AssignmentTest.php @@ -4,7 +4,7 @@ use Recruiter\Infrastructure\Memory\MemoryLimit; use Recruiter\Workable\LazyBones; -class AssignmentTest extends BaseAcceptanceTest +class AssignmentTest extends BaseAcceptanceTestCase { public function testAJobCanBeAssignedAndExecuted() { diff --git a/spec/Recruiter/Acceptance/BaseAcceptanceTest.php b/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php similarity index 97% rename from spec/Recruiter/Acceptance/BaseAcceptanceTest.php rename to spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php index 347be835..3be74e46 100644 --- a/spec/Recruiter/Acceptance/BaseAcceptanceTest.php +++ b/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php @@ -11,7 +11,7 @@ use Recruiter\Workable\ShellCommand; use Timeless as T; -abstract class BaseAcceptanceTest extends TestCase +abstract class BaseAcceptanceTestCase extends TestCase { protected $recruiterDb; @@ -24,7 +24,8 @@ abstract class BaseAcceptanceTest extends TestCase public function setUp(): void { $factory = new Factory(); - $this->recruiterDb = $factory->getMongoDb(MongoURI::from('mongodb://localhost:27017/recruiter'), []); + $uri = getenv('MONGODB_URI') ?: 'mongodb://localhost:27017'; + $this->recruiterDb = $factory->getMongoDb(MongoURI::from($uri), []); $this->cleanDb(); $this->files = ['/tmp/recruiter.log', '/tmp/worker.log']; $this->cleanLogs(); @@ -178,7 +179,7 @@ protected function enqueueJob($duration = 10, $tag = 'generic') $this->jobs++; } - protected function enqueueJobWithRetryPolicy($duration = 10, RetryPolicy $retryPolicy) + protected function enqueueJobWithRetryPolicy(int $duration, RetryPolicy $retryPolicy): void { $workable = ShellCommand::fromCommandLine("sleep " . ($duration / 1000)); $workable diff --git a/spec/Recruiter/Acceptance/EnduranceTest.php b/spec/Recruiter/Acceptance/EnduranceTest.php index 3ef3a43c..b71e3e29 100644 --- a/spec/Recruiter/Acceptance/EnduranceTest.php +++ b/spec/Recruiter/Acceptance/EnduranceTest.php @@ -12,7 +12,7 @@ /** * @group long */ -class EnduranceTest extends BaseAcceptanceTest +class EnduranceTest extends BaseAcceptanceTestCase { use Eris\TestTrait; @@ -73,7 +73,7 @@ function ($milliseconds) { ->hook(Listener\log('/tmp/recruiter-test-iterations.log')) ->hook(Listener\collectFrequencies()) ->disableShrinking() - ->then(function ($tuple) { + ->then(function ($tuple): void { list ($workers, $actions) = $tuple; $this->clean(); $this->start($workers); diff --git a/spec/Recruiter/Acceptance/FaultToleranceTest.php b/spec/Recruiter/Acceptance/FaultToleranceTest.php index d71afafe..9e2daaa8 100644 --- a/spec/Recruiter/Acceptance/FaultToleranceTest.php +++ b/spec/Recruiter/Acceptance/FaultToleranceTest.php @@ -8,7 +8,7 @@ use Recruiter\RetryPolicy\RetryManyTimes; use Timeless as T; -class FaultToleranceTest extends BaseAcceptanceTest +class FaultToleranceTest extends BaseAcceptanceTestCase { public function testRecruiterCrashAfterLockingJobsBeforeAssignmentAndIsRestarted() { diff --git a/spec/Recruiter/Acceptance/HooksTest.php b/spec/Recruiter/Acceptance/HooksTest.php index 05266dcf..e66b9899 100644 --- a/spec/Recruiter/Acceptance/HooksTest.php +++ b/spec/Recruiter/Acceptance/HooksTest.php @@ -7,7 +7,7 @@ use Recruiter\RetryPolicy\RetryManyTimes; use Symfony\Component\EventDispatcher\Event; -class HooksTest extends BaseAcceptanceTest +class HooksTest extends BaseAcceptanceTestCase { public function setUp(): void { @@ -22,7 +22,7 @@ public function testAfterFailureWithoutRetryEventIsFired() ->getEventDispatcher() ->addListener( 'job.failure.last', - function (Event $event) { + function (Event $event): void { $this->events[] = $event; } ); @@ -48,7 +48,7 @@ public function testAfterLastFailureEventIsFired() ->getEventDispatcher() ->addListener( 'job.failure.last', - function (Event $event) { + function (Event $event): void { $this->events[] = $event; } ); @@ -59,7 +59,7 @@ function (Event $event) { ->inBackground() ->execute(); - $runAJob = function ($howManyTimes, $worker) { + $runAJob = function ($howManyTimes, $worker): void { for ($i = 0; $i < $howManyTimes;) { list($_, $assigned) = $this->recruiter->assignJobsToWorkers(); $worker->work(); @@ -84,7 +84,7 @@ public function testJobStartedIsFired() ->getEventDispatcher() ->addListener( 'job.started', - function (Event $event) { + function (Event $event): void { $this->events[] = $event; } ); @@ -109,7 +109,7 @@ public function testJobEndedIsFired() ->getEventDispatcher() ->addListener( 'job.ended', - function (Event $event) { + function (Event $event): void { $this->events[] = $event; } ); diff --git a/spec/Recruiter/Acceptance/RepeatableJobsAreScheduledTest.php b/spec/Recruiter/Acceptance/RepeatableJobsAreScheduledTest.php index 7d1a29b4..abce5a0c 100644 --- a/spec/Recruiter/Acceptance/RepeatableJobsAreScheduledTest.php +++ b/spec/Recruiter/Acceptance/RepeatableJobsAreScheduledTest.php @@ -12,7 +12,7 @@ use Timeless as T; use Timeless\Moment; -class RepeatableJobsAreScheduledTest extends BaseAcceptanceTest +class RepeatableJobsAreScheduledTest extends BaseAcceptanceTestCase { use ArraySubsetAsserts; @@ -144,7 +144,7 @@ private function IHaveAScheduleWithALongStory(string $urn, $attempts) $this->recruiterScheduleJobsNTimes($attempts); } - private function scheduleAJob(string $urn, SchedulePolicy $schedulePolicy = null, bool $unique = false) + private function scheduleAJob(string $urn, ?SchedulePolicy $schedulePolicy = null, bool $unique = false) { if (is_null($schedulePolicy)) { $schedulePolicy = new FixedSchedulePolicy(strtotime('2023-02-18T17:00:00')); @@ -160,7 +160,7 @@ private function scheduleAJob(string $urn, SchedulePolicy $schedulePolicy = null return $scheduler->create(); } - private function recruiterScheduleJobsNTimes(int $nth = 1) + private function recruiterScheduleJobsNTimes(int $nth = 1): void { $i = 0; while ($i++ < $nth) { diff --git a/spec/Recruiter/Acceptance/SyncronousExecutionTest.php b/spec/Recruiter/Acceptance/SyncronousExecutionTest.php index e1898b5b..6161ff16 100644 --- a/spec/Recruiter/Acceptance/SyncronousExecutionTest.php +++ b/spec/Recruiter/Acceptance/SyncronousExecutionTest.php @@ -5,7 +5,7 @@ use Recruiter\Workable\FactoryMethodCommand; use Timeless as T; -class SyncronousExecutionTest extends BaseAcceptanceTest +class SyncronousExecutionTest extends BaseAcceptanceTestCase { public function testJobsAreExecutedInOrderOfScheduling() { diff --git a/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWhenAMemoryLeakOccurs.php b/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWhenAMemoryLeakOccurs.php index f6121ab2..5ceab2d7 100644 --- a/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWhenAMemoryLeakOccurs.php +++ b/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWhenAMemoryLeakOccurs.php @@ -6,7 +6,7 @@ use Recruiter\Workable\ConsumingMemoryCommand; use Timeless as T; -class WorkerGuaranteedToExitWhenAMemoryLeakOccurs extends BaseAcceptanceTest +class WorkerGuaranteedToExitWhenAMemoryLeakOccurs extends BaseAcceptanceTestCase { /** * @group acceptance @@ -30,7 +30,7 @@ public function testWorkerKillItselfAfterAMemoryLeakButNotAfterABigMemoryConsump ]); $this->waitForNumberOfWorkersToBe($numberOfWorkersBefore + 1, 5); - Timeout::inSeconds(5, function () { + Timeout::inSeconds(5, function (): void { }) ->until(function () { $at = T\now(); diff --git a/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest.php b/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest.php index e066a577..fcb0feb9 100644 --- a/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest.php +++ b/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest.php @@ -5,7 +5,7 @@ use Recruiter\Workable\FactoryMethodCommand; use Recruiter\Workable\ThrowsFatalError; -class WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest extends BaseAcceptanceTest +class WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest extends BaseAcceptanceTestCase { /** * @group acceptance diff --git a/spec/Recruiter/Acceptance/WorkerGuaranteedToRetireAfterDeathTest.php b/spec/Recruiter/Acceptance/WorkerGuaranteedToRetireAfterDeathTest.php index 95ca3c9f..68d18a71 100644 --- a/spec/Recruiter/Acceptance/WorkerGuaranteedToRetireAfterDeathTest.php +++ b/spec/Recruiter/Acceptance/WorkerGuaranteedToRetireAfterDeathTest.php @@ -2,7 +2,7 @@ namespace Recruiter\Acceptance; -class WorkerGuaranteedToRetireAfterDeathTest extends BaseAcceptanceTest +class WorkerGuaranteedToRetireAfterDeathTest extends BaseAcceptanceTestCase { /** * @group acceptance diff --git a/spec/Recruiter/Acceptance/WorkerRepositoryTest.php b/spec/Recruiter/Acceptance/WorkerRepositoryTest.php index 3364f238..f1918982 100644 --- a/spec/Recruiter/Acceptance/WorkerRepositoryTest.php +++ b/spec/Recruiter/Acceptance/WorkerRepositoryTest.php @@ -5,7 +5,7 @@ use Recruiter\Worker\Repository; use Recruiter\Recruiter; -class WorkerRepositoryTest extends BaseAcceptanceTest +class WorkerRepositoryTest extends BaseAcceptanceTestCase { public function setUp(): void { diff --git a/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php b/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php index 3a60e5fc..aa786638 100644 --- a/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php +++ b/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php @@ -31,7 +31,7 @@ public function testFinalizableFailureMethodsAreCalledWhenJobFails() [$this->equalTo('afterLastFailure'), $exception], [$this->equalTo('finalize'), $exception] ); - $workable = new FinalizableWorkable(function () use ($exception) { + $workable = new FinalizableWorkable(function () use ($exception): void { throw $exception; }, $listener); diff --git a/spec/Recruiter/Job/RepositoryTest.php b/spec/Recruiter/Job/RepositoryTest.php index 39153943..e4069a70 100644 --- a/spec/Recruiter/Job/RepositoryTest.php +++ b/spec/Recruiter/Job/RepositoryTest.php @@ -3,6 +3,7 @@ use DateTime; use MongoDB\BSON\ObjectId; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Recruiter\Factory; use Recruiter\Infrastructure\Persistence\Mongodb\URI as MongoURI; @@ -455,10 +456,10 @@ private function jobExecutionMock($executionParameters) } private function jobMockWithAttemptsAndCustomParameters( - Moment $createdAt = null, - Moment $endedAt = null, - array $workableParameters = null - ) { + ?Moment $createdAt = null, + ?Moment $endedAt = null, + ?array $workableParameters = null + ): Job&MockObject { $parameters = [ '_id' => new ObjectId(), 'created_at' => T\MongoDate::from($createdAt), @@ -482,12 +483,12 @@ private function jobMockWithAttemptsAndCustomParameters( $parameters['workable']['parameters'] = $workableParameters; } $job = $this - ->getMockBuilder('Recruiter\Job') + ->getMockBuilder(Job::class) ->disableOriginalConstructor() ->getMock(); $job->expects($this->once()) ->method('export') - ->will($this->returnValue($parameters)); + ->willReturn($parameters); return $job; } } diff --git a/spec/Recruiter/WaitStrategyTest.php b/spec/Recruiter/WaitStrategyTest.php index b721e1dd..e6a305d4 100644 --- a/spec/Recruiter/WaitStrategyTest.php +++ b/spec/Recruiter/WaitStrategyTest.php @@ -10,7 +10,7 @@ class WaitStrategyTest extends TestCase public function setUp(): void { $this->waited = 0; - $this->howToWait = function($microseconds) { + $this->howToWait = function($microseconds): void { $this->waited = T\milliseconds($microseconds/1000); }; $this->timeToWaitAtLeast = T\milliseconds(250); diff --git a/spec/Timeless/MongoDateTest.php b/spec/Timeless/MongoDateTest.php index 093556e1..baecddf2 100644 --- a/spec/Timeless/MongoDateTest.php +++ b/spec/Timeless/MongoDateTest.php @@ -15,7 +15,7 @@ public function testConvertsBackAndForthMongoDatesWithoutLosingMillisecondPrecis ->forAll( Generator\choose(0, 1500 * 1000 * 1000) ) - ->then(function ($milliseconds) { + ->then(function ($milliseconds): void { $moment = new Moment($milliseconds); $this->assertEquals( $moment, diff --git a/src/Recruiter/Job.php b/src/Recruiter/Job.php index ba95b6a7..7c278739 100644 --- a/src/Recruiter/Job.php +++ b/src/Recruiter/Job.php @@ -241,10 +241,10 @@ private function afterFailure($exception, $eventDispatcher) return $archived; } - private function emit($eventType, $eventDispatcher) + private function emit($eventType, EventDispatcherInterface $eventDispatcher): void { $event = new Event($this->export()); - $eventDispatcher->dispatch($eventType, $event); + $eventDispatcher->dispatch($event, $eventType); if ($this->workable instanceof EventListener) { $this->workable->onEvent($eventType, $event); } diff --git a/src/Recruiter/Job/Event.php b/src/Recruiter/Job/Event.php index 71738ebe..094a0f3c 100644 --- a/src/Recruiter/Job/Event.php +++ b/src/Recruiter/Job/Event.php @@ -1,23 +1,20 @@ jobExport = $jobExport; } - public function export() + public function export(): array { return $this->jobExport; } - public function hasTag($wantedTag) + public function hasTag(string $wantedTag): bool { $tags = array_key_exists('tags', $this->jobExport) ? $this->jobExport['tags'] : []; return in_array($wantedTag, $tags); diff --git a/src/Recruiter/RetryPolicy/SelectByException.php b/src/Recruiter/RetryPolicy/SelectByException.php index c38835cb..0a2841db 100644 --- a/src/Recruiter/RetryPolicy/SelectByException.php +++ b/src/Recruiter/RetryPolicy/SelectByException.php @@ -8,7 +8,6 @@ use Recruiter\JobAfterFailure; use Recruiter\RetryPolicy; use Throwable; -use function Recruiter\array_all; /** * Select retry policies based on the raised exception diff --git a/src/Recruiter/Scheduler.php b/src/Recruiter/Scheduler.php index 01b272db..3c5a73e1 100644 --- a/src/Recruiter/Scheduler.php +++ b/src/Recruiter/Scheduler.php @@ -151,7 +151,7 @@ public function schedule(JobsRepository $jobs) $this->status['last_scheduling']['scheduled_at'] = T\MongoDate::from($nextScheduling); $this->status['last_scheduling']['job_id'] = null; - $this->status['attempts'] = $this->status['attempts'] + 1; + $this->status['attempts'] += 1; $this->schedulers->save($this); $jobToSchedule = (new JobToSchedule(Job::around($this->repeatable, $jobs))) diff --git a/src/Recruiter/SynchronousExecutionReport.php b/src/Recruiter/SynchronousExecutionReport.php index ebd1ac46..68132ed6 100644 --- a/src/Recruiter/SynchronousExecutionReport.php +++ b/src/Recruiter/SynchronousExecutionReport.php @@ -30,9 +30,9 @@ public static function fromArray(array $data): SynchronousExecutionReport return new self($data); } - public function isThereAFailure() + public function isThereAFailure(): bool { - return array_some($this->data, function ($jobExecution, $jobId) { + return array_any($this->data, function ($jobExecution, $jobId) { return $jobExecution->isFailed(); }); } diff --git a/src/Recruiter/functions.php b/src/Recruiter/functions.php index 115728df..f09bb232 100644 --- a/src/Recruiter/functions.php +++ b/src/Recruiter/functions.php @@ -1,27 +1,7 @@ $value) { - if (!call_user_func($predicate, $value, $key, $array)) { - return false; - } - } - return true; -} - -function array_some($array, callable $predicate) -{ - foreach ($array as $key => $value) { - if (call_user_func($predicate, $value, $key, $array)) { - return true; - } - } - return false; -} - -function array_group_by($array, callable $f = null) +function array_group_by($array, ?callable $f = null): array { $f = $f ?: function ($value) { return $value; From 1379ee5ad362f546a3dfd839b9d90b9afb4d6958 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 01:03:23 +0200 Subject: [PATCH 02/59] Fix types in BaseAcceptanceTestCase --- .../Acceptance/BaseAcceptanceTestCase.php | 61 ++++++++++--------- 1 file changed, 33 insertions(+), 28 deletions(-) diff --git a/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php b/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php index 3be74e46..5f90e0cb 100644 --- a/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php +++ b/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php @@ -2,6 +2,8 @@ namespace Recruiter\Acceptance; use DateTimeImmutable; +use MongoDB\Collection; +use MongoDB\Database; use Recruiter\Concurrency\Timeout; use PHPUnit\Framework\TestCase; use Recruiter\Factory; @@ -13,15 +15,18 @@ abstract class BaseAcceptanceTestCase extends TestCase { - protected $recruiterDb; - - protected $recruiter; - protected $scheduled; - protected $archived; - protected $roster; - protected $schedulers; - - public function setUp(): void + protected Database $recruiterDb; + protected Recruiter $recruiter; + protected Collection $scheduled; + protected Collection $archived; + protected Collection $roster; + protected Collection $schedulers; + private ?array $processRecruiter; + private ?array $processCleaner; + private array $processWorkers; + private array $lastStatus; + + protected function setUp(): void { $factory = new Factory(); $uri = getenv('MONGODB_URI') ?: 'mongodb://localhost:27017'; @@ -40,17 +45,17 @@ public function setUp(): void $this->processWorkers = []; } - public function tearDown(): void + protected function tearDown(): void { $this->terminateProcesses(SIGKILL); } - protected function cleanDb() + protected function cleanDb(): void { $this->recruiterDb->drop(); } - protected function clean() + protected function clean(): void { $this->terminateProcesses(SIGKILL); $this->cleanLogs(); @@ -58,7 +63,7 @@ protected function clean() $this->jobs = 0; } - public function cleanLogs() + public function cleanLogs(): void { foreach ($this->files as $file) { if (file_exists($file)) { @@ -67,12 +72,12 @@ public function cleanLogs() } } - protected function numberOfWorkers() + protected function numberOfWorkers(): int { - return $this->roster->count(); + return $this->roster->countDocuments(); } - protected function waitForNumberOfWorkersToBe($expectedNumber, $howManySeconds = 1) + protected function waitForNumberOfWorkersToBe($expectedNumber, $howManySeconds = 1): void { Timeout::inSeconds($howManySeconds, "workers to be $expectedNumber") ->until(function () use ($expectedNumber) { @@ -81,7 +86,7 @@ protected function waitForNumberOfWorkersToBe($expectedNumber, $howManySeconds = }); } - protected function startRecruiter() + protected function startRecruiter(): array { $descriptors = [ 0 => ['pipe', 'r'], @@ -103,7 +108,7 @@ protected function startRecruiter() return $this->processRecruiter; } - protected function startCleaner() + protected function startCleaner(): array { $descriptors = [ 0 => ['pipe', 'r'], @@ -150,7 +155,7 @@ protected function startWorker(array $additionalOptions = []) return end($this->processWorkers); } - protected function stopProcessWithSignal(array $processAndPipes, $signal) + protected function stopProcessWithSignal(array $processAndPipes, $signal): void { list($process, $pipes, $name) = $processAndPipes; proc_terminate($process, $signal); @@ -168,7 +173,7 @@ protected function stopProcessWithSignal(array $processAndPipes, $signal) /** * @param integer $duration milliseconds */ - protected function enqueueJob($duration = 10, $tag = 'generic') + protected function enqueueJob($duration = 10, $tag = 'generic'): void { $workable = ShellCommand::fromCommandLine("sleep " . ($duration / 1000)); $workable @@ -190,7 +195,7 @@ protected function enqueueJobWithRetryPolicy(int $duration, RetryPolicy $retryPo $this->jobs++; } - protected function start($workers) + protected function start(int $workers): void { $this->startRecruiter(); $this->startCleaner(); @@ -199,7 +204,7 @@ protected function start($workers) } } - private function terminateProcesses($signal) + private function terminateProcesses($signal): void { if ($this->processRecruiter) { $this->stopProcessWithSignal($this->processRecruiter, $signal); @@ -215,31 +220,31 @@ private function terminateProcesses($signal) $this->processWorkers = []; } - protected function restartWorkerGracefully($workerIndex) + protected function restartWorkerGracefully($workerIndex): void { $this->stopProcessWithSignal($this->processWorkers[$workerIndex], SIGTERM); $this->processWorkers[$workerIndex] = $this->startWorker(); } - protected function restartWorkerByKilling($workerIndex) + protected function restartWorkerByKilling($workerIndex): void { $this->stopProcessWithSignal($this->processWorkers[$workerIndex], SIGKILL); $this->processWorkers[$workerIndex] = $this->startWorker(); } - protected function restartRecruiterGracefully() + protected function restartRecruiterGracefully(): void { $this->stopProcessWithSignal($this->processRecruiter, SIGTERM); $this->startRecruiter(); } - protected function restartRecruiterByKilling() + protected function restartRecruiterByKilling(): void { $this->stopProcessWithSignal($this->processRecruiter, SIGKILL); $this->startRecruiter(); } - protected function files() + protected function files(): string { $logs = ''; if (getenv('TEST_DUMP')) { @@ -253,7 +258,7 @@ protected function files() return $logs; } - private function optionsToString(array $options = []) + private function optionsToString(array $options = []): string { $optionsString = ''; From ae8a99a39817f41069d79e439e07eab146193585 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 01:24:10 +0200 Subject: [PATCH 03/59] Get MONGODB_URI from environment --- compose.yaml | 2 +- .../Acceptance/BaseAcceptanceTestCase.php | 5 ++-- spec/Recruiter/FactoryTest.php | 27 +++++-------------- spec/Recruiter/Job/RepositoryTest.php | 15 ++++++----- 4 files changed, 19 insertions(+), 30 deletions(-) diff --git a/compose.yaml b/compose.yaml index 463cd86c..61c32cee 100644 --- a/compose.yaml +++ b/compose.yaml @@ -6,7 +6,7 @@ services: - .:/app environment: - COMPOSER_ALLOW_SUPERUSER=1 - - MONGODB_URI=mongodb://mongodb:27017/concurrency + - MONGODB_URI=mongodb://mongodb:27017/recruiter depends_on: - mongodb diff --git a/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php b/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php index 5f90e0cb..50785542 100644 --- a/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php +++ b/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php @@ -7,6 +7,7 @@ use Recruiter\Concurrency\Timeout; use PHPUnit\Framework\TestCase; use Recruiter\Factory; +use Recruiter\Infrastructure\Persistence\Mongodb\URI; use Recruiter\Infrastructure\Persistence\Mongodb\URI as MongoURI; use Recruiter\Recruiter; use Recruiter\RetryPolicy; @@ -29,7 +30,7 @@ abstract class BaseAcceptanceTestCase extends TestCase protected function setUp(): void { $factory = new Factory(); - $uri = getenv('MONGODB_URI') ?: 'mongodb://localhost:27017'; + $uri = getenv('MONGODB_URI') ?: URI::DEFAULT_URI; $this->recruiterDb = $factory->getMongoDb(MongoURI::from($uri), []); $this->cleanDb(); $this->files = ['/tmp/recruiter.log', '/tmp/worker.log']; @@ -204,7 +205,7 @@ protected function start(int $workers): void } } - private function terminateProcesses($signal): void + private function terminateProcesses(int $signal): void { if ($this->processRecruiter) { $this->stopProcessWithSignal($this->processRecruiter, $signal); diff --git a/spec/Recruiter/FactoryTest.php b/spec/Recruiter/FactoryTest.php index 293f1623..ba94ff80 100644 --- a/spec/Recruiter/FactoryTest.php +++ b/spec/Recruiter/FactoryTest.php @@ -2,32 +2,19 @@ namespace Recruiter; -use MongoDB; +use MongoDB\Database; use PHPUnit\Framework\TestCase; use Recruiter\Infrastructure\Persistence\Mongodb\URI as MongoURI; class FactoryTest extends TestCase { - /** - * @var Factory - */ - private $factory; - - /** - * @var string - */ - private $dbHost; - - /** - * @var string - */ - private $dbName; + private Factory $factory; + private MongoURI $mongoURI; protected function setUp(): void { $this->factory = new Factory(); - $this->dbHost = 'localhost:27017'; - $this->dbName = 'recruiter'; + $this->mongoURI = MongoURI::from(getenv('MONGODB_URI') ?: MongoURI::DEFAULT_URI); } public function testShouldCreateAMongoDatabaseConnection() @@ -47,7 +34,7 @@ public function testWriteConcernIsMajorityByDefault() public function testShouldOverwriteTheWriteConcernPassedInTheOptions() { $mongoDb = $this->factory->getMongoDb( - MongoURI::from('mongodb://localhost:27017/recruiter'), + $this->mongoURI, [ 'connectTimeoutMS' => 1000, 'w' => '0', @@ -57,10 +44,10 @@ public function testShouldOverwriteTheWriteConcernPassedInTheOptions() $this->assertEquals('majority', $mongoDb->getWriteConcern()->getW()); } - private function creationOfDefaultMongoDb() + private function creationOfDefaultMongoDb(): Database { return $this->factory->getMongoDb( - MongoURI::from(sprintf('mongodb://%s/%s', $this->dbHost, $this->dbName)), + $this->mongoURI, [ 'connectTimeoutMS' => 1000, 'w' => '0', diff --git a/spec/Recruiter/Job/RepositoryTest.php b/spec/Recruiter/Job/RepositoryTest.php index e4069a70..0254b20f 100644 --- a/spec/Recruiter/Job/RepositoryTest.php +++ b/spec/Recruiter/Job/RepositoryTest.php @@ -1,31 +1,32 @@ recruiterDb = $factory->getMongoDb(MongoURI::from('mongodb://localhost:27017/recruiter'), []); + $this->recruiterDb = $factory->getMongoDb(MongoURI::from(getenv('MONGODB_URI') ?: MongoURI::DEFAULT_URI), []); $this->recruiterDb->drop(); $this->repository = new Repository($this->recruiterDb); $this->clock = T\clock()->stop(); - $this->eventDispatcher = $this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); } public function tearDown(): void From b9ac8876f0d421a2db1305fc897667cf138bc8a6 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 01:27:31 +0200 Subject: [PATCH 04/59] Try to fix phpstan --- Makefile | 2 +- phpstan.neon | 8 +++----- 2 files changed, 4 insertions(+), 6 deletions(-) diff --git a/Makefile b/Makefile index ca10c565..3df22988 100644 --- a/Makefile +++ b/Makefile @@ -25,7 +25,7 @@ test: up docker compose exec php vendor/bin/phpunit phpstan: up - docker compose exec php vendor/bin/phpstan + docker compose exec php vendor/bin/phpstan --memory-limit=2G rector: up docker compose exec php vendor/bin/rector diff --git a/phpstan.neon b/phpstan.neon index 010afda7..e4015b46 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -2,16 +2,14 @@ parameters: level: max paths: - src - - tests - autoload_directories: - - %currentWorkingDirectory%/spec + - spec ignoreErrors: - '#Instantiated class Recruiter\\Workable\\ThisClassDoesnNotExists not found.#' - '#Constructor of class Recruiter\\Workable\\FailsInConstructor has an unused parameter \$parameters.#' - - '#Static call to instance method stdClass::import()#' + - '#Static call to instance method stdClass::import\(\)#' - '#Call to function is_callable\(\) with .recruiter_stept_back. will always evaluate to false.#' - '#Call to function is_callable\(\) with .recruiter_became…. will always evaluate to false.#' - '#Function recruiter_became_master not found.#' - '#Function recruiter_stept_back not found.#' - - '#Call to an undefined method Traversable::toArray().#' + - '#Call to an undefined method Traversable::toArray\(\).#' inferPrivatePropertyTypeFromConstructor: true From a42cc0673f5fdff1d16d4234faa5fc4a4f87ed76 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 02:01:00 +0200 Subject: [PATCH 05/59] Fix more types --- composer.json | 3 +- spec/Recruiter/Acceptance/AssignmentTest.php | 2 +- spec/Recruiter/Acceptance/EnduranceTest.php | 2 +- src/Sink/BlackHole.php | 36 ++++---- src/Timeless/Clock.php | 8 +- src/Timeless/ClockInterface.php | 8 ++ src/Timeless/Interval.php | 90 +++++++++----------- src/Timeless/Moment.php | 39 ++++----- src/Timeless/StoppedClock.php | 13 ++- src/Timeless/functions.php | 39 ++++----- 10 files changed, 119 insertions(+), 121 deletions(-) create mode 100644 src/Timeless/ClockInterface.php diff --git a/composer.json b/composer.json index 85701f2d..8d1ece1f 100644 --- a/composer.json +++ b/composer.json @@ -37,7 +37,8 @@ "recruiterphp/geezer": "^6.0", "symfony/console": "^7.3", "symfony/event-dispatcher": "^7.3", - "ulrichsg/getopt-php": "^4.0" + "ulrichsg/getopt-php": "^4.0", + "ext-bcmath": "*" }, "require-dev": { "dms/phpunit-arraysubset-asserts": "^0.5", diff --git a/spec/Recruiter/Acceptance/AssignmentTest.php b/spec/Recruiter/Acceptance/AssignmentTest.php index e173d909..74d9edf7 100644 --- a/spec/Recruiter/Acceptance/AssignmentTest.php +++ b/spec/Recruiter/Acceptance/AssignmentTest.php @@ -6,7 +6,7 @@ class AssignmentTest extends BaseAcceptanceTestCase { - public function testAJobCanBeAssignedAndExecuted() + public function testAJobCanBeAssignedAndExecuted(): void { $memoryLimit = new MemoryLimit('64MB'); LazyBones::waitForMs(200, 100) diff --git a/spec/Recruiter/Acceptance/EnduranceTest.php b/spec/Recruiter/Acceptance/EnduranceTest.php index b71e3e29..d27e4495 100644 --- a/spec/Recruiter/Acceptance/EnduranceTest.php +++ b/spec/Recruiter/Acceptance/EnduranceTest.php @@ -16,7 +16,7 @@ class EnduranceTest extends BaseAcceptanceTestCase { use Eris\TestTrait; - public function setUp(): void + protected function setUp(): void { parent::setUp(); $this->jobRepository = new Repository($this->recruiterDb); diff --git a/src/Sink/BlackHole.php b/src/Sink/BlackHole.php index 154fe15a..4fb20e65 100644 --- a/src/Sink/BlackHole.php +++ b/src/Sink/BlackHole.php @@ -15,90 +15,90 @@ public function __destruct() { } - public function __set($name, $value) + public function __set(string $name, mixed $value) { } - public function __get($name) + public function __get(string $name): self { return $this; } - public function __isset($name) + public function __isset(string $name): bool { return false; } - public function __unset($name) + public function __unset(string $name): void { } - public function __call($name, $arguments) + public function __call(string $name, array $arguments) { return $this; } - public function __toString() + public function __toString(): string { return ''; } - public function __invoke() + public function __invoke(): self { return $this; } - public function __clone() + public function __clone(): void { } - public static function __callStatic($name, $args) + public static function __callStatic(string $name, array $args): self { return new self(); } // Iterator Interface - public function current() + public function current(): self { return $this; } - public function key() + public function key(): self { return $this; } - public function next() + public function next(): void { } - public function rewind() + public function rewind(): void { } - public function valid() + public function valid(): bool { return false; } // ArrayAccess Interface - public function offsetExists($offset) + public function offsetExists(mixed $offset): bool { return false; } - public function offsetGet($offset) + public function offsetGet(mixed $offset): self { return $this; } - public function offsetSet($offset, $value) + public function offsetSet(mixed $offset, mixed $value): void { } - public function offsetUnset($offset) + public function offsetUnset(mixed $offset): void { } } diff --git a/src/Timeless/Clock.php b/src/Timeless/Clock.php index eb8cc7c0..1a5984a5 100644 --- a/src/Timeless/Clock.php +++ b/src/Timeless/Clock.php @@ -2,15 +2,15 @@ namespace Timeless; -class Clock +class Clock implements ClockInterface { - public function stop() + public function stop(): ClockInterface { return clock(new StoppedClock($this->now())); } - public function now() + public function now(): Moment { - return new Moment(round(microtime(true) * 1000)); + return new Moment((int) round(microtime(true) * 1000)); } } diff --git a/src/Timeless/ClockInterface.php b/src/Timeless/ClockInterface.php new file mode 100644 index 00000000..7971515e --- /dev/null +++ b/src/Timeless/ClockInterface.php @@ -0,0 +1,8 @@ +ms = intval($ms); } public function us(): int @@ -101,7 +98,7 @@ public function from(Moment $reference): Moment return $reference->after($this); } - public function multiplyBy($multiplier): self + public function multiplyBy(int $multiplier): self { return new self($this->ms * $multiplier); } @@ -111,43 +108,40 @@ public function add(Interval $interval): self return new self($this->ms + $interval->ms); } - public function format($format) - { - if (is_string($format)) { - $availableFormatsTable = [ - 'ms' => ['milliseconds', 'ms', 'ms'], - 's' => ['seconds', 's', 's'], - 'm' => ['minutes', 'm', 'm'], - 'h' => ['hours', 'h', 'h'], - 'd' => ['days', 'd', 'd'], - 'w' => ['weeks', 'w', 'w'], - 'mo' => ['months', 'mo', 'mo'], - 'y' => ['years', 'y', 'y'], - 'milliseconds' => ['milliseconds', ' milliseconds', ' millisecond'], - 'seconds' => ['seconds', ' seconds', ' second'], - 'minutes' => ['minutes', ' minutes', ' minute'], - 'hours' => ['hours', ' hours', ' hour'], - 'days' => ['days', ' days', ' day'], - 'weeks' => ['weeks', ' weeks', ' week'], - 'months' => ['months', ' months', ' month'], - 'years' => ['years', ' years', ' year'], - ]; - $format = trim($format); - if (array_key_exists($format, $availableFormatsTable)) { - $callable = [$this, $availableFormatsTable[$format][0]]; - if (!is_callable($callable)) { - throw new \RuntimeException("function `{$availableFormatsTable[$format][0]}` does not exists"); - } - - $amountOfTime = call_user_func($callable); - $unitOfTime = $amountOfTime === 1 ? - $availableFormatsTable[$format][2] : - $availableFormatsTable[$format][1]; - return sprintf('%d%s', $amountOfTime, $unitOfTime); + public function format(string $format): string + { + $availableFormatsTable = [ + 'ms' => ['milliseconds', 'ms', 'ms'], + 's' => ['seconds', 's', 's'], + 'm' => ['minutes', 'm', 'm'], + 'h' => ['hours', 'h', 'h'], + 'd' => ['days', 'd', 'd'], + 'w' => ['weeks', 'w', 'w'], + 'mo' => ['months', 'mo', 'mo'], + 'y' => ['years', 'y', 'y'], + 'milliseconds' => ['milliseconds', ' milliseconds', ' millisecond'], + 'seconds' => ['seconds', ' seconds', ' second'], + 'minutes' => ['minutes', ' minutes', ' minute'], + 'hours' => ['hours', ' hours', ' hour'], + 'days' => ['days', ' days', ' day'], + 'weeks' => ['weeks', ' weeks', ' week'], + 'months' => ['months', ' months', ' month'], + 'years' => ['years', ' years', ' year'], + ]; + $format = trim($format); + if (array_key_exists($format, $availableFormatsTable)) { + $callable = [$this, $availableFormatsTable[$format][0]]; + if (!is_callable($callable)) { + throw new \RuntimeException("function `{$availableFormatsTable[$format][0]}` does not exists"); } - throw new InvalidIntervalFormat("'{$format}' is not a valid Interval format"); + + $amountOfTime = call_user_func($callable); + $unitOfTime = $amountOfTime === 1 ? + $availableFormatsTable[$format][2] : + $availableFormatsTable[$format][1]; + return sprintf('%d%s', $amountOfTime, $unitOfTime); } - throw new InvalidIntervalFormat('You need to use strings'); + throw new InvalidIntervalFormat("'{$format}' is not a valid Interval format"); } public function toDateInterval() @@ -188,7 +182,7 @@ public static function parse($string) throw new InvalidIntervalFormat('You need to use strings'); } - public static function fromDateInterval(DateInterval $interval) + public static function fromDateInterval(DateInterval $interval): self { $startTime = new DateTimeImmutable(); $endTime = $startTime->add($interval); diff --git a/src/Timeless/Moment.php b/src/Timeless/Moment.php index 94eeeabe..fd6c96cf 100644 --- a/src/Timeless/Moment.php +++ b/src/Timeless/Moment.php @@ -5,76 +5,73 @@ use DateTime; use DateTimeZone; -class Moment +readonly class Moment { - private $ms; - - public static function fromTimestamp($ts) + public static function fromTimestamp(int $ts): self { return new self($ts * 1000); } - public static function fromDateTime(DateTime $dateTime) + public static function fromDateTime(DateTime $dateTime): self { - return static::fromTimestamp($dateTime->getTimestamp()); + return self::fromTimestamp($dateTime->getTimestamp()); } - public function __construct($ms) + public function __construct(private int $ms) { - $this->ms = $ms; } - public function milliseconds() + public function milliseconds(): int { return $this->ms; } - public function ms() + public function ms(): int { return $this->ms; } - public function seconds() + public function seconds(): int { return $this->s(); } - public function s() + public function s(): int { - return round($this->ms / 1000.0); + return (int) round($this->ms / 1000.0); } - public function after(Interval $d) + public function after(Interval $d): self { return new self($this->ms + $d->ms()); } - public function before(Interval $d) + public function before(Interval $d): self { return new self($this->ms - $d->ms()); } - public function isAfter(Moment $m) + public function isAfter(Moment $m): bool { return $this->ms >= $m->ms(); } - public function isBefore(Moment $m) + public function isBefore(Moment $m): bool { return $this->ms <= $m->ms(); } - public function toSecondPrecision() + public function toSecondPrecision(): Moment { return new self($this->s() * 1000); } - public function format() + public function format(): string { - return (new DateTime('@' . $this->s(), new DateTimeZone('UTC')))->format(DateTime::RFC3339); + return new DateTime('@' . $this->s(), new DateTimeZone('UTC'))->format(DateTime::RFC3339); } - public function toDateTime() + public function toDateTime(): DateTime { return new DateTime('@' . $this->s(), new DateTimeZone('UTC')); } diff --git a/src/Timeless/StoppedClock.php b/src/Timeless/StoppedClock.php index d653dbf9..2e10ac94 100644 --- a/src/Timeless/StoppedClock.php +++ b/src/Timeless/StoppedClock.php @@ -2,26 +2,23 @@ namespace Timeless; -class StoppedClock +class StoppedClock implements ClockInterface { - private $now; - - public function __construct(Moment $now) + public function __construct(private Moment $now) { - $this->now = $now; } - public function now() + public function now(): Moment { return $this->now; } - public function driftForwardBySeconds($seconds) + public function driftForwardBySeconds(int $seconds): void { $this->now = $this->now->after(seconds($seconds)); } - public function start() + public function start(): ClockInterface { return clock(new Clock()); } diff --git a/src/Timeless/functions.php b/src/Timeless/functions.php index 26b0a0ab..ac986332 100644 --- a/src/Timeless/functions.php +++ b/src/Timeless/functions.php @@ -2,9 +2,10 @@ namespace Timeless; -function clock($clock = null) +function clock(?ClockInterface $clock = null): ClockInterface { global $__2852bec4cda046fca0e5e21dc007935c; + /** @var ClockInterface $__2852bec4cda046fca0e5e21dc007935c */ $__2852bec4cda046fca0e5e21dc007935c = $clock ?: ( $__2852bec4cda046fca0e5e21dc007935c ?: new Clock() @@ -12,92 +13,92 @@ function clock($clock = null) return $__2852bec4cda046fca0e5e21dc007935c; } -function now() +function now(): Moment { return clock()->now(); } -function millisecond($numberOf) +function millisecond(int $numberOf): Interval { return milliseconds($numberOf); } -function milliseconds($numberOf) +function milliseconds(int $numberOf): Interval { return new Interval($numberOf); } -function second($numberOf) +function second(int $numberOf): Interval { return seconds($numberOf); } -function seconds($numberOf) +function seconds(int $numberOf): Interval { return new Interval($numberOf * Interval::MILLISECONDS_IN_SECONDS); } -function minute($numberOf) +function minute(int $numberOf): Interval { return minutes($numberOf); } -function minutes($numberOf) +function minutes(int $numberOf): Interval { return new Interval($numberOf * Interval::MILLISECONDS_IN_MINUTES); } -function hour($numberOf) +function hour(int $numberOf): Interval { return hours($numberOf); } -function hours($numberOf) +function hours(int $numberOf): Interval { return new Interval($numberOf * Interval::MILLISECONDS_IN_HOURS); } -function day($numberOf) +function day(int $numberOf): Interval { return days($numberOf); } -function days($numberOf) +function days(int $numberOf): Interval { return new Interval($numberOf * Interval::MILLISECONDS_IN_DAYS); } -function week($numberOf) +function week(int $numberOf): Interval { return weeks($numberOf); } -function weeks($numberOf) +function weeks(int $numberOf): Interval { return new Interval($numberOf * Interval::MILLISECONDS_IN_WEEKS); } -function month($numberOf) +function month(int $numberOf): Interval { return months($numberOf); } -function months($numberOf) +function months(int $numberOf): Interval { return new Interval($numberOf * Interval::MILLISECONDS_IN_MONTHS); } -function year($numberOf) +function year(int $numberOf): Interval { return years($numberOf); } -function years($numberOf) +function years(int $numberOf): Interval { return new Interval($numberOf * Interval::MILLISECONDS_IN_YEARS); } -function fromDateInterval(\DateInterval $interval) +function fromDateInterval(\DateInterval $interval): Interval { $seconds = (string) $interval->s; if ($interval->i) { From 1283cca3e177032ad84c1018bb90ecdec5a61629 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 02:01:18 +0200 Subject: [PATCH 06/59] Lower phpstan --- phpstan.neon | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/phpstan.neon b/phpstan.neon index e4015b46..dc25ee1c 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ parameters: - level: max + level: 0 paths: - src - spec From d23ce9509125bc6d4e5685f799a335de6d93f433 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 02:12:32 +0200 Subject: [PATCH 07/59] Add more types --- spec/Recruiter/TaggableWorkableTest.php | 2 +- src/Recruiter/Workable.php | 12 +++--------- src/Recruiter/Workable/FactoryMethodCommand.php | 4 ++-- src/Recruiter/Workable/LazyBones.php | 16 ++++++---------- src/Recruiter/Workable/ShellCommand.php | 2 +- src/Recruiter/WorkableBehaviour.php | 11 ++++------- 6 files changed, 17 insertions(+), 30 deletions(-) diff --git a/spec/Recruiter/TaggableWorkableTest.php b/spec/Recruiter/TaggableWorkableTest.php index 5479a37a..c0f04a81 100644 --- a/spec/Recruiter/TaggableWorkableTest.php +++ b/spec/Recruiter/TaggableWorkableTest.php @@ -110,7 +110,7 @@ public function export() return ['tags' => $this->tags]; } - public static function import($parameters) + public static function import(array $parameters) { return new self($parameters['tags']); } diff --git a/src/Recruiter/Workable.php b/src/Recruiter/Workable.php index aefa2471..4a9733df 100644 --- a/src/Recruiter/Workable.php +++ b/src/Recruiter/Workable.php @@ -6,26 +6,20 @@ interface Workable { /** * Turn this `Recruiter\Workable` instance into a `Recruiter\Job` instance - * - * @param Recruiter $recruiter - * - * @return JobToSchedule */ - public function asJobOf(Recruiter $recruiter); + public function asJobOf(Recruiter $recruiter): JobToSchedule; /** * Export parameters that need to be persisted * * @return array */ - public function export(); + public function export(): array; /** * Import an array of parameters as a Workable instance * * @param array $parameters Previously exported parameters - * - * @return Workable */ - public static function import($parameters); + public static function import(array $parameters): static; } diff --git a/src/Recruiter/Workable/FactoryMethodCommand.php b/src/Recruiter/Workable/FactoryMethodCommand.php index 3951e9e4..6f7c33a0 100644 --- a/src/Recruiter/Workable/FactoryMethodCommand.php +++ b/src/Recruiter/Workable/FactoryMethodCommand.php @@ -104,8 +104,8 @@ public function export() ]; } - public static function import($document) + public static function import(array $parameters) { - return new self($document['steps']); + return new self($parameters['steps']); } } diff --git a/src/Recruiter/Workable/LazyBones.php b/src/Recruiter/Workable/LazyBones.php index d2612f7d..cff5c372 100644 --- a/src/Recruiter/Workable/LazyBones.php +++ b/src/Recruiter/Workable/LazyBones.php @@ -9,31 +9,27 @@ class LazyBones implements Workable { use WorkableBehaviour; - private $usToSleep; - private $usOfDelta; - public static function waitFor($timeInSeconds, $deltaInSeconds = 0) + public static function waitFor(int $timeInSeconds, int $deltaInSeconds = 0): self { return new self($timeInSeconds * 1000000, $deltaInSeconds * 1000000); } - public static function waitForMs($timeInMs, $deltaInMs = 0) + public static function waitForMs($timeInMs, $deltaInMs = 0): self { return new self($timeInMs * 1000, $deltaInMs * 1000); } - public function __construct($usToSleep = 1, $usOfDelta = 0) + public function __construct(private readonly int $usToSleep = 1, private readonly int $usOfDelta = 0) { - $this->usToSleep = $usToSleep; - $this->usOfDelta = $usOfDelta; } - public function execute() + public function execute(): void { usleep($this->usToSleep + (rand(intval(-$this->usOfDelta), $this->usOfDelta))); } - public function export() + public function export(): array { return [ 'us_to_sleep' => $this->usToSleep, @@ -41,7 +37,7 @@ public function export() ]; } - public static function import($parameters) + public static function import(array $parameters): static { return new self( $parameters['us_to_sleep'], diff --git a/src/Recruiter/Workable/ShellCommand.php b/src/Recruiter/Workable/ShellCommand.php index 62aea315..c3531b54 100644 --- a/src/Recruiter/Workable/ShellCommand.php +++ b/src/Recruiter/Workable/ShellCommand.php @@ -37,7 +37,7 @@ public function export() return ['command' => $this->commandLine]; } - public static function import($parameters) + public static function import(array $parameters) { return new self($parameters['command']); } diff --git a/src/Recruiter/WorkableBehaviour.php b/src/Recruiter/WorkableBehaviour.php index 8cfbc88b..2446863d 100644 --- a/src/Recruiter/WorkableBehaviour.php +++ b/src/Recruiter/WorkableBehaviour.php @@ -6,14 +6,11 @@ trait WorkableBehaviour { - protected $parameters; - - public function __construct($parameters = []) + public final function __construct(protected array $parameters = []) { - $this->parameters = $parameters; } - public function asJobOf(Recruiter $recruiter) + public function asJobOf(Recruiter $recruiter): JobToSchedule { return $recruiter->jobOf($this); } @@ -23,12 +20,12 @@ public function execute() throw new Exception('Workable::execute() need to be implemented'); } - public function export() + public function export(): array { return $this->parameters; } - public static function import($parameters) + public static function import(array $parameters): static { return new static($parameters); } From 6c7f127b60d7d6e55a2681722562c9f7886a1b03 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 02:32:43 +0200 Subject: [PATCH 08/59] Fix Timeless signature --- src/Timeless/Clock.php | 5 +++++ src/Timeless/ClockInterface.php | 4 ++++ src/Timeless/StoppedClock.php | 5 +++++ 3 files changed, 14 insertions(+) diff --git a/src/Timeless/Clock.php b/src/Timeless/Clock.php index 1a5984a5..d046af0f 100644 --- a/src/Timeless/Clock.php +++ b/src/Timeless/Clock.php @@ -4,6 +4,11 @@ class Clock implements ClockInterface { + public function start(): ClockInterface + { + return clock($this); + } + public function stop(): ClockInterface { return clock(new StoppedClock($this->now())); diff --git a/src/Timeless/ClockInterface.php b/src/Timeless/ClockInterface.php index 7971515e..c01f2dd3 100644 --- a/src/Timeless/ClockInterface.php +++ b/src/Timeless/ClockInterface.php @@ -5,4 +5,8 @@ interface ClockInterface { public function now(): Moment; + + public function start(): ClockInterface; + + public function stop(): ClockInterface; } \ No newline at end of file diff --git a/src/Timeless/StoppedClock.php b/src/Timeless/StoppedClock.php index 2e10ac94..77392ab4 100644 --- a/src/Timeless/StoppedClock.php +++ b/src/Timeless/StoppedClock.php @@ -22,4 +22,9 @@ public function start(): ClockInterface { return clock(new Clock()); } + + public function stop(): ClockInterface + { + return clock($this); + } } From 5f1f4775cc543de8cc8599c4c55bff511ce05a0f Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 02:33:49 +0200 Subject: [PATCH 09/59] Use array_any instead --- src/Recruiter/RetryPolicy/RetriableExceptionFilter.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Recruiter/RetryPolicy/RetriableExceptionFilter.php b/src/Recruiter/RetryPolicy/RetriableExceptionFilter.php index b0225307..10a5913b 100644 --- a/src/Recruiter/RetryPolicy/RetriableExceptionFilter.php +++ b/src/Recruiter/RetryPolicy/RetriableExceptionFilter.php @@ -78,7 +78,7 @@ private function ensureAreAllExceptions($exceptions) private function isExceptionRetriable($exception) { if (!is_null($exception) && is_object($exception)) { - return \Recruiter\array_some( + return array_any( $this->retriableExceptions, function ($retriableExceptionType) use ($exception) { return ($exception instanceof $retriableExceptionType); From 41dfc8a9b18f0d5cac85e67ddef7bca77fe7f00c Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 02:42:00 +0200 Subject: [PATCH 10/59] Make setUp and tearDown protected --- spec/Recruiter/Acceptance/HooksTest.php | 2 +- spec/Recruiter/Acceptance/WorkerRepositoryTest.php | 2 +- spec/Recruiter/CleanerTest.php | 4 ++-- ...sAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php | 2 +- spec/Recruiter/Job/RepositoryTest.php | 2 +- spec/Recruiter/JobCallCustomMethodOnWorkableTest.php | 2 +- spec/Recruiter/JobSendEventsToWorkableTest.php | 2 +- .../Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php | 2 +- spec/Recruiter/JobTest.php | 2 +- spec/Recruiter/JobToBePassedRetryStatisticsTest.php | 2 +- spec/Recruiter/JobToScheduleTest.php | 4 ++-- spec/Recruiter/PickAvailableWorkersTest.php | 2 +- spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php | 2 +- spec/Recruiter/RetryPolicy/TimeTableTest.php | 2 +- spec/Recruiter/TaggableWorkableTest.php | 2 +- spec/Recruiter/WaitStrategyTest.php | 2 +- spec/Recruiter/WorkerProcessTest.php | 2 +- 17 files changed, 19 insertions(+), 19 deletions(-) diff --git a/spec/Recruiter/Acceptance/HooksTest.php b/spec/Recruiter/Acceptance/HooksTest.php index e66b9899..6003f6a1 100644 --- a/spec/Recruiter/Acceptance/HooksTest.php +++ b/spec/Recruiter/Acceptance/HooksTest.php @@ -9,7 +9,7 @@ class HooksTest extends BaseAcceptanceTestCase { - public function setUp(): void + protected function setUp(): void { $this->memoryLimit = new MemoryLimit('64MB'); parent::setUp(); diff --git a/spec/Recruiter/Acceptance/WorkerRepositoryTest.php b/spec/Recruiter/Acceptance/WorkerRepositoryTest.php index f1918982..c8c1638f 100644 --- a/spec/Recruiter/Acceptance/WorkerRepositoryTest.php +++ b/spec/Recruiter/Acceptance/WorkerRepositoryTest.php @@ -7,7 +7,7 @@ class WorkerRepositoryTest extends BaseAcceptanceTestCase { - public function setUp(): void + protected function setUp(): void { parent::setUp(); $this->repository = new Repository( diff --git a/spec/Recruiter/CleanerTest.php b/spec/Recruiter/CleanerTest.php index a1a70f64..59123781 100644 --- a/spec/Recruiter/CleanerTest.php +++ b/spec/Recruiter/CleanerTest.php @@ -8,7 +8,7 @@ class CleanerTest extends TestCase { - public function setUp(): void + protected function setUp(): void { $this->clock = T\clock()->stop(); $this->now = $this->clock->now(); @@ -23,7 +23,7 @@ public function setUp(): void $this->interval = Interval::parse('10s'); } - public function tearDown(): void + protected function tearDown(): void { T\clock()->start(); } diff --git a/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php b/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php index aa786638..84f8960e 100644 --- a/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php +++ b/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php @@ -7,7 +7,7 @@ class FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest extends TestCase { - public function setUp(): void + protected function setUp(): void { $this->repository = $this ->getMockBuilder('Recruiter\Job\Repository') diff --git a/spec/Recruiter/Job/RepositoryTest.php b/spec/Recruiter/Job/RepositoryTest.php index 0254b20f..7c0e7e58 100644 --- a/spec/Recruiter/Job/RepositoryTest.php +++ b/spec/Recruiter/Job/RepositoryTest.php @@ -29,7 +29,7 @@ protected function setUp(): void $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); } - public function tearDown(): void + protected function tearDown(): void { T\clock()->start(); } diff --git a/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php b/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php index 1c3cb65e..9217b2dc 100644 --- a/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php +++ b/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php @@ -7,7 +7,7 @@ class JobCallCustomMethodOnWorkableTest extends TestCase { - public function setUp(): void + protected function setUp(): void { $this->workable = $this ->getMockBuilder('Recruiter\Workable') diff --git a/spec/Recruiter/JobSendEventsToWorkableTest.php b/spec/Recruiter/JobSendEventsToWorkableTest.php index ab72e71e..240a2ccb 100644 --- a/spec/Recruiter/JobSendEventsToWorkableTest.php +++ b/spec/Recruiter/JobSendEventsToWorkableTest.php @@ -8,7 +8,7 @@ class JobSendEventsToWorkableTest extends TestCase { - public function setUp(): void + protected function setUp(): void { $this->repository = $this ->getMockBuilder('Recruiter\Job\Repository') diff --git a/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php b/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php index 4bafeda3..a8a67abd 100644 --- a/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php +++ b/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php @@ -8,7 +8,7 @@ class JobTakeRetryPolicyFromRetriableWorkableTest extends TestCase { - public function setUp(): void + protected function setUp(): void { $this->repository = $this ->getMockBuilder('Recruiter\Job\Repository') diff --git a/spec/Recruiter/JobTest.php b/spec/Recruiter/JobTest.php index 6df774bf..e9150b62 100644 --- a/spec/Recruiter/JobTest.php +++ b/spec/Recruiter/JobTest.php @@ -8,7 +8,7 @@ class JobTest extends TestCase { - public function setUp(): void + protected function setUp(): void { $this->repository = $this ->getMockBuilder('Recruiter\Job\Repository') diff --git a/spec/Recruiter/JobToBePassedRetryStatisticsTest.php b/spec/Recruiter/JobToBePassedRetryStatisticsTest.php index cce51d76..ce95ef0a 100644 --- a/spec/Recruiter/JobToBePassedRetryStatisticsTest.php +++ b/spec/Recruiter/JobToBePassedRetryStatisticsTest.php @@ -8,7 +8,7 @@ class JobToBePassedRetryStatisticsTest extends TestCase { - public function setUp(): void + protected function setUp(): void { $this->repository = $this ->getMockBuilder('Recruiter\Job\Repository') diff --git a/spec/Recruiter/JobToScheduleTest.php b/spec/Recruiter/JobToScheduleTest.php index 5de7f8f7..1d1d72e5 100644 --- a/spec/Recruiter/JobToScheduleTest.php +++ b/spec/Recruiter/JobToScheduleTest.php @@ -8,7 +8,7 @@ class JobToScheduleTest extends TestCase { - public function setUp(): void + protected function setUp(): void { $this->clock = T\clock()->stop(); $this->job = $this @@ -17,7 +17,7 @@ public function setUp(): void ->getMock(); } - public function tearDown(): void + protected function tearDown(): void { $this->clock->start(); } diff --git a/spec/Recruiter/PickAvailableWorkersTest.php b/spec/Recruiter/PickAvailableWorkersTest.php index 5ca538a4..60ff3db7 100644 --- a/spec/Recruiter/PickAvailableWorkersTest.php +++ b/spec/Recruiter/PickAvailableWorkersTest.php @@ -8,7 +8,7 @@ class PickAvailableWorkersTest extends TestCase { - public function setUp(): void + protected function setUp(): void { $this->repository = $this ->getMockBuilder('MongoDB\Collection') diff --git a/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php b/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php index 3d2354b6..f753f0ce 100644 --- a/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php +++ b/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php @@ -8,7 +8,7 @@ class RetriableExceptionFilterTest extends TestCase { - public function setUp(): void + protected function setUp(): void { $this->filteredRetryPolicy = $this->createMock('Recruiter\RetryPolicy'); } diff --git a/spec/Recruiter/RetryPolicy/TimeTableTest.php b/spec/Recruiter/RetryPolicy/TimeTableTest.php index d6c3fbf1..2e46f143 100644 --- a/spec/Recruiter/RetryPolicy/TimeTableTest.php +++ b/spec/Recruiter/RetryPolicy/TimeTableTest.php @@ -10,7 +10,7 @@ class TimeTableTest extends TestCase { private $scheduler; - public function setUp(): void + protected function setUp(): void { $this->scheduler = new TimeTable([ '5 minutes ago' => '1 minute', diff --git a/spec/Recruiter/TaggableWorkableTest.php b/spec/Recruiter/TaggableWorkableTest.php index c0f04a81..33bb37c1 100644 --- a/spec/Recruiter/TaggableWorkableTest.php +++ b/spec/Recruiter/TaggableWorkableTest.php @@ -8,7 +8,7 @@ class TaggableWorkableTest extends TestCase { - public function setUp(): void + protected function setUp(): void { $this->repository = $this ->getMockBuilder('Recruiter\Job\Repository') diff --git a/spec/Recruiter/WaitStrategyTest.php b/spec/Recruiter/WaitStrategyTest.php index e6a305d4..7cdf3fbd 100644 --- a/spec/Recruiter/WaitStrategyTest.php +++ b/spec/Recruiter/WaitStrategyTest.php @@ -7,7 +7,7 @@ class WaitStrategyTest extends TestCase { - public function setUp(): void + protected function setUp(): void { $this->waited = 0; $this->howToWait = function($microseconds): void { diff --git a/spec/Recruiter/WorkerProcessTest.php b/spec/Recruiter/WorkerProcessTest.php index 8ef203fa..b808410b 100644 --- a/spec/Recruiter/WorkerProcessTest.php +++ b/spec/Recruiter/WorkerProcessTest.php @@ -6,7 +6,7 @@ class WorkerProcessTest extends TestCase { - public function setUp(): void + protected function setUp(): void { $this->pid = 4242; From c422d04225381b1ed76d49b3272ff9b5609e7b1f Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 03:08:32 +0200 Subject: [PATCH 11/59] Fix more types --- .../Acceptance/BaseAcceptanceTestCase.php | 2 ++ spec/Recruiter/Acceptance/EnduranceTest.php | 3 +++ spec/Recruiter/Acceptance/HooksTest.php | 2 ++ .../RepeatableJobsAreScheduledTest.php | 4 ++-- .../Acceptance/WorkerRepositoryTest.php | 1 + spec/Recruiter/CleanerTest.php | 9 ++++++++ ...rkableImplementsFinalizerInterfaceTest.php | 15 +++++++++---- spec/Recruiter/Job/RepositoryTest.php | 7 +++++++ .../JobCallCustomMethodOnWorkableTest.php | 10 +++++++-- .../Recruiter/JobSendEventsToWorkableTest.php | 12 +++++++---- ...keRetryPolicyFromRetriableWorkableTest.php | 21 +++++++++++++------ spec/Recruiter/JobTest.php | 6 +++++- .../JobToBePassedRetryStatisticsTest.php | 14 ++++++++++--- spec/Recruiter/JobToScheduleTest.php | 7 ++++++- spec/Recruiter/PickAvailableWorkersTest.php | 7 ++++++- .../RetriableExceptionFilterTest.php | 15 +++++++++---- spec/Recruiter/TaggableWorkableTest.php | 6 +++++- spec/Recruiter/WaitStrategyTest.php | 7 ++++++- spec/Recruiter/WorkerProcessTest.php | 13 +++++++++--- 19 files changed, 128 insertions(+), 33 deletions(-) diff --git a/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php b/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php index 50785542..15d1ff4a 100644 --- a/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php +++ b/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php @@ -22,6 +22,8 @@ abstract class BaseAcceptanceTestCase extends TestCase protected Collection $archived; protected Collection $roster; protected Collection $schedulers; + protected array $files; + protected int $jobs; private ?array $processRecruiter; private ?array $processCleaner; private array $processWorkers; diff --git a/spec/Recruiter/Acceptance/EnduranceTest.php b/spec/Recruiter/Acceptance/EnduranceTest.php index d27e4495..66d74e72 100644 --- a/spec/Recruiter/Acceptance/EnduranceTest.php +++ b/spec/Recruiter/Acceptance/EnduranceTest.php @@ -16,6 +16,9 @@ class EnduranceTest extends BaseAcceptanceTestCase { use Eris\TestTrait; + private Repository $jobRepository; + private string $actionLog; + protected function setUp(): void { parent::setUp(); diff --git a/spec/Recruiter/Acceptance/HooksTest.php b/spec/Recruiter/Acceptance/HooksTest.php index 6003f6a1..68fed4bd 100644 --- a/spec/Recruiter/Acceptance/HooksTest.php +++ b/spec/Recruiter/Acceptance/HooksTest.php @@ -9,6 +9,8 @@ class HooksTest extends BaseAcceptanceTestCase { + private MemoryLimit $memoryLimit; + private array $events; protected function setUp(): void { $this->memoryLimit = new MemoryLimit('64MB'); diff --git a/spec/Recruiter/Acceptance/RepeatableJobsAreScheduledTest.php b/spec/Recruiter/Acceptance/RepeatableJobsAreScheduledTest.php index abce5a0c..a9c6ab34 100644 --- a/spec/Recruiter/Acceptance/RepeatableJobsAreScheduledTest.php +++ b/spec/Recruiter/Acceptance/RepeatableJobsAreScheduledTest.php @@ -183,8 +183,8 @@ private function fetchSchedulers() class FixedSchedulePolicy implements SchedulePolicy { - private $timestamp; - private $index; + private array $timestamps; + private int $index; public function __construct($timestamps, $index = 0) { diff --git a/spec/Recruiter/Acceptance/WorkerRepositoryTest.php b/spec/Recruiter/Acceptance/WorkerRepositoryTest.php index c8c1638f..85634cbd 100644 --- a/spec/Recruiter/Acceptance/WorkerRepositoryTest.php +++ b/spec/Recruiter/Acceptance/WorkerRepositoryTest.php @@ -7,6 +7,7 @@ class WorkerRepositoryTest extends BaseAcceptanceTestCase { + private Repository $repository; protected function setUp(): void { parent::setUp(); diff --git a/spec/Recruiter/CleanerTest.php b/spec/Recruiter/CleanerTest.php index 59123781..295875a0 100644 --- a/spec/Recruiter/CleanerTest.php +++ b/spec/Recruiter/CleanerTest.php @@ -3,11 +3,20 @@ namespace Recruiter; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; use Timeless\Interval; +use Timeless\Clock; +use Timeless\Moment; use Timeless as T; class CleanerTest extends TestCase { + private T\ClockInterface $clock; + private Moment $now; + private MockObject $jobRepository; + private Cleaner $cleaner; + private Interval $interval; + protected function setUp(): void { $this->clock = T\clock()->stop(); diff --git a/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php b/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php index 84f8960e..40c86b12 100644 --- a/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php +++ b/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php @@ -4,19 +4,26 @@ use Exception; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; +use Recruiter\Job\Repository; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest extends TestCase { + private MockObject&Repository $repository; + private EventDispatcherInterface $dispatcher; + + /** + * @throws \PHPUnit\Framework\MockObject\Exception + */ protected function setUp(): void { $this->repository = $this - ->getMockBuilder('Recruiter\Job\Repository') + ->getMockBuilder(Repository::class) ->disableOriginalConstructor() ->getMock(); - $this->dispatcher = $this->createMock( - 'Symfony\Component\EventDispatcher\EventDispatcherInterface' - ); + $this->dispatcher = $this->createMock(EventDispatcherInterface::class); } public function testFinalizableFailureMethodsAreCalledWhenJobFails() diff --git a/spec/Recruiter/Job/RepositoryTest.php b/spec/Recruiter/Job/RepositoryTest.php index 7c0e7e58..34579406 100644 --- a/spec/Recruiter/Job/RepositoryTest.php +++ b/spec/Recruiter/Job/RepositoryTest.php @@ -2,6 +2,7 @@ namespace Recruiter\Job; use MongoDB\BSON\ObjectId; +use MongoDB\Database; use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -11,11 +12,17 @@ use Recruiter\JobToSchedule; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Timeless as T; +use Timeless\Clock; use Timeless\Interval; use Timeless\Moment; class RepositoryTest extends TestCase { + private Database $recruiterDb; + private Repository $repository; + private T\ClockInterface $clock; + private EventDispatcherInterface $eventDispatcher; + /** * @throws Exception */ diff --git a/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php b/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php index 9217b2dc..580d9c9b 100644 --- a/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php +++ b/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php @@ -4,18 +4,24 @@ use Exception; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; +use Recruiter\Job\Repository; class JobCallCustomMethodOnWorkableTest extends TestCase { + private MockObject&Workable $workable; + private MockObject&Repository $repository; + private Job $job; + protected function setUp(): void { $this->workable = $this - ->getMockBuilder('Recruiter\Workable') + ->getMockBuilder(Workable::class) ->setMethods(['export', 'import', 'asJobOf', 'send']) ->getMock(); $this->repository = $this - ->getMockBuilder('Recruiter\Job\Repository') + ->getMockBuilder(Repository::class) ->disableOriginalConstructor() ->getMock(); diff --git a/spec/Recruiter/JobSendEventsToWorkableTest.php b/spec/Recruiter/JobSendEventsToWorkableTest.php index 240a2ccb..cf72836f 100644 --- a/spec/Recruiter/JobSendEventsToWorkableTest.php +++ b/spec/Recruiter/JobSendEventsToWorkableTest.php @@ -3,21 +3,25 @@ namespace Recruiter; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; use Recruiter\Job\Event; use Recruiter\Job\EventListener; +use Recruiter\Job\Repository; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class JobSendEventsToWorkableTest extends TestCase { + private MockObject&Repository $repository; + private MockObject&EventDispatcherInterface $dispatcher; + protected function setUp(): void { $this->repository = $this - ->getMockBuilder('Recruiter\Job\Repository') + ->getMockBuilder(Repository::class) ->disableOriginalConstructor() ->getMock(); - $this->dispatcher = $this->createMock( - 'Symfony\Component\EventDispatcher\EventDispatcherInterface' - ); + $this->dispatcher = $this->createMock(EventDispatcherInterface::class); } public function testTakeRetryPolicyFromRetriableInstance() diff --git a/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php b/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php index a8a67abd..84f89dd6 100644 --- a/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php +++ b/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php @@ -2,26 +2,36 @@ namespace Recruiter; +use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; +use Recruiter\Job\Repository; +use Recruiter\RetryPolicy\BaseRetryPolicy; use Timeless as T; use Recruiter\RetryPolicy; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class JobTakeRetryPolicyFromRetriableWorkableTest extends TestCase { + private MockObject&Repository $repository; + private MockObject&EventDispatcherInterface $eventDispatcher; + protected function setUp(): void { $this->repository = $this - ->getMockBuilder('Recruiter\Job\Repository') + ->getMockBuilder(Repository::class) ->disableOriginalConstructor() ->getMock(); - $this->eventDispatcher = $this - ->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'); + $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); } + /** + * @throws Exception + */ public function testTakeRetryPolicyFromRetriableInstance() { - $retryPolicy = $this->createMock('Recruiter\RetryPolicy\BaseRetryPolicy'); + $retryPolicy = $this->createMock(BaseRetryPolicy::class); $retryPolicy->expects($this->once())->method('schedule'); $workable = new WorkableThatIsAlsoRetriable($retryPolicy); @@ -35,9 +45,8 @@ class WorkableThatIsAlsoRetriable implements Workable, Retriable { use WorkableBehaviour; - public function __construct(RetryPolicy $retryWithPolicy) + public function __construct(private readonly RetryPolicy $retryWithPolicy) { - $this->retryWithPolicy = $retryWithPolicy; } public function retryWithPolicy(): RetryPolicy diff --git a/spec/Recruiter/JobTest.php b/spec/Recruiter/JobTest.php index e9150b62..88334c7f 100644 --- a/spec/Recruiter/JobTest.php +++ b/spec/Recruiter/JobTest.php @@ -2,16 +2,20 @@ namespace Recruiter; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; +use Recruiter\Job\Repository; use Recruiter\Workable\AlwaysFail; use RuntimeException; use Recruiter\Infrastructure\Memory\MemoryLimit; class JobTest extends TestCase { + private MockObject&Repository $repository; + protected function setUp(): void { $this->repository = $this - ->getMockBuilder('Recruiter\Job\Repository') + ->getMockBuilder(Repository::class) ->disableOriginalConstructor() ->getMock(); } diff --git a/spec/Recruiter/JobToBePassedRetryStatisticsTest.php b/spec/Recruiter/JobToBePassedRetryStatisticsTest.php index ce95ef0a..e54cf7a2 100644 --- a/spec/Recruiter/JobToBePassedRetryStatisticsTest.php +++ b/spec/Recruiter/JobToBePassedRetryStatisticsTest.php @@ -2,26 +2,34 @@ namespace Recruiter; +use PHPUnit\Framework\MockObject\Exception; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Timeless as T; +use Recruiter\Job\Repository; use Recruiter\RetryPolicy\DoNotDoItAgain; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class JobToBePassedRetryStatisticsTest extends TestCase { + private MockObject&Repository $repository; + protected function setUp(): void { $this->repository = $this - ->getMockBuilder('Recruiter\Job\Repository') + ->getMockBuilder(Repository::class) ->disableOriginalConstructor() ->getMock(); } + /** + * @throws Exception + */ public function testTakeRetryPolicyFromRetriableInstance() { $workable = new WorkableThatUsesRetryStatistics(); $job = Job::around($workable, $this->repository); - $job->execute($this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface')); + $job->execute($this->createMock(EventDispatcherInterface::class)); $this->assertTrue($job->done(), "Job requiring retry statistics was not executed correctly: " . var_export($job->export(), true)); } } diff --git a/spec/Recruiter/JobToScheduleTest.php b/spec/Recruiter/JobToScheduleTest.php index 1d1d72e5..36fc6ff5 100644 --- a/spec/Recruiter/JobToScheduleTest.php +++ b/spec/Recruiter/JobToScheduleTest.php @@ -3,16 +3,21 @@ namespace Recruiter; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; use Timeless as T; +use Timeless\Clock; use Recruiter\RetryPolicy; class JobToScheduleTest extends TestCase { + private T\ClockInterface $clock; + private MockObject&Job $job; + protected function setUp(): void { $this->clock = T\clock()->stop(); $this->job = $this - ->getMockBuilder('Recruiter\Job') + ->getMockBuilder(Job::class) ->disableOriginalConstructor() ->getMock(); } diff --git a/spec/Recruiter/PickAvailableWorkersTest.php b/spec/Recruiter/PickAvailableWorkersTest.php index 60ff3db7..7c54d84f 100644 --- a/spec/Recruiter/PickAvailableWorkersTest.php +++ b/spec/Recruiter/PickAvailableWorkersTest.php @@ -4,14 +4,19 @@ use ArrayIterator; use MongoDB\BSON\ObjectId; +use MongoDB\Collection; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; class PickAvailableWorkersTest extends TestCase { + private MockObject&Collection $repository; + private int $workersPerUnit; + protected function setUp(): void { $this->repository = $this - ->getMockBuilder('MongoDB\Collection') + ->getMockBuilder(Collection::class) ->disableOriginalConstructor() ->getMock(); diff --git a/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php b/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php index f753f0ce..bffbd9e8 100644 --- a/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php +++ b/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php @@ -5,17 +5,24 @@ use Exception; use InvalidArgumentException; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; +use Recruiter\RetryPolicy; class RetriableExceptionFilterTest extends TestCase { + private MockObject&RetryPolicy $filteredRetryPolicy; + + /** + * @throws \PHPUnit\Framework\MockObject\Exception + */ protected function setUp(): void { - $this->filteredRetryPolicy = $this->createMock('Recruiter\RetryPolicy'); + $this->filteredRetryPolicy = $this->createMock(RetryPolicy::class); } public function testCallScheduleOnRetriableException() { - $exception = $this->createMock('Exception'); + $exception = $this->createMock(Exception::class); $classOfException = get_class($exception); $filter = new RetriableExceptionFilter($this->filteredRetryPolicy, [$classOfException]); @@ -28,7 +35,7 @@ public function testCallScheduleOnRetriableException() public function testDoNotCallScheduleOnNonRetriableException() { - $exception = $this->createMock('Exception'); + $exception = $this->createMock(Exception::class); $classOfException = get_class($exception); $filter = new RetriableExceptionFilter($this->filteredRetryPolicy, [$classOfException]); @@ -41,7 +48,7 @@ public function testDoNotCallScheduleOnNonRetriableException() public function testWhenExceptionIsNotRetriableThenArchiveTheJob() { - $exception = $this->createMock('Exception'); + $exception = $this->createMock(Exception::class); $classOfException = get_class($exception); $filter = new RetriableExceptionFilter($this->filteredRetryPolicy, [$classOfException]); diff --git a/spec/Recruiter/TaggableWorkableTest.php b/spec/Recruiter/TaggableWorkableTest.php index 33bb37c1..949d52a3 100644 --- a/spec/Recruiter/TaggableWorkableTest.php +++ b/spec/Recruiter/TaggableWorkableTest.php @@ -3,15 +3,19 @@ namespace Recruiter; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; +use Recruiter\Job\Repository; use Timeless as T; use Recruiter\Taggable; class TaggableWorkableTest extends TestCase { + private MockObject&Repository $repository; + protected function setUp(): void { $this->repository = $this - ->getMockBuilder('Recruiter\Job\Repository') + ->getMockBuilder(Repository::class) ->disableOriginalConstructor() ->getMock(); } diff --git a/spec/Recruiter/WaitStrategyTest.php b/spec/Recruiter/WaitStrategyTest.php index 7cdf3fbd..909aa0a9 100644 --- a/spec/Recruiter/WaitStrategyTest.php +++ b/spec/Recruiter/WaitStrategyTest.php @@ -7,9 +7,14 @@ class WaitStrategyTest extends TestCase { + private T\Interval $waited; + private \Closure $howToWait; + private T\Interval $timeToWaitAtLeast; + private T\Interval $timeToWaitAtMost; + protected function setUp(): void { - $this->waited = 0; + $this->waited = T\milliseconds(0); $this->howToWait = function($microseconds): void { $this->waited = T\milliseconds($microseconds/1000); }; diff --git a/spec/Recruiter/WorkerProcessTest.php b/spec/Recruiter/WorkerProcessTest.php index b808410b..df9c1b30 100644 --- a/spec/Recruiter/WorkerProcessTest.php +++ b/spec/Recruiter/WorkerProcessTest.php @@ -3,14 +3,21 @@ namespace Recruiter; use PHPUnit\Framework\TestCase; +use PHPUnit\Framework\MockObject\MockObject; +use Recruiter\Worker\Process; +use Recruiter\Worker\Repository; +use Sink\BlackHole; class WorkerProcessTest extends TestCase { + private int $pid; + private MockObject&Repository $repository; + protected function setUp(): void { $this->pid = 4242; - $this->repository = $this->getMockBuilder('Recruiter\Worker\Repository') + $this->repository = $this->getMockBuilder(Repository::class) ->disableOriginalConstructor() ->getMock(); } @@ -18,13 +25,13 @@ protected function setUp(): void public function testIfNotAliveWhenIsNotAliveReturnsItself() { $process = $this->givenWorkerProcessDead(); - $this->assertInstanceOf('Recruiter\Worker\Process', $process->ifDead()); + $this->assertInstanceOf(Process::class, $process->ifDead()); } public function testIfNotAliveWhenIsAliveReturnsBlackHole() { $process = $this->givenWorkerProcessAlive(); - $this->assertInstanceOf('Sink\BlackHole', $process->ifDead()); + $this->assertInstanceOf(BlackHole::class, $process->ifDead()); } public function testRetireWorkerIfNotAlive() From 9a603c482c45a8fe9d6dc4dbd280e74bc526a0e1 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 03:19:24 +0200 Subject: [PATCH 12/59] Fix further type hints --- spec/Recruiter/Job/RepositoryTest.php | 4 +-- .../Recruiter/JobSendEventsToWorkableTest.php | 13 +++++---- spec/Recruiter/JobTest.php | 2 +- spec/Recruiter/TaggableWorkableTest.php | 4 +-- src/Recruiter/Cleaner.php | 2 +- src/Recruiter/Command/RecruiterJobCommand.php | 18 +++++------- .../Command/Bko/AnalyticsCommand.php | 24 ++++----------- .../Command/Bko/JobRecoverCommand.php | 29 ++++--------------- .../Command/Bko/RemoveSchedulerCommand.php | 6 ++-- src/Recruiter/Job/Repository.php | 8 ++--- src/Recruiter/Recruiter.php | 4 +-- 11 files changed, 43 insertions(+), 71 deletions(-) diff --git a/spec/Recruiter/Job/RepositoryTest.php b/spec/Recruiter/Job/RepositoryTest.php index 34579406..772da5d4 100644 --- a/spec/Recruiter/Job/RepositoryTest.php +++ b/spec/Recruiter/Job/RepositoryTest.php @@ -238,7 +238,7 @@ public function testGetRecentJobsWithManyAttempts() $jobs = $this->repository->recentJobsWithManyAttempts($lowerLimit, $upperLimit); $jobsFounds = 0; foreach ($jobs as $job) { - $this->assertRegExp( + $this->assertMatchesRegularExpression( '/many_attempts_and_archived|many_attempts_and_scheduled/', reset($job->export()['workable']['parameters']) ); @@ -384,7 +384,7 @@ public function testGetSlowRecentJobs() $jobs = $this->repository->slowRecentJobs($lowerLimit, $upperLimit); $jobsFounds = 0; foreach ($jobs as $job) { - $this->assertRegExp( + $this->assertMatchesRegularExpression( '/slow_job_recent_archived|slow_job_recent_scheduled/', reset($job->export()['workable']['parameters']) ); diff --git a/spec/Recruiter/JobSendEventsToWorkableTest.php b/spec/Recruiter/JobSendEventsToWorkableTest.php index cf72836f..ff778b6d 100644 --- a/spec/Recruiter/JobSendEventsToWorkableTest.php +++ b/spec/Recruiter/JobSendEventsToWorkableTest.php @@ -2,6 +2,7 @@ namespace Recruiter; +use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\TestCase; use PHPUnit\Framework\MockObject\MockObject; use Recruiter\Job\Event; @@ -24,9 +25,12 @@ protected function setUp(): void $this->dispatcher = $this->createMock(EventDispatcherInterface::class); } + /** + * @throws Exception + */ public function testTakeRetryPolicyFromRetriableInstance() { - $listener = $this->createPartialMock('StdClass', ['onEvent']); + $listener = $this->createPartialMock(EventListener::class, ['onEvent']); $listener ->expects($this->exactly(3)) ->method('onEvent') @@ -46,14 +50,13 @@ class WorkableThatIsAlsoAnEventListener implements Workable, EventListener { use WorkableBehaviour; - public function __construct($listener) + public function __construct(private readonly EventListener $listener) { - $this->listener = $listener; } - public function onEvent($channel, Event $e) + public function onEvent($channel, Event $ev) { - return $this->listener->onEvent($channel, $e); + return $this->listener->onEvent($channel, $ev); } public function execute() diff --git a/spec/Recruiter/JobTest.php b/spec/Recruiter/JobTest.php index 88334c7f..c56a31dc 100644 --- a/spec/Recruiter/JobTest.php +++ b/spec/Recruiter/JobTest.php @@ -53,7 +53,7 @@ public function testRetryStatisticsOnSubsequentExecutions() $this->assertArrayHasKey('message', $lastExecution); $this->assertArrayHasKey('trace', $lastExecution); $this->assertEquals("Sorry, I'm good for nothing", $lastExecution['message']); - $this->assertRegexp("/.*AlwaysFail->execute.*/", $lastExecution['trace']); + $this->assertMatchesRegularExpression("/.*AlwaysFail->execute.*/", $lastExecution['trace']); } public function testArrayAsGroupIsNotAllowed() diff --git a/spec/Recruiter/TaggableWorkableTest.php b/spec/Recruiter/TaggableWorkableTest.php index 949d52a3..ee85fea5 100644 --- a/spec/Recruiter/TaggableWorkableTest.php +++ b/spec/Recruiter/TaggableWorkableTest.php @@ -109,12 +109,12 @@ public function taggedAs() return $this->tags; } - public function export() + public function export(): array { return ['tags' => $this->tags]; } - public static function import(array $parameters) + public static function import(array $parameters): static { return new self($parameters['tags']); } diff --git a/src/Recruiter/Cleaner.php b/src/Recruiter/Cleaner.php index 0c901282..b207b7eb 100644 --- a/src/Recruiter/Cleaner.php +++ b/src/Recruiter/Cleaner.php @@ -25,7 +25,7 @@ public function cleanArchived(Interval $gracePeriod) return $this->repository->cleanArchived($upperLimit); } - public function cleanScheduled(Interval $gracePeriod = null) + public function cleanScheduled(?Interval $gracePeriod = null) { $upperLimit = T\now(); if (!is_null($gracePeriod)) { diff --git a/src/Recruiter/Command/RecruiterJobCommand.php b/src/Recruiter/Command/RecruiterJobCommand.php index d5f12374..77be4590 100644 --- a/src/Recruiter/Command/RecruiterJobCommand.php +++ b/src/Recruiter/Command/RecruiterJobCommand.php @@ -1,25 +1,21 @@ recruiter = $recruiter; } - protected function configure() + protected function configure(): void { $this ->setName('recruiter:command') @@ -32,11 +28,13 @@ protected function configure() ; } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { ShellCommand::fromCommandLine($input->getArgument('shell_command')) ->asJobOf($this->recruiter) ->inBackground() ->execute(); + + return self::SUCCESS; } } diff --git a/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php b/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php index 580b577a..49c4273c 100644 --- a/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php +++ b/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php @@ -22,27 +22,11 @@ class AnalyticsCommand extends Command /** * @var Recruiter */ - private $recruiter; + private Recruiter $recruiter; - /** - * @var Factory - */ - private $factory; - - /** - * @var LoggerInterface - */ - private $logger; - - /** - * @param Factory $factory - * @param LoggerInterface $logger - */ - public function __construct(Factory $factory, LoggerInterface $logger) + public function __construct(private readonly Factory $factory, private readonly LoggerInterface $logger) { parent::__construct(); - $this->factory = $factory; - $this->logger = $logger; } protected function configure() @@ -66,7 +50,7 @@ protected function configure() ; } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { /** @var string */ $target = $input->getOption('target'); @@ -96,6 +80,8 @@ protected function execute(InputInterface $input, OutputInterface $output) $table->render(); echo PHP_EOL; } + + return self::SUCCESS; } private function calculateColumnsWidth(array $analytics): int diff --git a/src/Recruiter/Infrastructure/Command/Bko/JobRecoverCommand.php b/src/Recruiter/Infrastructure/Command/Bko/JobRecoverCommand.php index c484c085..6ec828e7 100644 --- a/src/Recruiter/Infrastructure/Command/Bko/JobRecoverCommand.php +++ b/src/Recruiter/Infrastructure/Command/Bko/JobRecoverCommand.php @@ -23,35 +23,16 @@ class JobRecoverCommand extends Command { - /** - * @var Recruiter - */ - private $recruiter; - - /** - * @var Factory - */ - private $factory; - - /** - * @var LoggerInterface - */ - private $logger; - - /** - * @var JobRepository - */ - private $jobRepository; + private Recruiter $recruiter; + private JobRepository $jobRepository; /** * @param Factory $factory * @param LoggerInterface $logger */ - public function __construct(Factory $factory, LoggerInterface $logger) + public function __construct(private readonly Factory $factory, private readonly LoggerInterface $logger) { parent::__construct(); - $this->factory = $factory; - $this->logger = $logger; } protected function configure() @@ -80,7 +61,7 @@ protected function configure() ; } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { /** @var string */ $target = $input->getOption('target'); @@ -109,6 +90,8 @@ protected function execute(InputInterface $input, OutputInterface $output) ->save(); $output->writeln("Job recovered, new job id is `{$job->id()}`"); + + return self::SUCCESS; } private function createJobFromAnArchivedJob(Job $archivedJob, JobRepository $repository): Job diff --git a/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php b/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php index f7798df2..70add3e9 100644 --- a/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php +++ b/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php @@ -72,12 +72,12 @@ protected function initialize(InputInterface $input, OutputInterface $output) $this->schedulerRepository = new SchedulerRepository($db); } - protected function execute(InputInterface $input, OutputInterface $output) + protected function execute(InputInterface $input, OutputInterface $output): int { $outputData = $this->buildOutputData(); if (!$outputData) { $output->writeln('There are no schedulers yet.'); - return null; + return self::SUCCESS; } $this->printTable($outputData, $output); @@ -89,6 +89,8 @@ protected function execute(InputInterface $input, OutputInterface $output) $this->schedulerRepository->deleteByUrn($selectedUrn); $this->logger->info("[Recruiter] the scheduler with urn `$selectedUrn` was deleted!"); } + + return self::SUCCESS; } private function selectUrnToDelete(array $urns, InputInterface $input, OutputInterface $output) diff --git a/src/Recruiter/Job/Repository.php b/src/Recruiter/Job/Repository.php index 9ea4e305..169bda40 100644 --- a/src/Recruiter/Job/Repository.php +++ b/src/Recruiter/Job/Repository.php @@ -131,8 +131,8 @@ public function cleanScheduled(T\Moment $upperLimit) public function queued( $group = null, - T\Moment $at = null, - T\Moment $from = null, + ?T\Moment $at = null, + ?T\Moment $from = null, array $query = [] ) { if ($at === null) { @@ -152,7 +152,7 @@ public function queued( return $this->scheduled->count($query); } - public function postponed($group = null, T\Moment $at = null, array $query = []) + public function postponed($group = null, ?T\Moment $at = null, array $query = []) { if ($at === null) { $at = T\now(); @@ -199,7 +199,7 @@ public function queuedGroupedBy($field, array $query = [], $group = null) return $distinctAndCount; } - public function recentHistory($group = null, T\Moment $at = null, array $query = []) + public function recentHistory($group = null, ?T\Moment $at = null, array $query = []) { if ($at === null) { $at = T\now(); diff --git a/src/Recruiter/Recruiter.php b/src/Recruiter/Recruiter.php index 12adc429..f9dea2e8 100644 --- a/src/Recruiter/Recruiter.php +++ b/src/Recruiter/Recruiter.php @@ -65,12 +65,12 @@ public function queuedGroupedBy($field, array $query = [], $group = null) /** * @deprecated use the method `analytics` instead. */ - public function statistics($group = null, Moment $at = null, array $query = []) + public function statistics($group = null, ?Moment $at = null, array $query = []) { return $this->analytics($group, $at, $query); } - public function analytics($group = null, Moment $at = null, array $query = []) + public function analytics($group = null, ?Moment $at = null, array $query = []) { $totalsScheduledJobs = $this->jobs->scheduledCount($group, $query); $queued = $this->jobs->queued($group, $at, $at ? $at->before(T\hour(24)) : null, $query); From 1dad2bd1fe6ef554305fc16362e6e86746ca41a7 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 03:26:08 +0200 Subject: [PATCH 13/59] Fix more type errors --- spec/Recruiter/Acceptance/HooksTest.php | 2 +- .../WorkerGuaranteedToExitWhenAMemoryLeakOccurs.php | 2 +- src/Recruiter/Workable/FactoryMethodCommand.php | 7 ++++--- src/Recruiter/Workable/ShellCommand.php | 4 ++-- 4 files changed, 8 insertions(+), 7 deletions(-) diff --git a/spec/Recruiter/Acceptance/HooksTest.php b/spec/Recruiter/Acceptance/HooksTest.php index 68fed4bd..69db7974 100644 --- a/spec/Recruiter/Acceptance/HooksTest.php +++ b/spec/Recruiter/Acceptance/HooksTest.php @@ -5,7 +5,7 @@ use Recruiter\Workable\AlwaysFail; use Recruiter\Workable\AlwaysSucceed; use Recruiter\RetryPolicy\RetryManyTimes; -use Symfony\Component\EventDispatcher\Event; +use Recruiter\Job\Event; class HooksTest extends BaseAcceptanceTestCase { diff --git a/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWhenAMemoryLeakOccurs.php b/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWhenAMemoryLeakOccurs.php index 5ceab2d7..a48487bb 100644 --- a/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWhenAMemoryLeakOccurs.php +++ b/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWhenAMemoryLeakOccurs.php @@ -2,7 +2,7 @@ namespace Recruiter\Acceptance; -Recruiter\Concurrency\Timeout; +use Recruiter\Concurrency\Timeout; use Recruiter\Workable\ConsumingMemoryCommand; use Timeless as T; diff --git a/src/Recruiter/Workable/FactoryMethodCommand.php b/src/Recruiter/Workable/FactoryMethodCommand.php index 6f7c33a0..8a354534 100644 --- a/src/Recruiter/Workable/FactoryMethodCommand.php +++ b/src/Recruiter/Workable/FactoryMethodCommand.php @@ -3,6 +3,7 @@ use Recruiter\Workable; use Recruiter\Recruiter; +use Recruiter\JobToSchedule; class FactoryMethodCommand implements Workable { @@ -42,7 +43,7 @@ private function __construct(array $steps = []) $this->steps = $steps; } - public function asJobOf(Recruiter $recruiter) + public function asJobOf(Recruiter $recruiter): JobToSchedule { return $recruiter->jobOf($this); } @@ -97,14 +98,14 @@ public function __call($method, $arguments) return $this; } - public function export() + public function export(): array { return [ 'steps' => $this->steps, ]; } - public static function import(array $parameters) + public static function import(array $parameters): static { return new self($parameters['steps']); } diff --git a/src/Recruiter/Workable/ShellCommand.php b/src/Recruiter/Workable/ShellCommand.php index c3531b54..ee8c3543 100644 --- a/src/Recruiter/Workable/ShellCommand.php +++ b/src/Recruiter/Workable/ShellCommand.php @@ -32,12 +32,12 @@ public function execute() return $output; } - public function export() + public function export(): array { return ['command' => $this->commandLine]; } - public static function import(array $parameters) + public static function import(array $parameters): static { return new self($parameters['command']); } From 2608689a9593f4ad216ce767c4ae0578209be4f3 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 03:30:20 +0200 Subject: [PATCH 14/59] Normalize + extensions --- composer.json | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/composer.json b/composer.json index 8d1ece1f..29e2ca4b 100644 --- a/composer.json +++ b/composer.json @@ -27,7 +27,9 @@ "homepage": "https://github.com/recruiterphp/recruiter", "require": { "php": "^8.4", + "ext-bcmath": "*", "ext-mongodb": ">=1.1", + "ext-posix": "*", "dragonmantank/cron-expression": "^3.4", "gabrielelana/byte-units": "^0.5", "mongodb/mongodb": "^2.1", @@ -37,17 +39,16 @@ "recruiterphp/geezer": "^6.0", "symfony/console": "^7.3", "symfony/event-dispatcher": "^7.3", - "ulrichsg/getopt-php": "^4.0", - "ext-bcmath": "*" + "ulrichsg/getopt-php": "^4.0" }, "require-dev": { + "ext-pcntl": "*", "dms/phpunit-arraysubset-asserts": "^0.5", "ergebnis/composer-normalize": "^2.47", "giorgiosironi/eris": "^1.0", "phpstan/phpstan": "*", "phpunit/phpunit": "^10.0", - "rector/rector": "^2.1", - "ext-pcntl": "*" + "rector/rector": "^2.1" }, "suggest": { "symfony/console": "In order to use Recruiter\\Command\\RecruiterJobCommand." From 1a0348e7c1c2e26ab6d980cc48e9167611b0b1ec Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 03:31:00 +0200 Subject: [PATCH 15/59] Scan directory for Eris --- phpstan.neon | 2 ++ 1 file changed, 2 insertions(+) diff --git a/phpstan.neon b/phpstan.neon index dc25ee1c..dc4e3b09 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -3,6 +3,8 @@ parameters: paths: - src - spec + scanDirectories: + - %currentWorkingDirectory%/vendor/giorgiosironi/eris/src/ ignoreErrors: - '#Instantiated class Recruiter\\Workable\\ThisClassDoesnNotExists not found.#' - '#Constructor of class Recruiter\\Workable\\FailsInConstructor has an unused parameter \$parameters.#' From 791f2fb6397a030115e922ca052a23c7d1c6367c Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 03:34:17 +0200 Subject: [PATCH 16/59] Fix configuration of phpstan --- phpstan.neon | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index dc4e3b09..7f86cc94 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -7,11 +7,7 @@ parameters: - %currentWorkingDirectory%/vendor/giorgiosironi/eris/src/ ignoreErrors: - '#Instantiated class Recruiter\\Workable\\ThisClassDoesnNotExists not found.#' - - '#Constructor of class Recruiter\\Workable\\FailsInConstructor has an unused parameter \$parameters.#' - - '#Static call to instance method stdClass::import\(\)#' - - '#Call to function is_callable\(\) with .recruiter_stept_back. will always evaluate to false.#' - - '#Call to function is_callable\(\) with .recruiter_became…. will always evaluate to false.#' - '#Function recruiter_became_master not found.#' - '#Function recruiter_stept_back not found.#' - - '#Call to an undefined method Traversable::toArray\(\).#' + - '#Unsafe usage of new static\(\).#' inferPrivatePropertyTypeFromConstructor: true From 7787a5d412673b49664b051d5dcd9759fff8e69c Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 03:38:24 +0200 Subject: [PATCH 17/59] Increase PHPStan level to 1 --- phpstan.neon | 4 +++- spec/Recruiter/RetryPolicy/TimeTableTest.php | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 7f86cc94..e80c96d0 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ parameters: - level: 0 + level: 1 paths: - src - spec @@ -10,4 +10,6 @@ parameters: - '#Function recruiter_became_master not found.#' - '#Function recruiter_stept_back not found.#' - '#Unsafe usage of new static\(\).#' + - '#Call to an undefined static method Sink\\BlackHole::whateverStaticMethod\(\).#' + - '#Constructor of class Recruiter\\Workable\\FailsInConstructor has an unused parameter \$parameters.#' inferPrivatePropertyTypeFromConstructor: true diff --git a/spec/Recruiter/RetryPolicy/TimeTableTest.php b/spec/Recruiter/RetryPolicy/TimeTableTest.php index 2e46f143..dd93d037 100644 --- a/spec/Recruiter/RetryPolicy/TimeTableTest.php +++ b/spec/Recruiter/RetryPolicy/TimeTableTest.php @@ -119,7 +119,7 @@ private function givenJobThat(T\Moment $wasCreatedAt) private function jobThatWasCreated($relativeTime) { - $wasCreatedAt = T\Moment::fromTimestamp(strtotime($relativeTime), T\now()->seconds()); + $wasCreatedAt = T\Moment::fromTimestamp(strtotime($relativeTime)); $job = $this->getMockBuilder('Recruiter\JobAfterFailure') ->disableOriginalConstructor() ->setMethods(['createdAt', 'scheduleAt']) From 66456e366d98efc94dbcef9b97dd246f59f165a8 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 03:52:06 +0200 Subject: [PATCH 18/59] Separate long tests --- Makefile | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/Makefile b/Makefile index 3df22988..df96bd4d 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build up down test phpstan rector fix-cs install update shell logs clean +.PHONY: build up down test test-long phpstan rector fix-cs install update shell logs clean # Build the Docker image build: @@ -22,7 +22,11 @@ update: # Run all tests except the long ones test: up - docker compose exec php vendor/bin/phpunit + docker compose exec php vendor/bin/phpunit --exclude-group=long + +# Run long tests specifically +test-long: up + docker compose exec php vendor/bin/phpunit --group=long phpstan: up docker compose exec php vendor/bin/phpstan --memory-limit=2G From a22871a07f34b160afe303161d618e37cae84e4f Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 03:53:50 +0200 Subject: [PATCH 19/59] Fix constructor --- src/Recruiter/Workable/FailsInConstructor.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Recruiter/Workable/FailsInConstructor.php b/src/Recruiter/Workable/FailsInConstructor.php index 0c1e25df..9523f5b0 100644 --- a/src/Recruiter/Workable/FailsInConstructor.php +++ b/src/Recruiter/Workable/FailsInConstructor.php @@ -9,7 +9,7 @@ class FailsInConstructor implements Workable { use WorkableBehaviour; - public function __construct($parameters = [], $fromRecruiter = true) + public function __construct(protected array $parameters = [], $fromRecruiter = true) { if ($fromRecruiter) { throw new Exception("I am supposed to fail in constructor code for testing purpose"); From d5e83bc4ad986b7426604ca45800c47fc0c81e62 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 11:54:26 +0200 Subject: [PATCH 20/59] Get MONGODB_URI from environment --- src/Recruiter/Infrastructure/Command/CleanerCommand.php | 3 ++- src/Recruiter/Infrastructure/Command/RecruiterCommand.php | 3 ++- src/Recruiter/Infrastructure/Command/WorkerCommand.php | 3 ++- 3 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/Recruiter/Infrastructure/Command/CleanerCommand.php b/src/Recruiter/Infrastructure/Command/CleanerCommand.php index 6a83f108..302d5b71 100644 --- a/src/Recruiter/Infrastructure/Command/CleanerCommand.php +++ b/src/Recruiter/Infrastructure/Command/CleanerCommand.php @@ -127,8 +127,9 @@ public function description(): string public function definition(): InputDefinition { + $defaultMongoUri = getenv('MONGODB_URI') ?: 'mongodb://localhost:27017'; return new InputDefinition([ - new InputOption('target', 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', 'mongodb://localhost:27017/recruiter'), + new InputOption('target', 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', $defaultMongoUri), new InputOption('clean-after', 'c', InputOption::VALUE_REQUIRED, 'delete jobs after :period', '5days'), new InputOption('wait-at-least', null, InputOption::VALUE_REQUIRED, 'Time to wait at least before to search for jobs to clear', '1m'), new InputOption('wait-at-most', null, InputOption::VALUE_REQUIRED, 'Upper limit of time to wait before next polling', '3m'), diff --git a/src/Recruiter/Infrastructure/Command/RecruiterCommand.php b/src/Recruiter/Infrastructure/Command/RecruiterCommand.php index 8d06743c..61f97b95 100644 --- a/src/Recruiter/Infrastructure/Command/RecruiterCommand.php +++ b/src/Recruiter/Infrastructure/Command/RecruiterCommand.php @@ -183,8 +183,9 @@ public function description(): string public function definition(): InputDefinition { + $defaultMongoUri = getenv('MONGODB_URI') ?: 'mongodb://localhost:27017'; return new InputDefinition([ - new InputOption('target', 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', 'mongodb://localhost:27017/recruiter'), + new InputOption('target', 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', $defaultMongoUri), new InputOption('backoff-to', 'b', InputOption::VALUE_REQUIRED, 'Upper limit of time to wait before next polling (milliseconds)', '1600ms'), new InputOption('backoff-from', 'f', InputOption::VALUE_REQUIRED, 'Time to wait at least before to search for new jobs (milliseconds)', '200ms'), new InputOption('lease-time', 'l', InputOption::VALUE_REQUIRED, 'Maximum time to hold a lock before a refresh', '60s'), diff --git a/src/Recruiter/Infrastructure/Command/WorkerCommand.php b/src/Recruiter/Infrastructure/Command/WorkerCommand.php index 45de89ad..626091e0 100644 --- a/src/Recruiter/Infrastructure/Command/WorkerCommand.php +++ b/src/Recruiter/Infrastructure/Command/WorkerCommand.php @@ -119,8 +119,9 @@ public function description(): string public function definition(): InputDefinition { + $defaultMongoUri = getenv('MONGODB_URI') ?: 'mongodb://localhost:27017'; return new InputDefinition([ - new InputOption('target', 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', 'mongodb://localhost:27017/recruiter'), + new InputOption('target', 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', $defaultMongoUri), new InputOption('backoff-to', 'b', InputOption::VALUE_REQUIRED, 'Upper limit of time to wait before next polling', '6400ms'), new InputOption('backoff-from', null, InputOption::VALUE_REQUIRED, 'Time to wait at least before to search for new jobs', '200ms'), new InputOption('memory-limit', 'm', InputOption::VALUE_REQUIRED, 'Maximum amount of memory allocable', '64MB'), From 64de1b0ce0021b581cd816cebc2b54af21cda86b Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 12:18:28 +0200 Subject: [PATCH 21/59] Fix mock call with spy --- ...rkableImplementsFinalizerInterfaceTest.php | 55 ++++++++++++------- 1 file changed, 35 insertions(+), 20 deletions(-) diff --git a/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php b/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php index 40c86b12..7998d92d 100644 --- a/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php +++ b/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php @@ -12,6 +12,7 @@ class FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest exte { private MockObject&Repository $repository; private EventDispatcherInterface $dispatcher; + private ListenerSpy $listener; /** * @throws \PHPUnit\Framework\MockObject\Exception @@ -24,44 +25,57 @@ protected function setUp(): void ->getMock(); $this->dispatcher = $this->createMock(EventDispatcherInterface::class); + $this->listener = new ListenerSpy(); } - public function testFinalizableFailureMethodsAreCalledWhenJobFails() + public function testFinalizableFailureMethodsAreCalledWhenJobFails(): void { $exception = new \Exception('job was failed'); - $listener = $this->createPartialMock('StdClass', ['methodWasCalled']); - $listener - ->expects($this->exactly(3)) - ->method('methodWasCalled') - ->withConsecutive( - [$this->equalTo('afterFailure'), $exception], - [$this->equalTo('afterLastFailure'), $exception], - [$this->equalTo('finalize'), $exception] - ); + $workable = new FinalizableWorkable(function () use ($exception): void { throw $exception; - }, $listener); + }, $this->listener); $job = Job::around($workable, $this->repository); $job->execute($this->dispatcher); + + $calls = $this->listener->calls; + $this->assertCount(3, $calls); + + $this->assertSame('afterFailure', $calls[0][0]); + $this->assertSame($exception, $calls[0][1]); + + $this->assertSame('afterLastFailure', $calls[1][0]); + $this->assertSame($exception, $calls[1][1]); + + $this->assertSame('finalize', $calls[2][0]); + $this->assertSame($exception, $calls[2][1]); } + public function testFinalizableSuccessfullMethodsAreCalledWhenJobIsDone() { - $listener = $this->createPartialMock('StdClass', ['methodWasCalled']); - $listener - ->expects($this->exactly(2)) - ->method('methodWasCalled') - ->withConsecutive( - [$this->equalTo('afterSuccess')], - [$this->equalTo('finalize')] - ); $workable = new FinalizableWorkable(function () { return true; - }, $listener); + }, $this->listener); $job = Job::around($workable, $this->repository); $job->execute($this->dispatcher); + + $calls = $this->listener->calls; + $this->assertCount(2, $calls); + $this->assertSame('afterSuccess', $calls[0][0]); + $this->assertSame('finalize', $calls[1][0]); + } +} + +class ListenerSpy +{ + public array $calls = []; + + public function methodWasCalled(string $name, ?\Throwable $exception = null): void + { + $this->calls[] = [$name, $exception]; } } @@ -76,6 +90,7 @@ class FinalizableWorkable implements Workable, Finalizable public function __construct(callable $whatToDo, $listener) { + $this->parameters = []; $this->listener = $listener; $this->whatToDo = $whatToDo; } From f452a397709e3c714248c26b58fb5c600d624d38 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 12:22:29 +0200 Subject: [PATCH 22/59] Add PHP-CS-Fixer --- .gitignore | 3 +-- .php-cs-fixer.dist.php | 25 +++++++++++++++++++++++++ composer.json | 1 + 3 files changed, 27 insertions(+), 2 deletions(-) create mode 100644 .php-cs-fixer.dist.php diff --git a/.gitignore b/.gitignore index 62560d1e..d1b4fe30 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,4 @@ -.* -!.github +.*.cache composer.phar composer.lock vendor/ diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php new file mode 100644 index 00000000..389eada7 --- /dev/null +++ b/.php-cs-fixer.dist.php @@ -0,0 +1,25 @@ +setRiskyAllowed(true) + ->setRules([ + '@PSR12' => true, + '@Symfony' => true, + 'array_indentation' => true, + 'array_syntax' => ['syntax' => 'short'], + 'concat_space' => ['spacing' => 'one'], + 'declare_strict_types' => true, + 'string_implicit_backslashes' => true, + 'list_syntax' => ['syntax' => 'short'], + 'multiline_whitespace_before_semicolons' => ['strategy' => 'new_line_for_chained_calls'], + 'ordered_imports' => true, + 'phpdoc_to_comment' => false, + 'trailing_comma_in_multiline' => ['elements' => ['arrays', 'arguments', 'parameters']], + 'visibility_required' => ['elements' => ['property', 'method', 'const']], + ]) + ->setFinder( + PhpCsFixer\Finder::create() + ->in(__DIR__ . '/src') + ->in(__DIR__ . '/spec') + ) +; diff --git a/composer.json b/composer.json index 29e2ca4b..1fd7cb23 100644 --- a/composer.json +++ b/composer.json @@ -45,6 +45,7 @@ "ext-pcntl": "*", "dms/phpunit-arraysubset-asserts": "^0.5", "ergebnis/composer-normalize": "^2.47", + "friendsofphp/php-cs-fixer": "^3.85", "giorgiosironi/eris": "^1.0", "phpstan/phpstan": "*", "phpunit/phpunit": "^10.0", From 26e2aeca1292b5a7e79cc1e159a38481b3b480a2 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 12:23:06 +0200 Subject: [PATCH 23/59] Disable strict types for now --- .php-cs-fixer.dist.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.php-cs-fixer.dist.php b/.php-cs-fixer.dist.php index 389eada7..d1055d88 100644 --- a/.php-cs-fixer.dist.php +++ b/.php-cs-fixer.dist.php @@ -8,7 +8,7 @@ 'array_indentation' => true, 'array_syntax' => ['syntax' => 'short'], 'concat_space' => ['spacing' => 'one'], - 'declare_strict_types' => true, + /* 'declare_strict_types' => true, */ 'string_implicit_backslashes' => true, 'list_syntax' => ['syntax' => 'short'], 'multiline_whitespace_before_semicolons' => ['strategy' => 'new_line_for_chained_calls'], From 0ba72786d1df4c56761db94e145df27ebc572c9a Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 12:23:20 +0200 Subject: [PATCH 24/59] Fix Coding Standards --- spec/Recruiter/Acceptance/AssignmentTest.php | 6 +- .../Acceptance/BaseAcceptanceTestCase.php | 68 ++++--- spec/Recruiter/Acceptance/EnduranceTest.php | 47 ++--- .../Acceptance/FaultToleranceTest.php | 30 +-- spec/Recruiter/Acceptance/HooksTest.php | 45 +++-- .../RepeatableJobsAreScheduledTest.php | 17 +- .../Acceptance/SyncronousExecutionTest.php | 7 +- ...rGuaranteedToExitWhenAMemoryLeakOccurs.php | 14 +- ...itWithFailureCodeInCaseOfExceptionTest.php | 3 +- ...WorkerGuaranteedToRetireAfterDeathTest.php | 2 +- .../Acceptance/WorkerRepositoryTest.php | 4 +- spec/Recruiter/CleanerTest.php | 13 +- spec/Recruiter/FactoryTest.php | 6 +- ...rkableImplementsFinalizerInterfaceTest.php | 14 +- .../Infrastructure/Memory/MemoryLimitTest.php | 1 + spec/Recruiter/Job/EventTest.php | 5 +- spec/Recruiter/Job/RepositoryTest.php | 180 +++++++++--------- .../JobCallCustomMethodOnWorkableTest.php | 11 +- .../Recruiter/JobSendEventsToWorkableTest.php | 10 +- ...keRetryPolicyFromRetriableWorkableTest.php | 7 +- spec/Recruiter/JobTest.php | 19 +- .../JobToBePassedRetryStatisticsTest.php | 5 +- spec/Recruiter/JobToScheduleTest.php | 68 ++++--- spec/Recruiter/PickAvailableWorkersTest.php | 26 +-- .../RetryPolicy/ExponentialBackoffTest.php | 16 +- .../RetriableExceptionFilterTest.php | 50 ++--- .../RetryPolicy/SelectByExceptionTest.php | 35 ++-- spec/Recruiter/RetryPolicy/TimeTableTest.php | 35 ++-- spec/Recruiter/SchedulePolicy/CronTest.php | 7 +- spec/Recruiter/TaggableWorkableTest.php | 7 +- spec/Recruiter/WaitStrategyTest.php | 14 +- .../Workable/FactoryMethodCommandTest.php | 12 +- spec/Recruiter/Workable/ShellCommandTest.php | 3 +- spec/Recruiter/WorkablePersistenceTest.php | 2 +- spec/Recruiter/WorkerProcessTest.php | 18 +- spec/Sink/BlackHoleTest.php | 2 +- spec/Timeless/IntervalParseTest.php | 7 +- spec/Timeless/MongoDateTest.php | 8 +- src/Recruiter/AlreadyRunningException.php | 4 +- .../CannotRetireWorkerAtWorkException.php | 5 +- src/Recruiter/Cleaner.php | 2 +- src/Recruiter/Command/RecruiterJobCommand.php | 6 +- src/Recruiter/Factory.php | 13 +- src/Recruiter/Finalizable.php | 9 +- src/Recruiter/FinalizableBehaviour.php | 9 +- .../Command/Bko/AnalyticsCommand.php | 12 +- .../Command/Bko/JobRecoverCommand.php | 19 +- .../Command/Bko/RemoveSchedulerCommand.php | 21 +- .../Infrastructure/Command/CleanerCommand.php | 26 ++- .../Command/RecruiterCommand.php | 35 ++-- .../Infrastructure/Command/WorkerCommand.php | 20 +- .../Filesystem/BootstrapFile.php | 18 +- .../Infrastructure/Memory/MemoryLimit.php | 14 +- .../Memory/MemoryLimitExceededException.php | 2 +- .../Persistence/Mongodb/URI.php | 7 +- src/Recruiter/Job.php | 72 +++---- src/Recruiter/Job/Event.php | 2 + src/Recruiter/Job/Repository.php | 152 +++++++-------- src/Recruiter/JobAfterFailure.php | 5 +- src/Recruiter/JobExecution.php | 10 +- src/Recruiter/JobToSchedule.php | 16 +- src/Recruiter/Recruiter.php | 59 +++--- src/Recruiter/Repeatable.php | 6 +- src/Recruiter/RepeatableInJob.php | 18 +- src/Recruiter/Retriable.php | 4 +- src/Recruiter/RetryPolicy.php | 13 +- src/Recruiter/RetryPolicy/DoNotDoItAgain.php | 2 +- .../RetryPolicy/ExponentialBackoff.php | 13 +- .../RetryPolicy/RetriableException.php | 9 +- .../RetryPolicy/RetriableExceptionFilter.php | 24 +-- src/Recruiter/RetryPolicy/RetryForever.php | 11 +- src/Recruiter/RetryPolicy/RetryManyTimes.php | 11 +- .../RetryPolicy/SelectByException.php | 49 ++--- .../RetryPolicy/SelectByExceptionBuilder.php | 8 +- src/Recruiter/RetryPolicy/TimeTable.php | 26 +-- src/Recruiter/RetryPolicyBehaviour.php | 4 +- src/Recruiter/RetryPolicyInJob.php | 14 +- src/Recruiter/SchedulePolicy.php | 12 +- src/Recruiter/SchedulePolicy/Cron.php | 11 +- src/Recruiter/SchedulePolicy/EveryMinutes.php | 2 - src/Recruiter/SchedulePolicyInJob.php | 14 +- src/Recruiter/Scheduler.php | 31 ++- src/Recruiter/Scheduler/Repository.php | 13 +- src/Recruiter/SynchronousExecutionReport.php | 6 +- src/Recruiter/Taggable.php | 2 +- src/Recruiter/WaitStrategy.php | 8 +- src/Recruiter/Workable.php | 8 +- src/Recruiter/Workable/AlwaysFail.php | 4 +- .../Workable/ConsumingMemoryCommand.php | 3 +- .../Workable/FactoryMethodCommand.php | 17 +- src/Recruiter/Workable/FailsInConstructor.php | 4 +- src/Recruiter/Workable/LazyBones.php | 5 +- .../RecoverRepeatableFromException.php | 11 +- .../Workable/RecoverWorkableFromException.php | 9 +- .../Workable/SampleRepeatableCommand.php | 7 +- src/Recruiter/Workable/ShellCommand.php | 6 +- src/Recruiter/Workable/ThrowsFatalError.php | 1 + src/Recruiter/WorkableBehaviour.php | 6 +- src/Recruiter/WorkableInJob.php | 18 +- src/Recruiter/Worker.php | 39 ++-- src/Recruiter/Worker/Process.php | 2 +- src/Recruiter/Worker/Repository.php | 11 +- .../WorkerDiedInTheLineOfDutyException.php | 5 +- src/Recruiter/functions.php | 5 +- src/Sink/BlackHole.php | 4 +- src/Timeless/ClockInterface.php | 2 +- src/Timeless/Interval.php | 30 +-- src/Timeless/InvalidIntervalFormat.php | 4 +- src/Timeless/Moment.php | 11 +- src/Timeless/MongoDate.php | 3 +- src/Timeless/functions.php | 1 + 111 files changed, 938 insertions(+), 951 deletions(-) diff --git a/spec/Recruiter/Acceptance/AssignmentTest.php b/spec/Recruiter/Acceptance/AssignmentTest.php index 74d9edf7..4c764bb7 100644 --- a/spec/Recruiter/Acceptance/AssignmentTest.php +++ b/spec/Recruiter/Acceptance/AssignmentTest.php @@ -1,4 +1,5 @@ asJobOf($this->recruiter) ->inBackground() - ->execute(); + ->execute() + ; $worker = $this->recruiter->hire($memoryLimit); - list ($assignments, $totalNumber) = $this->recruiter->assignJobsToWorkers(); + [$assignments, $totalNumber] = $this->recruiter->assignJobsToWorkers(); $this->assertEquals(1, count($assignments)); $this->assertEquals(1, $totalNumber); $this->assertTrue((bool) $worker->work()); diff --git a/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php b/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php index 15d1ff4a..f88d26a1 100644 --- a/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php +++ b/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php @@ -1,11 +1,11 @@ until(function () use ($expectedNumber) { - $this->recruiter->retireDeadWorkers(new DateTimeImmutable(), T\seconds(0)); + $this->recruiter->retireDeadWorkers(new \DateTimeImmutable(), T\seconds(0)); + return $this->numberOfWorkers() == $expectedNumber; - }); + }) + ; } protected function startRecruiter(): array @@ -101,13 +103,16 @@ protected function startRecruiter(): array $process = proc_open('exec php bin/recruiter start:recruiter --backoff-to 5000ms --lease-time 10s --considered-dead-after 20s >> /tmp/recruiter.log 2>&1', $descriptors, $pipes, $cwd); - Timeout::inSeconds(1, "recruiter to be up") + Timeout::inSeconds(1, 'recruiter to be up') ->until(function () use ($process) { $status = proc_get_status($process); + return $status['running']; - }); + }) + ; $this->processRecruiter = [$process, $pipes, 'recruiter']; + return $this->processRecruiter; } @@ -120,11 +125,13 @@ protected function startCleaner(): array ]; $cwd = __DIR__ . '/../../../'; $process = proc_open('exec php bin/recruiter start:cleaner --wait-at-least=5s --wait-at-most=1m --lease-time 20s >> /tmp/cleaner.log 2>&1', $descriptors, $pipes, $cwd); - Timeout::inSeconds(1, "cleaner to be up") + Timeout::inSeconds(1, 'cleaner to be up') ->until(function () use ($process) { $status = proc_get_status($process); + return $status['running']; - }); + }) + ; $this->processCleaner = [$process, $pipes, 'cleaner']; return $this->processCleaner; @@ -147,62 +154,68 @@ protected function startWorker(array $additionalOptions = []) $cwd = __DIR__ . '/../../../'; $process = proc_open("exec php bin/recruiter start:worker $options >> /tmp/worker.log 2>&1", $descriptors, $pipes, $cwd); - Timeout::inSeconds(1, "worker to be up") + Timeout::inSeconds(1, 'worker to be up') ->until(function () use ($process) { $status = proc_get_status($process); + return $status['running']; - }); + }) + ; // proc_get_status($process); $this->processWorkers[] = [$process, $pipes, 'worker']; + return end($this->processWorkers); } protected function stopProcessWithSignal(array $processAndPipes, $signal): void { - list($process, $pipes, $name) = $processAndPipes; + [$process, $pipes, $name] = $processAndPipes; proc_terminate($process, $signal); $this->lastStatus = proc_get_status($process); - Timeout - ::inSeconds(30, function () use ($signal) { - return 'termination of process: ' . var_export($this->lastStatus, true) . " after sending the `$signal` signal to it"; - }) + Timeout::inSeconds(30, function () use ($signal) { + return 'termination of process: ' . var_export($this->lastStatus, true) . " after sending the `$signal` signal to it"; + }) ->until(function () use ($process) { $this->lastStatus = proc_get_status($process); - return $this->lastStatus['running'] == false; - }); + + return false == $this->lastStatus['running']; + }) + ; } /** - * @param integer $duration milliseconds + * @param int $duration milliseconds */ protected function enqueueJob($duration = 10, $tag = 'generic'): void { - $workable = ShellCommand::fromCommandLine("sleep " . ($duration / 1000)); + $workable = ShellCommand::fromCommandLine('sleep ' . ($duration / 1000)); $workable ->asJobOf($this->recruiter) ->inGroup($tag) ->inBackground() - ->execute(); - $this->jobs++; + ->execute() + ; + ++$this->jobs; } protected function enqueueJobWithRetryPolicy(int $duration, RetryPolicy $retryPolicy): void { - $workable = ShellCommand::fromCommandLine("sleep " . ($duration / 1000)); + $workable = ShellCommand::fromCommandLine('sleep ' . ($duration / 1000)); $workable ->asJobOf($this->recruiter) ->retryWithPolicy($retryPolicy) ->inBackground() - ->execute(); - $this->jobs++; + ->execute() + ; + ++$this->jobs; } protected function start(int $workers): void { $this->startRecruiter(); $this->startCleaner(); - for ($i = 0; $i < $workers; $i++) { + for ($i = 0; $i < $workers; ++$i) { $this->startWorker(); } } @@ -252,12 +265,13 @@ protected function files(): string $logs = ''; if (getenv('TEST_DUMP')) { foreach ($this->files as $file) { - $logs .= $file. ":". PHP_EOL; + $logs .= $file . ':' . PHP_EOL; $logs .= file_get_contents($file); } } else { $logs .= var_export($this->files, true); } + return $logs; } @@ -267,7 +281,7 @@ private function optionsToString(array $options = []): string foreach ($options as $option => $value) { $optionsString .= " --$option=$value"; - }; + } return $optionsString; } diff --git a/spec/Recruiter/Acceptance/EnduranceTest.php b/spec/Recruiter/Acceptance/EnduranceTest.php index 66d74e72..a0406630 100644 --- a/spec/Recruiter/Acceptance/EnduranceTest.php +++ b/spec/Recruiter/Acceptance/EnduranceTest.php @@ -1,12 +1,12 @@ hook(Listener\log('/tmp/recruiter-test-iterations.log')) ->hook(Listener\collectFrequencies()) ->disableShrinking() ->then(function ($tuple): void { - list ($workers, $actions) = $tuple; + [$workers, $actions] = $tuple; $this->clean(); $this->start($workers); foreach ($actions as $action) { @@ -87,7 +88,7 @@ function ($milliseconds) { $method = array_shift($arguments); call_user_func_array( [$this, $method], - $arguments + $arguments, ); } else { $this->$action(); @@ -98,12 +99,13 @@ function ($milliseconds) { Timeout::inSeconds( $estimatedTime, function () { - return "all $this->jobs jobs to be performed. Now is " . date('c') . " Logs: " . $this->files(); - } + return "all $this->jobs jobs to be performed. Now is " . date('c') . ' Logs: ' . $this->files(); + }, ) ->until(function () { return $this->jobRepository->countArchived() === $this->jobs; - }); + }) + ; $at = T\now(); $statistics = $this->recruiter->statistics($tag = null, $at); @@ -118,7 +120,8 @@ function () { } // TODO: add tolerance $this->assertEquals($statistics['throughput']['value'], $cumulativeThroughput); - }); + }) + ; } private function logAction($action) @@ -126,11 +129,11 @@ private function logAction($action) file_put_contents( $this->actionLog, sprintf( - "[ACTIONS][PHPUNIT][%s] %s" . PHP_EOL, + '[ACTIONS][PHPUNIT][%s] %s' . PHP_EOL, date('c'), - json_encode($action) + json_encode($action), ), - FILE_APPEND + FILE_APPEND, ); } diff --git a/spec/Recruiter/Acceptance/FaultToleranceTest.php b/spec/Recruiter/Acceptance/FaultToleranceTest.php index 9e2daaa8..abe2bcfd 100644 --- a/spec/Recruiter/Acceptance/FaultToleranceTest.php +++ b/spec/Recruiter/Acceptance/FaultToleranceTest.php @@ -1,11 +1,11 @@ recruiter->hire($memoryLimit); $this->recruiter->bookJobsForWorkers(); $this->recruiter->rollbackLockedJobs(); - list ($assignments, $totalNumber) = $this->recruiter->assignJobsToWorkers(); + [$assignments, $totalNumber] = $this->recruiter->assignJobsToWorkers(); $this->assertEquals(1, count($assignments)); $this->assertEquals(1, $totalNumber); } @@ -28,30 +28,31 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDiesInConstructor() ->asJobOf($this->recruiter) ->inBackground() ->retryWithPolicy(RetryManyTimes::forTimes(1, 0)) - ->execute(); + ->execute() + ; $worker = $this->startWorker(); $this->waitForNumberOfWorkersToBe(1); - list ($assignments, $_) = $this->recruiter->assignJobsToWorkers(); + [$assignments, $_] = $this->recruiter->assignJobsToWorkers(); $this->assertEquals(1, count($assignments)); sleep(2); $jobDocument = current($this->scheduled->find()->toArray()); $this->assertEquals(1, $jobDocument['attempts']); - $this->assertEquals('Recruiter\\Workable\\FailsInConstructor', $jobDocument['workable']['class']); + $this->assertEquals('Recruiter\Workable\FailsInConstructor', $jobDocument['workable']['class']); $this->assertStringContainsString('This job failed while instantiating a workable', $jobDocument['last_execution']['message']); $this->assertStringContainsString('I am supposed to fail in constructor code for testing purpose', $jobDocument['last_execution']['message']); - list ($assignments, $_) = $this->recruiter->assignJobsToWorkers(); + [$assignments, $_] = $this->recruiter->assignJobsToWorkers(); $this->assertEquals(1, count($assignments)); sleep(2); $jobDocument = current($this->archived->find()->toArray()); $this->assertEquals(2, $jobDocument['attempts']); - $this->assertEquals('Recruiter\\Workable\\FailsInConstructor', $jobDocument['workable']['class']); + $this->assertEquals('Recruiter\Workable\FailsInConstructor', $jobDocument['workable']['class']); $this->assertStringContainsString('This job failed while instantiating a workable', $jobDocument['last_execution']['message']); $this->assertStringContainsString('I am supposed to fail in constructor code for testing purpose', $jobDocument['last_execution']['message']); - list ($assignments, $_) = $this->recruiter->assignJobsToWorkers(); + [$assignments, $_] = $this->recruiter->assignJobsToWorkers(); $this->assertEquals(0, count($assignments)); } @@ -66,7 +67,8 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDies() ->asJobOf($this->recruiter) ->inBackground() ->retryWithPolicy(RetryManyTimes::forTimes(1, 0)) - ->execute(); + ->execute() + ; // Right now we recover for dead jobs when we // Recruiter::retireDeadWorkers and when we @@ -78,7 +80,7 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDies() // First execution of the job $worker = $this->startWorker(); $this->waitForNumberOfWorkersToBe(1); - list ($assignments, $_) = $this->recruiter->assignJobsToWorkers(); + [$assignments, $_] = $this->recruiter->assignJobsToWorkers(); $this->assertEquals(1, count($assignments)); sleep(2); // The worker is dead and the job is not properly scheduled @@ -89,7 +91,7 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDies() $worker = $this->startWorker(); $this->waitForNumberOfWorkersToBe(1); // Here the job is assigned and rescheduled by the retry policy because found crashed - list ($assignments, $_) = $this->recruiter->assignJobsToWorkers(); + [$assignments, $_] = $this->recruiter->assignJobsToWorkers(); $this->assertEquals(1, count($assignments)); sleep(2); // The worker is dead and the job is not properly scheduled @@ -103,7 +105,7 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDies() // Here the job is assigned and archived by the retry policy // because found crashed and because it has been already // executed 2 times - list ($assignments, $_) = $this->recruiter->assignJobsToWorkers(); + [$assignments, $_] = $this->recruiter->assignJobsToWorkers(); $this->assertEquals(1, count($assignments)); sleep(1); // The worker is not dead and the job is not scheduled anymore diff --git a/spec/Recruiter/Acceptance/HooksTest.php b/spec/Recruiter/Acceptance/HooksTest.php index 69db7974..07b03d30 100644 --- a/spec/Recruiter/Acceptance/HooksTest.php +++ b/spec/Recruiter/Acceptance/HooksTest.php @@ -1,16 +1,18 @@ memoryLimit = new MemoryLimit('64MB'); @@ -26,13 +28,15 @@ public function testAfterFailureWithoutRetryEventIsFired() 'job.failure.last', function (Event $event): void { $this->events[] = $event; - } - ); + }, + ) + ; $job = (new AlwaysFail()) ->asJobOf($this->recruiter) ->inBackground() - ->execute(); + ->execute() + ; $worker = $this->recruiter->hire($this->memoryLimit); $this->recruiter->assignJobsToWorkers(); @@ -52,21 +56,23 @@ public function testAfterLastFailureEventIsFired() 'job.failure.last', function (Event $event): void { $this->events[] = $event; - } - ); + }, + ) + ; $job = (new AlwaysFail()) ->asJobOf($this->recruiter) ->retryWithPolicy(RetryManyTimes::forTimes(1, 0)) ->inBackground() - ->execute(); + ->execute() + ; $runAJob = function ($howManyTimes, $worker): void { for ($i = 0; $i < $howManyTimes;) { - list($_, $assigned) = $this->recruiter->assignJobsToWorkers(); + [$_, $assigned] = $this->recruiter->assignJobsToWorkers(); $worker->work(); if ($assigned > 0) { - $i++; + ++$i; } } }; @@ -88,13 +94,15 @@ public function testJobStartedIsFired() 'job.started', function (Event $event): void { $this->events[] = $event; - } - ); + }, + ) + ; $job = (new AlwaysSucceed()) ->asJobOf($this->recruiter) ->inBackground() - ->execute(); + ->execute() + ; $worker = $this->recruiter->hire($this->memoryLimit); $this->recruiter->assignJobsToWorkers(); @@ -113,18 +121,21 @@ public function testJobEndedIsFired() 'job.ended', function (Event $event): void { $this->events[] = $event; - } - ); + }, + ) + ; (new AlwaysSucceed()) ->asJobOf($this->recruiter) ->inBackground() - ->execute(); + ->execute() + ; (new AlwaysFail()) ->asJobOf($this->recruiter) ->inBackground() - ->execute(); + ->execute() + ; $worker = $this->recruiter->hire($this->memoryLimit); $this->recruiter->assignJobsToWorkers(); diff --git a/spec/Recruiter/Acceptance/RepeatableJobsAreScheduledTest.php b/spec/Recruiter/Acceptance/RepeatableJobsAreScheduledTest.php index a9c6ab34..e847ea58 100644 --- a/spec/Recruiter/Acceptance/RepeatableJobsAreScheduledTest.php +++ b/spec/Recruiter/Acceptance/RepeatableJobsAreScheduledTest.php @@ -1,14 +1,13 @@ 0, 'group' => 'generic', 'workable' => [ - 'class' => 'Recruiter\\Workable\\SampleRepeatableCommand', + 'class' => 'Recruiter\Workable\SampleRepeatableCommand', 'parameters' => [], 'method' => 'execute', ], @@ -48,12 +47,12 @@ public function testARepeatableJobIsScheduledAtExpectedScheduledTime() 'executions' => 1, ], 'retry_policy' => [ - 'class' => 'Recruiter\\RetryPolicy\\ExponentialBackoff', + 'class' => 'Recruiter\RetryPolicy\ExponentialBackoff', 'parameters' => [ 'retry_how_many_times' => 2, 'seconds_to_initially_wait_before_retry' => 5, ], - ] + ], ], $jobData); } @@ -72,7 +71,7 @@ public function testOnlyASingleJobAreScheduledForTheSameSchedulingTime() $this->assertEquals( T\MongoDate::from(Moment::fromTimestamp($expectedScheduleDate)), - $jobs[0]->export()['scheduled_at'] + $jobs[0]->export()['scheduled_at'], ); } @@ -134,8 +133,8 @@ public function testSchedulersAreUniqueOnUrn() private function IHaveAScheduleWithALongStory(string $urn, $attempts) { $scheduleTimes = []; - for ($i = 1; $i <= $attempts; $i++) { - $scheduleTimes[] = strtotime("2018-05-" . $i . "T15:00:00"); + for ($i = 1; $i <= $attempts; ++$i) { + $scheduleTimes[] = strtotime('2018-05-' . $i . 'T15:00:00'); } $schedulePolicy = new FixedSchedulePolicy($scheduleTimes); @@ -171,12 +170,14 @@ private function recruiterScheduleJobsNTimes(int $nth = 1): void private function fetchScheduledJobs() { $jobsRepository = new JobsRepository($this->recruiterDb); + return $jobsRepository->all(); } private function fetchSchedulers() { $schedulersRepository = new SchedulersRepository($this->recruiterDb); + return $schedulersRepository->all(); } } diff --git a/spec/Recruiter/Acceptance/SyncronousExecutionTest.php b/spec/Recruiter/Acceptance/SyncronousExecutionTest.php index 6161ff16..34f3dde6 100644 --- a/spec/Recruiter/Acceptance/SyncronousExecutionTest.php +++ b/spec/Recruiter/Acceptance/SyncronousExecutionTest.php @@ -1,4 +1,5 @@ asJobOf($this->recruiter) ->inBackground() - ->execute(); + ->execute() + ; $report = $this->recruiter->flushJobsSynchronously(); @@ -40,7 +42,8 @@ private function enqueueAnAnswerJob($answer, $scheduledAt) ->asJobOf($this->recruiter) ->scheduleAt($scheduledAt) ->inBackground() - ->execute(); + ->execute() + ; } } diff --git a/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWhenAMemoryLeakOccurs.php b/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWhenAMemoryLeakOccurs.php index a48487bb..0ab1fb42 100644 --- a/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWhenAMemoryLeakOccurs.php +++ b/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWhenAMemoryLeakOccurs.php @@ -10,6 +10,7 @@ class WorkerGuaranteedToExitWhenAMemoryLeakOccurs extends BaseAcceptanceTestCase { /** * @group acceptance + * * @dataProvider provideMemoryConsumptions */ public function testWorkerKillItselfAfterAMemoryLeakButNotAfterABigMemoryConsumptionWithoutLeak($withMemoryLeak, $howManyItems, $memoryLimit, $expectedWorkerAlive) @@ -20,7 +21,8 @@ public function testWorkerKillItselfAfterAMemoryLeakButNotAfterABigMemoryConsump ])) ->asJobOf($this->recruiter) ->inBackground() - ->execute(); + ->execute() + ; $this->startRecruiter(); @@ -35,8 +37,10 @@ public function testWorkerKillItselfAfterAMemoryLeakButNotAfterABigMemoryConsump ->until(function () { $at = T\now(); $statistics = $this->recruiter->statistics($tag = null, $at); - return $statistics['jobs']['queued'] == 0; - }); + + return 0 == $statistics['jobs']['queued']; + }) + ; $numberOfWorkersCurrently = $this->numberOfWorkers(); @@ -49,14 +53,14 @@ public function testWorkerKillItselfAfterAMemoryLeakButNotAfterABigMemoryConsump $this->assertEquals( $numberOfExpectedWorkers, $numberOfWorkersCurrently, - "The number of workers before was $numberOfWorkersBefore and now after starting 1 and execute a job we have $numberOfWorkersCurrently" + "The number of workers before was $numberOfWorkersBefore and now after starting 1 and execute a job we have $numberOfWorkersCurrently", ); } public static function provideMemoryConsumptions() { return [ - //legend: [$withMemoryLeak, $howManyItems, $memoryLimit, $expectedWorkerAlive], + // legend: [$withMemoryLeak, $howManyItems, $memoryLimit, $expectedWorkerAlive], [false, 2000000, '20MB', true], [true, 2000000, '20MB', false], [true, 2000000, '128MB', true], diff --git a/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest.php b/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest.php index fcb0feb9..3d11950e 100644 --- a/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest.php +++ b/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest.php @@ -2,7 +2,6 @@ namespace Recruiter\Acceptance; -use Recruiter\Workable\FactoryMethodCommand; use Recruiter\Workable\ThrowsFatalError; class WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest extends BaseAcceptanceTestCase @@ -21,7 +20,7 @@ public function testInCaseOfExceptionTheExitCodeOfWorkerProcessIsNotZero() $worker = $this->startWorker(); $workerProcess = $worker[0]; $this->waitForNumberOfWorkersToBe(1); - list ($assignments, $_) = $this->recruiter->assignJobsToWorkers(); + [$assignments, $_] = $this->recruiter->assignJobsToWorkers(); $this->assertEquals(1, count($assignments)); $this->waitForNumberOfWorkersToBe(0, $seconds = 10); diff --git a/spec/Recruiter/Acceptance/WorkerGuaranteedToRetireAfterDeathTest.php b/spec/Recruiter/Acceptance/WorkerGuaranteedToRetireAfterDeathTest.php index 68d18a71..83996c13 100644 --- a/spec/Recruiter/Acceptance/WorkerGuaranteedToRetireAfterDeathTest.php +++ b/spec/Recruiter/Acceptance/WorkerGuaranteedToRetireAfterDeathTest.php @@ -17,7 +17,7 @@ public function testRetireAfterAskedToStop() $this->assertEquals( $numberOfWorkersBefore, $numberOfWorkersCurrently, - "The number of workers before was $numberOfWorkersBefore and now after starting and stopping 1 we have $numberOfWorkersCurrently" + "The number of workers before was $numberOfWorkersBefore and now after starting and stopping 1 we have $numberOfWorkersCurrently", ); } } diff --git a/spec/Recruiter/Acceptance/WorkerRepositoryTest.php b/spec/Recruiter/Acceptance/WorkerRepositoryTest.php index 85634cbd..cb1949d0 100644 --- a/spec/Recruiter/Acceptance/WorkerRepositoryTest.php +++ b/spec/Recruiter/Acceptance/WorkerRepositoryTest.php @@ -3,17 +3,17 @@ namespace Recruiter\Acceptance; use Recruiter\Worker\Repository; -use Recruiter\Recruiter; class WorkerRepositoryTest extends BaseAcceptanceTestCase { private Repository $repository; + protected function setUp(): void { parent::setUp(); $this->repository = new Repository( $this->recruiterDb, - $this->recruiter + $this->recruiter, ); } diff --git a/spec/Recruiter/CleanerTest.php b/spec/Recruiter/CleanerTest.php index 295875a0..d1d99c4c 100644 --- a/spec/Recruiter/CleanerTest.php +++ b/spec/Recruiter/CleanerTest.php @@ -2,12 +2,11 @@ namespace Recruiter; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; +use Timeless as T; use Timeless\Interval; -use Timeless\Clock; use Timeless\Moment; -use Timeless as T; class CleanerTest extends TestCase { @@ -25,7 +24,8 @@ protected function setUp(): void $this->jobRepository = $this ->getMockBuilder('Recruiter\Job\Repository') ->disableOriginalConstructor() - ->getMock(); + ->getMock() + ; $this->cleaner = new Cleaner($this->jobRepository); @@ -50,11 +50,12 @@ public function testDelegatesTheCleanupOfArchivedJobsToTheJobsRepository() ->expects($this->once()) ->method('cleanArchived') ->with($expectedUpperLimit) - ->will($this->returnValue($jobsCleaned = 10)); + ->will($this->returnValue($jobsCleaned = 10)) + ; $this->assertEquals( $jobsCleaned, - $this->cleaner->cleanArchived($this->interval) + $this->cleaner->cleanArchived($this->interval), ); } } diff --git a/spec/Recruiter/FactoryTest.php b/spec/Recruiter/FactoryTest.php index ba94ff80..b59ac993 100644 --- a/spec/Recruiter/FactoryTest.php +++ b/spec/Recruiter/FactoryTest.php @@ -21,7 +21,7 @@ public function testShouldCreateAMongoDatabaseConnection() { $this->assertInstanceOf( 'MongoDB\Database', - $this->creationOfDefaultMongoDb() + $this->creationOfDefaultMongoDb(), ); } @@ -38,7 +38,7 @@ public function testShouldOverwriteTheWriteConcernPassedInTheOptions() [ 'connectTimeoutMS' => 1000, 'w' => '0', - ] + ], ); $this->assertEquals('majority', $mongoDb->getWriteConcern()->getW()); @@ -51,7 +51,7 @@ private function creationOfDefaultMongoDb(): Database [ 'connectTimeoutMS' => 1000, 'w' => '0', - ] + ], ); } } diff --git a/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php b/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php index 7998d92d..a89b6c6c 100644 --- a/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php +++ b/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php @@ -2,9 +2,8 @@ namespace Recruiter; -use Exception; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; use Recruiter\Job\Repository; use Symfony\Component\EventDispatcher\EventDispatcherInterface; @@ -22,7 +21,8 @@ protected function setUp(): void $this->repository = $this ->getMockBuilder(Repository::class) ->disableOriginalConstructor() - ->getMock(); + ->getMock() + ; $this->dispatcher = $this->createMock(EventDispatcherInterface::class); $this->listener = new ListenerSpy(); @@ -52,7 +52,6 @@ public function testFinalizableFailureMethodsAreCalledWhenJobFails(): void $this->assertSame($exception, $calls[2][1]); } - public function testFinalizableSuccessfullMethodsAreCalledWhenJobIsDone() { $workable = new FinalizableWorkable(function () { @@ -98,6 +97,7 @@ public function __construct(callable $whatToDo, $listener) public function execute() { $whatToDo = $this->whatToDo; + return $whatToDo(); } @@ -106,17 +106,17 @@ public function afterSuccess() $this->listener->methodWasCalled(__FUNCTION__); } - public function afterFailure(Exception $e) + public function afterFailure(\Exception $e) { $this->listener->methodWasCalled(__FUNCTION__, $e); } - public function afterLastFailure(Exception $e) + public function afterLastFailure(\Exception $e) { $this->listener->methodWasCalled(__FUNCTION__, $e); } - public function finalize(?Exception $e = null) + public function finalize(?\Exception $e = null) { $this->listener->methodWasCalled(__FUNCTION__, $e); } diff --git a/spec/Recruiter/Infrastructure/Memory/MemoryLimitTest.php b/spec/Recruiter/Infrastructure/Memory/MemoryLimitTest.php index d552d154..75507e1d 100644 --- a/spec/Recruiter/Infrastructure/Memory/MemoryLimitTest.php +++ b/spec/Recruiter/Infrastructure/Memory/MemoryLimitTest.php @@ -1,4 +1,5 @@ 'generic', - 'tags' =>[ + 'tags' => [ 1 => 'billing-notification', ], ]); @@ -21,7 +22,7 @@ public function testHasTagReturnsFalseWhenTheExportedJobDoesNotContainTheTag() { $event = new Event([ 'group' => 'generic', - 'tags' =>[ + 'tags' => [ 1 => 'billing-notification', ], ]); diff --git a/spec/Recruiter/Job/RepositoryTest.php b/spec/Recruiter/Job/RepositoryTest.php index 772da5d4..7b6aacb3 100644 --- a/spec/Recruiter/Job/RepositoryTest.php +++ b/spec/Recruiter/Job/RepositoryTest.php @@ -1,4 +1,5 @@ repository->queued( 'generic', T\now(), - T\now()->before(T\hour(24)) - ) + T\now()->before(T\hour(24)), + ), ); } @@ -94,7 +94,7 @@ public function testRecentHistory() [ 'throughput' => [ 'value' => 3.0, - 'value_per_second' => 3/60.0, + 'value_per_second' => 3 / 60.0, ], 'latency' => [ 'average' => 5.0, @@ -103,7 +103,7 @@ public function testRecentHistory() 'average' => 0.0, ], ], - $this->repository->recentHistory() + $this->repository->recentHistory(), ); } @@ -115,12 +115,14 @@ public function testCountQueuedJobsGroupingByASpecificKeyword() $workable1 ->expects($this->any()) ->method('export') - ->will($this->returnValue(['seller' => 'seller1'])); + ->will($this->returnValue(['seller' => 'seller1'])) + ; $workable2 ->expects($this->any()) ->method('export') - ->will($this->returnValue(['seller' => 'seller2'])); + ->will($this->returnValue(['seller' => 'seller2'])) + ; $job1 = $this->aJob($workable1); $job2 = $this->aJob($workable2); @@ -134,32 +136,32 @@ public function testCountQueuedJobsGroupingByASpecificKeyword() 'seller1' => '1', 'seller2' => '2', ], - $this->repository->queuedGroupedBy('workable.parameters.seller', []) + $this->repository->queuedGroupedBy('workable.parameters.seller', []), ); } public function testGetDelayedScheduledJobs() { $workable1 = $this->workableMockWithCustomParameters([ - 'job1' => 'delayed_and_unpicked' + 'job1' => 'delayed_and_unpicked', ]); $workable2 = $this->workableMockWithCustomParameters([ - 'job2' => 'delayed_and_unpicked' + 'job2' => 'delayed_and_unpicked', ]); $workable3 = $this->workableMockWithCustomParameters([ - 'job3' => 'in_schedulation' + 'job3' => 'in_schedulation', ]); $this->aJobToSchedule($this->aJob($workable1))->inBackground()->execute(); $this->aJobToSchedule($this->aJob($workable2))->inBackground()->execute(); $lowerLimit = $this->clock->now(); - $fiveHoursInSeconds = 5*60*60; + $fiveHoursInSeconds = 5 * 60 * 60; $this->clock->driftForwardBySeconds($fiveHoursInSeconds); $this->aJobToSchedule($this->aJob($workable3))->inBackground()->execute(); $jobs = $this->repository->delayedScheduledJobs($lowerLimit); $jobsFounds = 0; foreach ($jobs as $job) { $this->assertEquals('delayed_and_unpicked', reset($job->export()['workable']['parameters'])); - $jobsFounds++; + ++$jobsFounds; } $this->assertEquals(2, $jobsFounds); } @@ -169,7 +171,7 @@ public function testCountDelayedScheduledJobs() $this->aJobToSchedule($this->aJob())->inBackground()->execute(); $this->aJobToSchedule($this->aJob())->inBackground()->execute(); $lowerLimit = $this->clock->now(); - $twoHoursInSeconds = 2*60*60; + $twoHoursInSeconds = 2 * 60 * 60; $this->clock->driftForwardBySeconds($twoHoursInSeconds); $this->aJobToSchedule($this->aJob())->inBackground()->execute(); $this->assertEquals(2, $this->repository->countDelayedScheduledJobs($lowerLimit)); @@ -180,19 +182,19 @@ public function testCountRecentJobsWithManyAttempts() $ed = $this->eventDispatcher; $this->repository->archive($this->aJob()->beforeExecution($ed)->beforeExecution($ed)->afterExecution(42, $ed)); $this->clock->now(); - $threeHoursInSeconds = 3*60*60; + $threeHoursInSeconds = 3 * 60 * 60; $this->clock->driftForwardBySeconds($threeHoursInSeconds); $lowerLimit = $this->clock->now(); $this->repository->archive($this->aJob()->beforeExecution($ed)->beforeExecution($ed)->afterExecution(42, $ed)); $this->repository->archive($this->aJob()->beforeExecution($ed)->beforeExecution($ed)->afterExecution(42, $ed)); - $oneHourInSeconds = 60*60; + $oneHourInSeconds = 60 * 60; $this->clock->driftForwardBySeconds($oneHourInSeconds); $createdAt = $endedAt = $this->clock->now(); $this->repository->save($this->jobMockWithAttemptsAndCustomParameters($createdAt, $endedAt)); $this->repository->save($this->jobMockWithAttemptsAndCustomParameters($createdAt, $endedAt)); $this->aJobToSchedule($this->aJob())->inBackground()->execute(); $upperLimit = $this->clock->now(); - $oneHourInSeconds = 60*60; + $oneHourInSeconds = 60 * 60; $this->clock->driftForwardBySeconds($oneHourInSeconds); $createdAt = $endedAt = $this->clock->now(); $this->repository->archive($this->aJob()->beforeExecution($ed)->beforeExecution($ed)->afterExecution(42, $ed)); @@ -204,31 +206,31 @@ public function testGetRecentJobsWithManyAttempts() { $ed = $this->eventDispatcher; $workable1 = $this->workableMockWithCustomParameters([ - 'job1' => 'many_attempts_and_archived_but_too_old' + 'job1' => 'many_attempts_and_archived_but_too_old', ]); $workable2 = $this->workableMockWithCustomParameters([ - 'job2' => 'many_attempts_and_archived' + 'job2' => 'many_attempts_and_archived', ]); $workable3 = $this->workableMockWithCustomParameters([ - 'job3' => 'many_attempts_and_archived' + 'job3' => 'many_attempts_and_archived', ]); - $workable4 = [ - 'job4' => 'many_attempts_and_scheduled' + $workable4 = [ + 'job4' => 'many_attempts_and_scheduled', ]; - $workable5 = [ - 'job5' => 'many_attempts_and_scheduled' + $workable5 = [ + 'job5' => 'many_attempts_and_scheduled', ]; $workable6 = $this->workableMockWithCustomParameters([ - 'job6' => 'one_attempt_and_scheduled' + 'job6' => 'one_attempt_and_scheduled', ]); $this->repository->archive($this->aJob($workable1)->beforeExecution($ed)->beforeExecution($ed)->afterExecution(42, $ed)); $this->clock->now(); - $threeHoursInSeconds = 3*60*60; + $threeHoursInSeconds = 3 * 60 * 60; $this->clock->driftForwardBySeconds($threeHoursInSeconds); $lowerLimit = $this->clock->now(); $this->repository->archive($this->aJob($workable2)->beforeExecution($ed)->beforeExecution($ed)->afterExecution(42, $ed)); $this->repository->archive($this->aJob($workable3)->beforeExecution($ed)->beforeExecution($ed)->afterExecution(42, $ed)); - $oneHourInSeconds = 60*60; + $oneHourInSeconds = 60 * 60; $this->clock->driftForwardBySeconds($oneHourInSeconds); $createdAt = $endedAt = $this->clock->now(); $this->repository->save($this->jobMockWithAttemptsAndCustomParameters($createdAt, $endedAt, $workable4)); @@ -240,9 +242,9 @@ public function testGetRecentJobsWithManyAttempts() foreach ($jobs as $job) { $this->assertMatchesRegularExpression( '/many_attempts_and_archived|many_attempts_and_scheduled/', - reset($job->export()['workable']['parameters']) + reset($job->export()['workable']['parameters']), ); - $jobsFounds++; + ++$jobsFounds; } $this->assertEquals(4, $jobsFounds); } @@ -255,20 +257,20 @@ public function testCountSlowRecentJobs() $this->repository->save( $this->jobMockWithAttemptsAndCustomParameters( $createdAt, - $endedAt->after(Interval::parse($elapseTimeInSecondsBeforeJobsExecutionEnd . ' s')) - ) + $endedAt->after(Interval::parse($elapseTimeInSecondsBeforeJobsExecutionEnd . ' s')), + ), ); $archivedJobSlowExpired = $this->aJob()->beforeExecution($ed); $this->clock->driftForwardBySeconds($elapseTimeInSecondsBeforeJobsExecutionEnd); $archivedJobSlowExpired->afterExecution(42, $ed); - $threeHoursInSeconds = 3*60*60; + $threeHoursInSeconds = 3 * 60 * 60; $this->clock->driftForwardBySeconds($threeHoursInSeconds); $lowerLimit = $createdAt = $endedAt = $this->clock->now(); $this->repository->save( $this->jobMockWithAttemptsAndCustomParameters( $createdAt, - $endedAt->after(Interval::parse($elapseTimeInSecondsBeforeJobsExecutionEnd . ' s')) - ) + $endedAt->after(Interval::parse($elapseTimeInSecondsBeforeJobsExecutionEnd . ' s')), + ), ); $archivedJobSlow1 = $this->aJob()->beforeExecution($ed); $this->clock->driftForwardBySeconds($elapseTimeInSecondsBeforeJobsExecutionEnd); @@ -278,7 +280,7 @@ public function testCountSlowRecentJobs() $this->clock->driftForwardBySeconds($elapseTimeInSecondsBeforeJobsExecutionEnd); $archivedJobSlow2->afterExecution(42, $ed); $this->repository->archive($archivedJobSlow2); - $oneHourInSeconds = 60*60; + $oneHourInSeconds = 60 * 60; $this->clock->driftForwardBySeconds($oneHourInSeconds); $createdAt = $endedAt = $this->clock->now(); $archivedJobNotSlow = $this->aJob()->beforeExecution($ed)->afterExecution(42, $ed); @@ -286,26 +288,26 @@ public function testCountSlowRecentJobs() $this->repository->save( $this->jobMockWithAttemptsAndCustomParameters( $createdAt, - $endedAt->after(Interval::parse($elapseTimeInSecondsBeforeJobsExecutionEnd . ' s')) - ) + $endedAt->after(Interval::parse($elapseTimeInSecondsBeforeJobsExecutionEnd . ' s')), + ), ); - $oneHourInSeconds = 60*60; + $oneHourInSeconds = 60 * 60; $this->clock->driftForwardBySeconds($oneHourInSeconds); $upperLimit = $createdAt = $endedAt = $this->clock->now(); $this->repository->save( $this->jobMockWithAttemptsAndCustomParameters( $createdAt, - $endedAt - ) + $endedAt, + ), ); - $oneHourInSeconds = 60*60; + $oneHourInSeconds = 60 * 60; $this->clock->driftForwardBySeconds($oneHourInSeconds); $createdAt = $endedAt = $this->clock->now(); $this->repository->save( $this->jobMockWithAttemptsAndCustomParameters( $createdAt, - $endedAt->after(Interval::parse($elapseTimeInSecondsBeforeJobsExecutionEnd . ' s')) - ) + $endedAt->after(Interval::parse($elapseTimeInSecondsBeforeJobsExecutionEnd . ' s')), + ), ); $this->assertEquals(4, $this->repository->countSlowRecentJobs($lowerLimit, $upperLimit)); } @@ -319,76 +321,76 @@ public function testGetSlowRecentJobs() $this->jobMockWithAttemptsAndCustomParameters( $createdAt, $endedAt->after(Interval::parse($elapseTimeInSecondsBeforeJobsExecutionEnd . ' s')), - ['job_scheduled_old' => 'slow_jobs_scheduled_but_too_old'] - ) + ['job_scheduled_old' => 'slow_jobs_scheduled_but_too_old'], + ), ); $archivedJobSlowExpired = $this->aJob($this->workableMockWithCustomParameters([ - 'job_archived_old' => 'slow_job_archived_but_too_old' - ]))->beforeExecution($ed); + 'job_archived_old' => 'slow_job_archived_but_too_old', + ]))->beforeExecution($ed); $this->clock->driftForwardBySeconds($elapseTimeInSecondsBeforeJobsExecutionEnd); $archivedJobSlowExpired->afterExecution(42, $ed); - $threeHoursInSeconds = 3*60*60; + $threeHoursInSeconds = 3 * 60 * 60; $this->clock->driftForwardBySeconds($threeHoursInSeconds); $lowerLimit = $createdAt = $endedAt = $this->clock->now(); $this->repository->save( $this->jobMockWithAttemptsAndCustomParameters( $createdAt, $endedAt->after(Interval::parse($elapseTimeInSecondsBeforeJobsExecutionEnd . ' s')), - ['job1_scheduled' => 'slow_job_recent_scheduled'] - ) + ['job1_scheduled' => 'slow_job_recent_scheduled'], + ), ); $archivedJobSlow1 = $this->aJob($this->workableMockWithCustomParameters([ - 'job1_archived' => 'slow_job_recent_archived' - ]))->beforeExecution($ed); + 'job1_archived' => 'slow_job_recent_archived', + ]))->beforeExecution($ed); $archivedJobSlow2 = $this->aJob($this->workableMockWithCustomParameters([ - 'job2_archived' => 'slow_job_recent_archived' - ]))->beforeExecution($ed); + 'job2_archived' => 'slow_job_recent_archived', + ]))->beforeExecution($ed); $this->clock->driftForwardBySeconds($elapseTimeInSecondsBeforeJobsExecutionEnd); $archivedJobSlow1->afterExecution(41, $ed); $this->repository->archive($archivedJobSlow1); $archivedJobSlow2->afterExecution(42, $ed); $this->repository->archive($archivedJobSlow2); - $oneHourInSeconds = 60*60; + $oneHourInSeconds = 60 * 60; $this->clock->driftForwardBySeconds($oneHourInSeconds); $createdAt = $endedAt = $this->clock->now(); $archivedJobNotSlow = $this->aJob($this->workableMockWithCustomParameters([ - 'job_archived' => 'job_archived_not_slow' - ]))->beforeExecution($ed)->afterExecution(42, $ed); + 'job_archived' => 'job_archived_not_slow', + ]))->beforeExecution($ed)->afterExecution(42, $ed); $this->repository->save( $this->jobMockWithAttemptsAndCustomParameters( $createdAt, $endedAt->after(Interval::parse($elapseTimeInSecondsBeforeJobsExecutionEnd . ' s')), - ['job2_scheduled' => 'slow_job_recent_scheduled'] - ) + ['job2_scheduled' => 'slow_job_recent_scheduled'], + ), ); - $oneHourInSeconds = 60*60; + $oneHourInSeconds = 60 * 60; $this->clock->driftForwardBySeconds($oneHourInSeconds); $upperLimit = $createdAt = $endedAt = $this->clock->now(); $this->repository->save( $this->jobMockWithAttemptsAndCustomParameters( $createdAt, $endedAt, - ['job_scheduled' => 'job_recent_scheduled_slow'] - ) + ['job_scheduled' => 'job_recent_scheduled_slow'], + ), ); - $oneHourInSeconds = 60*60; + $oneHourInSeconds = 60 * 60; $this->clock->driftForwardBySeconds($oneHourInSeconds); $createdAt = $endedAt = $this->clock->now(); $this->repository->save( $this->jobMockWithAttemptsAndCustomParameters( $createdAt, $endedAt->after(Interval::parse($elapseTimeInSecondsBeforeJobsExecutionEnd . ' s')), - ['job3_scheduled' => 'slow_job_recent_scheduled'] - ) + ['job3_scheduled' => 'slow_job_recent_scheduled'], + ), ); $jobs = $this->repository->slowRecentJobs($lowerLimit, $upperLimit); $jobsFounds = 0; foreach ($jobs as $job) { $this->assertMatchesRegularExpression( '/slow_job_recent_archived|slow_job_recent_scheduled/', - reset($job->export()['workable']['parameters']) + reset($job->export()['workable']['parameters']), ); - $jobsFounds++; + ++$jobsFounds; } $this->assertEquals(4, $jobsFounds); } @@ -422,7 +424,8 @@ private function aJob($workable = null) } return Job::around($workable, $this->repository) - ->scheduleAt(T\now()->before(T\seconds(5))); + ->scheduleAt(T\now()->before(T\seconds(5))) + ; } private function aJobToSchedule($job = null) @@ -438,7 +441,8 @@ private function workableMock() { return $this ->getMockBuilder('Recruiter\Workable') - ->getMock(); + ->getMock() + ; } private function workableMockWithCustomParameters($parameters) @@ -447,7 +451,9 @@ private function workableMockWithCustomParameters($parameters) $workable ->expects($this->any()) ->method('export') - ->will($this->returnValue($parameters)); + ->will($this->returnValue($parameters)) + ; + return $workable; } @@ -455,10 +461,12 @@ private function jobExecutionMock($executionParameters) { $jobExecutionMock = $this ->getMockBuilder('Recruiter\JobExecution') - ->getMock(); + ->getMock() + ; $jobExecutionMock->expects($this->once()) ->method('export') - ->will($this->returnValue($executionParameters)); + ->will($this->returnValue($executionParameters)) + ; return $jobExecutionMock; } @@ -466,23 +474,23 @@ private function jobExecutionMock($executionParameters) private function jobMockWithAttemptsAndCustomParameters( ?Moment $createdAt = null, ?Moment $endedAt = null, - ?array $workableParameters = null + ?array $workableParameters = null, ): Job&MockObject { $parameters = [ '_id' => new ObjectId(), 'created_at' => T\MongoDate::from($createdAt), - "done" => false, - "attempts" => 10, - "group" => "generic", - "scheduled_at" => T\MongoDate::from($createdAt), - "last_execution" => [ - "started_at" => T\MongoDate::from($createdAt), - "ended_at" => T\MongoDate::from($endedAt) + 'done' => false, + 'attempts' => 10, + 'group' => 'generic', + 'scheduled_at' => T\MongoDate::from($createdAt), + 'last_execution' => [ + 'started_at' => T\MongoDate::from($createdAt), + 'ended_at' => T\MongoDate::from($endedAt), + ], + 'retry_policy' => [ + 'class' => 'Recruiter\\RetryPolicy\\DoNotDoItAgain', + 'parameters' => [], ], - "retry_policy" => [ - "class" => "Recruiter\\RetryPolicy\\DoNotDoItAgain", - "parameters" => [] - ] ]; if (!empty($workableParameters)) { @@ -493,10 +501,12 @@ private function jobMockWithAttemptsAndCustomParameters( $job = $this ->getMockBuilder(Job::class) ->disableOriginalConstructor() - ->getMock(); + ->getMock() + ; $job->expects($this->once()) ->method('export') ->willReturn($parameters); + return $job; } } diff --git a/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php b/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php index 580d9c9b..a71b8d94 100644 --- a/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php +++ b/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php @@ -2,9 +2,8 @@ namespace Recruiter; -use Exception; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; use Recruiter\Job\Repository; class JobCallCustomMethodOnWorkableTest extends TestCase @@ -18,12 +17,14 @@ protected function setUp(): void $this->workable = $this ->getMockBuilder(Workable::class) ->setMethods(['export', 'import', 'asJobOf', 'send']) - ->getMock(); + ->getMock() + ; $this->repository = $this ->getMockBuilder(Repository::class) ->disableOriginalConstructor() - ->getMock(); + ->getMock() + ; $this->job = Job::around($this->workable, $this->repository); } @@ -37,7 +38,7 @@ public function testConfigureMethodToCallOnWorkable() public function testRaiseExceptionWhenConfigureMethodToCallOnWorkableThatDoNotExists() { - $this->expectException(Exception::class); + $this->expectException(\Exception::class); $this->job->methodToCallOnWorkable('methodThatDoNotExists'); } diff --git a/spec/Recruiter/JobSendEventsToWorkableTest.php b/spec/Recruiter/JobSendEventsToWorkableTest.php index ff778b6d..4bbb0c19 100644 --- a/spec/Recruiter/JobSendEventsToWorkableTest.php +++ b/spec/Recruiter/JobSendEventsToWorkableTest.php @@ -3,8 +3,8 @@ namespace Recruiter; use PHPUnit\Framework\MockObject\Exception; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; use Recruiter\Job\Event; use Recruiter\Job\EventListener; use Recruiter\Job\Repository; @@ -20,7 +20,8 @@ protected function setUp(): void $this->repository = $this ->getMockBuilder(Repository::class) ->disableOriginalConstructor() - ->getMock(); + ->getMock() + ; $this->dispatcher = $this->createMock(EventDispatcherInterface::class); } @@ -37,8 +38,9 @@ public function testTakeRetryPolicyFromRetriableInstance() ->withConsecutive( [$this->equalTo('job.started'), $this->anything()], [$this->equalTo('job.ended'), $this->anything()], - [$this->equalTo('job.failure.last'), $this->anything()] - ); + [$this->equalTo('job.failure.last'), $this->anything()], + ) + ; $workable = new WorkableThatIsAlsoAnEventListener($listener); $job = Job::around($workable, $this->repository); diff --git a/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php b/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php index 84f89dd6..e2939e21 100644 --- a/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php +++ b/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php @@ -3,12 +3,10 @@ namespace Recruiter; use PHPUnit\Framework\MockObject\Exception; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; use Recruiter\Job\Repository; use Recruiter\RetryPolicy\BaseRetryPolicy; -use Timeless as T; -use Recruiter\RetryPolicy; use Symfony\Component\EventDispatcher\EventDispatcherInterface; class JobTakeRetryPolicyFromRetriableWorkableTest extends TestCase @@ -21,7 +19,8 @@ protected function setUp(): void $this->repository = $this ->getMockBuilder(Repository::class) ->disableOriginalConstructor() - ->getMock(); + ->getMock() + ; $this->eventDispatcher = $this->createMock(EventDispatcherInterface::class); } diff --git a/spec/Recruiter/JobTest.php b/spec/Recruiter/JobTest.php index c56a31dc..b419fc29 100644 --- a/spec/Recruiter/JobTest.php +++ b/spec/Recruiter/JobTest.php @@ -1,12 +1,12 @@ repository = $this ->getMockBuilder(Repository::class) ->disableOriginalConstructor() - ->getMock(); + ->getMock() + ; } public function testRetryStatisticsOnFirstExecution() { - $job = Job::around(new AlwaysFail, $this->repository); + $job = Job::around(new AlwaysFail(), $this->repository); $retryStatistics = $job->retryStatistics(); $this->assertIsArray($retryStatistics); $this->assertArrayHasKey('job_id', $retryStatistics); @@ -38,7 +39,7 @@ public function testRetryStatisticsOnFirstExecution() */ public function testRetryStatisticsOnSubsequentExecutions() { - $job = Job::around(new AlwaysFail, $this->repository); + $job = Job::around(new AlwaysFail(), $this->repository); // maybe make the argument optional $job->execute($this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface')); $job = Job::import($job->export(), $this->repository); @@ -53,14 +54,14 @@ public function testRetryStatisticsOnSubsequentExecutions() $this->assertArrayHasKey('message', $lastExecution); $this->assertArrayHasKey('trace', $lastExecution); $this->assertEquals("Sorry, I'm good for nothing", $lastExecution['message']); - $this->assertMatchesRegularExpression("/.*AlwaysFail->execute.*/", $lastExecution['trace']); + $this->assertMatchesRegularExpression('/.*AlwaysFail->execute.*/', $lastExecution['trace']); } public function testArrayAsGroupIsNotAllowed() { - $this->expectException(RuntimeException::class); + $this->expectException(\RuntimeException::class); $memoryLimit = new MemoryLimit(1); - $job = Job::around(new AlwaysFail, $this->repository); + $job = Job::around(new AlwaysFail(), $this->repository); $job->inGroup(['test']); } } diff --git a/spec/Recruiter/JobToBePassedRetryStatisticsTest.php b/spec/Recruiter/JobToBePassedRetryStatisticsTest.php index e54cf7a2..4f98057b 100644 --- a/spec/Recruiter/JobToBePassedRetryStatisticsTest.php +++ b/spec/Recruiter/JobToBePassedRetryStatisticsTest.php @@ -18,7 +18,8 @@ protected function setUp(): void $this->repository = $this ->getMockBuilder(Repository::class) ->disableOriginalConstructor() - ->getMock(); + ->getMock() + ; } /** @@ -30,7 +31,7 @@ public function testTakeRetryPolicyFromRetriableInstance() $job = Job::around($workable, $this->repository); $job->execute($this->createMock(EventDispatcherInterface::class)); - $this->assertTrue($job->done(), "Job requiring retry statistics was not executed correctly: " . var_export($job->export(), true)); + $this->assertTrue($job->done(), 'Job requiring retry statistics was not executed correctly: ' . var_export($job->export(), true)); } } diff --git a/spec/Recruiter/JobToScheduleTest.php b/spec/Recruiter/JobToScheduleTest.php index 36fc6ff5..b81c8afb 100644 --- a/spec/Recruiter/JobToScheduleTest.php +++ b/spec/Recruiter/JobToScheduleTest.php @@ -2,11 +2,9 @@ namespace Recruiter; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; use Timeless as T; -use Timeless\Clock; -use Recruiter\RetryPolicy; class JobToScheduleTest extends TestCase { @@ -19,7 +17,8 @@ protected function setUp(): void $this->job = $this ->getMockBuilder(Job::class) ->disableOriginalConstructor() - ->getMock(); + ->getMock() + ; } protected function tearDown(): void @@ -33,12 +32,14 @@ public function testInBackgroundShouldScheduleJobNow() ->expects($this->once()) ->method('scheduleAt') ->with( - $this->equalTo($this->clock->now()) - ); + $this->equalTo($this->clock->now()), + ) + ; (new JobToSchedule($this->job)) ->inBackground() - ->execute(); + ->execute() + ; } public function testScheduledInShouldScheduleInCertainAmountOfTime() @@ -48,12 +49,14 @@ public function testScheduledInShouldScheduleInCertainAmountOfTime() ->expects($this->once()) ->method('scheduleAt') ->with( - $this->equalTo($amountOfTime->fromNow()) - ); + $this->equalTo($amountOfTime->fromNow()), + ) + ; (new JobToSchedule($this->job)) ->scheduleIn($amountOfTime) - ->execute(); + ->execute() + ; } public function testConfigureRetryPolicy() @@ -63,12 +66,14 @@ public function testConfigureRetryPolicy() $this->job ->expects($this->once()) ->method('retryWithPolicy') - ->with($doNotDoItAgain); + ->with($doNotDoItAgain) + ; (new JobToSchedule($this->job)) ->inBackground() ->retryWithPolicy($doNotDoItAgain) - ->execute(); + ->execute() + ; } public function tesShortcutToConfigureJobToNotBeRetried() @@ -76,43 +81,51 @@ public function tesShortcutToConfigureJobToNotBeRetried() $this->job ->expects($this->once()) ->method('retryWithPolicy') - ->with($this->isInstanceOf('Recruiter\RetryPolicy\DoNotDoItAgain')); + ->with($this->isInstanceOf('Recruiter\RetryPolicy\DoNotDoItAgain')) + ; (new JobToSchedule($this->job)) ->inBackground() ->doNotRetry() - ->execute(); + ->execute() + ; } public function testShouldNotExecuteJobWhenScheduled() { $this->job ->expects($this->once()) - ->method('save'); + ->method('save') + ; $this->job ->expects($this->never()) - ->method('execute'); + ->method('execute') + ; (new JobToSchedule($this->job)) ->inBackground() - ->execute(); + ->execute() + ; } public function testShouldExecuteJobWhenNotScheduled() { $this->job ->expects($this->never()) - ->method('scheduleAt'); + ->method('scheduleAt') + ; $this->job ->expects($this->once()) - ->method('execute'); + ->method('execute') + ; (new JobToSchedule($this->job)) ->execute( - $this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface') - ); + $this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'), + ) + ; } public function testConfigureMethodToCallOnWorkableInJob() @@ -120,10 +133,12 @@ public function testConfigureMethodToCallOnWorkableInJob() $this->job ->expects($this->once()) ->method('methodToCallOnWorkable') - ->with('send'); + ->with('send') + ; (new JobToSchedule($this->job)) - ->send(); + ->send() + ; } public function testReturnsJobId() @@ -131,14 +146,15 @@ public function testReturnsJobId() $this->job ->expects($this->any()) ->method('id') - ->will($this->returnValue('42')); + ->will($this->returnValue('42')) + ; $this->assertEquals( '42', (new JobToSchedule($this->job)) ->execute( - $this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface') - ) + $this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'), + ), ); } } diff --git a/spec/Recruiter/PickAvailableWorkersTest.php b/spec/Recruiter/PickAvailableWorkersTest.php index 7c54d84f..54abd630 100644 --- a/spec/Recruiter/PickAvailableWorkersTest.php +++ b/spec/Recruiter/PickAvailableWorkersTest.php @@ -2,11 +2,10 @@ namespace Recruiter; -use ArrayIterator; use MongoDB\BSON\ObjectId; use MongoDB\Collection; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; class PickAvailableWorkersTest extends TestCase { @@ -18,7 +17,8 @@ protected function setUp(): void $this->repository = $this ->getMockBuilder(Collection::class) ->disableOriginalConstructor() - ->getMock(); + ->getMock() + ; $this->workersPerUnit = 42; } @@ -39,7 +39,7 @@ public function testFewWorkersWithNoSpecifiSkill() $picked = Worker::pickAvailableWorkers($this->repository, $this->workersPerUnit); - list ($worksOn, $workers) = $picked[0]; + [$worksOn, $workers] = $picked[0]; $this->assertEquals('*', $worksOn); $this->assertEquals(3, count($workers)); } @@ -51,7 +51,7 @@ public function testFewWorkersWithSameSkill() $picked = Worker::pickAvailableWorkers($this->repository, $this->workersPerUnit); - list ($worksOn, $workers) = $picked[0]; + [$worksOn, $workers] = $picked[0]; $this->assertEquals('send-emails', $worksOn); $this->assertEquals(3, count($workers)); } @@ -64,7 +64,7 @@ public function testFewWorkersWithSomeDifferentSkills() $allSkillsGiven = []; $totalWorkersGiven = 0; foreach ($picked as $pickedRow) { - list ($worksOn, $workers) = $pickedRow; + [$worksOn, $workers] = $pickedRow; $allSkillsGiven[] = $worksOn; $totalWorkersGiven += count($workers); } @@ -80,7 +80,7 @@ public function testMoreWorkersThanAllowedPerUnit() $totalWorkersGiven = 0; foreach ($picked as $pickedRow) { - list ($worksOn, $workers) = $pickedRow; + [$worksOn, $workers] = $pickedRow; $totalWorkersGiven += count($workers); } $this->assertEquals($this->workersPerUnit, $totalWorkersGiven); @@ -90,11 +90,11 @@ private function withAvailableWorkers($workers) { $workersThatShouldBeFound = []; foreach ($workers as $skill => $quantity) { - for ($counter = 0; $counter < $quantity; $counter++) { + for ($counter = 0; $counter < $quantity; ++$counter) { $workerId = new ObjectId(); - $workersThatShouldBeFound[(string)$workerId] = [ + $workersThatShouldBeFound[(string) $workerId] = [ '_id' => $workerId, - 'work_on' => $skill + 'work_on' => $skill, ]; } } @@ -102,7 +102,8 @@ private function withAvailableWorkers($workers) $this->repository ->expects($this->any()) ->method('find') - ->will($this->returnValue(new ArrayIterator($workersThatShouldBeFound))); + ->will($this->returnValue(new \ArrayIterator($workersThatShouldBeFound))) + ; } private function withNoAvailableWorkers() @@ -110,7 +111,8 @@ private function withNoAvailableWorkers() $this->repository ->expects($this->any()) ->method('find') - ->will($this->returnValue(new ArrayIterator([]))); + ->will($this->returnValue(new \ArrayIterator([]))) + ; } private function assertArrayAreEquals($expected, $given) diff --git a/spec/Recruiter/RetryPolicy/ExponentialBackoffTest.php b/spec/Recruiter/RetryPolicy/ExponentialBackoffTest.php index fa451aad..102c100a 100644 --- a/spec/Recruiter/RetryPolicy/ExponentialBackoffTest.php +++ b/spec/Recruiter/RetryPolicy/ExponentialBackoffTest.php @@ -1,4 +1,5 @@ expects($this->once()) ->method('scheduleIn') - ->with(T\seconds(5)); + ->with(T\seconds(5)) + ; $retryPolicy->schedule($job); } @@ -24,7 +26,8 @@ public function testAfterEachFailureDoublesTheAmountOfTimeToWaitBetweenRetries() $job->expects($this->once()) ->method('scheduleIn') - ->with(T\seconds(10)); + ->with(T\seconds(10)) + ; $retryPolicy->schedule($job); } @@ -35,7 +38,8 @@ public function testAfterTooManyFailuresGivesUp() $job->expects($this->once()) ->method('archive') - ->with('tried-too-many-times'); + ->with('tried-too-many-times') + ; $retryPolicy->schedule($job); } @@ -43,7 +47,7 @@ public function testCanBeCreatedByTargetingAMaximumInterval() { $this->assertEquals( ExponentialBackoff::forAnInterval(1025, T\seconds(1)), - new ExponentialBackoff(10, 1) + new ExponentialBackoff(10, 1), ); } @@ -52,7 +56,9 @@ private function jobExecutedFor($times) $job = $this->getMockBuilder('Recruiter\JobAfterFailure')->disableOriginalConstructor()->getMock(); $job->expects($this->any()) ->method('numberOfAttempts') - ->will($this->returnValue($times)); + ->will($this->returnValue($times)) + ; + return $job; } } diff --git a/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php b/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php index bffbd9e8..13ccdfd7 100644 --- a/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php +++ b/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php @@ -2,10 +2,8 @@ namespace Recruiter\RetryPolicy; -use Exception; -use InvalidArgumentException; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; use Recruiter\RetryPolicy; class RetriableExceptionFilterTest extends TestCase @@ -22,40 +20,43 @@ protected function setUp(): void public function testCallScheduleOnRetriableException() { - $exception = $this->createMock(Exception::class); + $exception = $this->createMock(\Exception::class); $classOfException = get_class($exception); $filter = new RetriableExceptionFilter($this->filteredRetryPolicy, [$classOfException]); $this->filteredRetryPolicy ->expects($this->once()) - ->method('schedule'); + ->method('schedule') + ; $filter->schedule($this->jobFailedWithException($exception)); } public function testDoNotCallScheduleOnNonRetriableException() { - $exception = $this->createMock(Exception::class); + $exception = $this->createMock(\Exception::class); $classOfException = get_class($exception); $filter = new RetriableExceptionFilter($this->filteredRetryPolicy, [$classOfException]); $this->filteredRetryPolicy ->expects($this->never()) - ->method('schedule'); + ->method('schedule') + ; - $filter->schedule($this->jobFailedWithException(new Exception('Test'))); + $filter->schedule($this->jobFailedWithException(new \Exception('Test'))); } public function testWhenExceptionIsNotRetriableThenArchiveTheJob() { - $exception = $this->createMock(Exception::class); + $exception = $this->createMock(\Exception::class); $classOfException = get_class($exception); $filter = new RetriableExceptionFilter($this->filteredRetryPolicy, [$classOfException]); - $job = $this->jobFailedWithException(new Exception('Test')); + $job = $this->jobFailedWithException(new \Exception('Test')); $job->expects($this->once()) ->method('archive') - ->with('non-retriable-exception'); + ->with('non-retriable-exception') + ; $filter->schedule($job); } @@ -64,10 +65,11 @@ public function testAllExceptionsAreRetriableByDefault() { $this->filteredRetryPolicy ->expects($this->once()) - ->method('schedule'); + ->method('schedule') + ; $filter = new RetriableExceptionFilter($this->filteredRetryPolicy); - $filter->schedule($this->jobFailedWithException(new Exception('Test'))); + $filter->schedule($this->jobFailedWithException(new \Exception('Test'))); } public function testJobFailedWithSomethingThatIsNotAnException() @@ -75,7 +77,8 @@ public function testJobFailedWithSomethingThatIsNotAnException() $jobAfterFailure = $this->jobFailedWithException(null); $jobAfterFailure ->expects($this->once()) - ->method('archive'); + ->method('archive') + ; $filter = new RetriableExceptionFilter($this->filteredRetryPolicy); $filter->schedule($jobAfterFailure); @@ -86,19 +89,20 @@ public function testExportFilteredRetryPolicy() $this->filteredRetryPolicy ->expects($this->once()) ->method('export') - ->will($this->returnValue(['key' => 'value'])); + ->will($this->returnValue(['key' => 'value'])) + ; $filter = new RetriableExceptionFilter($this->filteredRetryPolicy); $this->assertEquals( [ 'retriable_exceptions' => ['Exception'], - 'filtered_retry_policy' => [ + 'filtered_retry_policy' => [ 'class' => get_class($this->filteredRetryPolicy), - 'parameters' => ['key' => 'value'] - ] + 'parameters' => ['key' => 'value'], + ], ], - $filter->export() + $filter->export(), ); } @@ -109,27 +113,27 @@ public function testImportRetryPolicy() $exported = $filter->export(); $filter = RetriableExceptionFilter::import($exported); - $filter->schedule($this->jobFailedWithException(new Exception('Test'))); + $filter->schedule($this->jobFailedWithException(new \Exception('Test'))); $this->assertEquals($exported, $filter->export()); } public function testRetriableExceptionsThatAreNotExceptions() { - $this->expectException(InvalidArgumentException::class); + $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage("Only subclasses of Exception can be retriable exceptions, 'StdClass' is not"); $retryPolicy = new DoNotDoItAgain(); $notAnExceptionClass = 'StdClass'; new RetriableExceptionFilter($retryPolicy, [$notAnExceptionClass]); } - private function jobFailedWithException($exception) { $jobAfterFailure = $this ->getMockBuilder('Recruiter\JobAfterFailure') ->disableOriginalConstructor() - ->getMock(); + ->getMock() + ; $jobAfterFailure ->expects($this->any()) diff --git a/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php b/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php index 06e272bf..91f62db4 100644 --- a/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php +++ b/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php @@ -1,9 +1,7 @@ when(InvalidArgumentException::class)->then(new DoNotDoItAgain()) - ->when(LogicException::class)->then(new DoNotDoItAgain()) - ->build(); + ->when(\InvalidArgumentException::class)->then(new DoNotDoItAgain()) + ->when(\LogicException::class)->then(new DoNotDoItAgain()) + ->build() + ; $this->assertInstanceOf(RetryPolicy::class, $retryPolicy); } @@ -23,9 +22,10 @@ public function testCanBeBuilt() public function testCanBeExportedAndImported() { $retryPolicy = SelectByException::create() - ->when(InvalidArgumentException::class)->then(new DoNotDoItAgain()) - ->when(LogicException::class)->then(new DoNotDoItAgain()) - ->build(); + ->when(\InvalidArgumentException::class)->then(new DoNotDoItAgain()) + ->when(\LogicException::class)->then(new DoNotDoItAgain()) + ->build() + ; $retryPolicyExported = $retryPolicy->export(); $retryPolicyImported = SelectByException::import($retryPolicyExported); @@ -36,24 +36,25 @@ public function testCanBeExportedAndImported() public function testSelectByException() { - $exception = new InvalidArgumentException('something'); + $exception = new \InvalidArgumentException('something'); $retryPolicy = new SelectByException([ - new RetriableException(get_class($exception), RetryForever::afterSeconds(10)) + new RetriableException(get_class($exception), RetryForever::afterSeconds(10)), ]); $job = $this->jobFailedWith($exception); $job->expects($this->once()) ->method('scheduleIn') - ->with(T\seconds(10)); + ->with(T\seconds(10)) + ; $retryPolicy->schedule($job); } public function testDefaultDoNotSchedule() { - $exception = new Exception('something'); + $exception = new \Exception('something'); $retryPolicy = new SelectByException([ - new RetriableException(InvalidArgumentException::class, RetryForever::afterSeconds(10)) + new RetriableException(\InvalidArgumentException::class, RetryForever::afterSeconds(10)), ]); $job = $this->jobFailedWith($exception); @@ -63,12 +64,14 @@ public function testDefaultDoNotSchedule() $retryPolicy->schedule($job); } - private function jobFailedWith(Exception $exception) + private function jobFailedWith(\Exception $exception) { $job = $this->getMockBuilder('Recruiter\JobAfterFailure')->disableOriginalConstructor()->getMock(); $job->expects($this->any()) ->method('causeOfFailure') - ->will($this->returnValue($exception)); + ->will($this->returnValue($exception)) + ; + return $job; } } diff --git a/spec/Recruiter/RetryPolicy/TimeTableTest.php b/spec/Recruiter/RetryPolicy/TimeTableTest.php index dd93d037..2ce8ea2e 100644 --- a/spec/Recruiter/RetryPolicy/TimeTableTest.php +++ b/spec/Recruiter/RetryPolicy/TimeTableTest.php @@ -2,7 +2,6 @@ namespace Recruiter\RetryPolicy; -use Exception; use PHPUnit\Framework\TestCase; use Timeless as T; @@ -26,7 +25,8 @@ public function testShouldRescheduleInOneMinuteWhenWasCreatedLessThanFiveMinutes $job = $this->givenJobThat($wasCreatedAt); $job->expects($this->once()) ->method('scheduleAt') - ->with($this->equalTo($expectedToBeScheduledAt)); + ->with($this->equalTo($expectedToBeScheduledAt)) + ; $this->scheduler->schedule($job); } @@ -37,7 +37,8 @@ public function testShouldRescheduleInFiveMinutesWhenWasCreatedLessThanOneHourAg $job = $this->givenJobThat($wasCreatedAt); $job->expects($this->once()) ->method('scheduleAt') - ->with($this->equalTo($expectedToBeScheduledAt)); + ->with($this->equalTo($expectedToBeScheduledAt)) + ; $this->scheduler->schedule($job); } @@ -48,7 +49,8 @@ public function testShouldRescheduleInFiveMinutesWhenWasCreatedLessThan24HoursAg $job = $this->givenJobThat($wasCreatedAt); $job->expects($this->once()) ->method('scheduleAt') - ->with($this->equalTo($expectedToBeScheduledAt)); + ->with($this->equalTo($expectedToBeScheduledAt)) + ; $this->scheduler->schedule($job); } @@ -64,7 +66,8 @@ public function testIsLastRetryReturnTrueIfJobWasCreatedMoreThanLastTimeSpen() $job = $this->createMock('Recruiter\Job'); $job->expects($this->any()) ->method('createdAt') - ->will($this->returnValue(T\hours(3)->ago())); + ->will($this->returnValue(T\hours(3)->ago())) + ; $tt = new TimeTable([ '1 minute ago' => '1 minute', @@ -78,30 +81,31 @@ public function testIsLastRetryReturnFalseIfJobWasCreatedLessThanLastTimeSpen() $job = $this->createMock('Recruiter\Job'); $job->expects($this->any()) ->method('createdAt') - ->will($this->returnValue(T\hours(3)->ago())); + ->will($this->returnValue(T\hours(3)->ago())) + ; $tt = new TimeTable([ '1 hour ago' => '1 minute', - '24 hours ago' => '1 minute' + '24 hours ago' => '1 minute', ]); $this->assertFalse($tt->isLastRetry($job)); } public function testInvalidTimeTableBecauseTimeWindow() { - $this->expectException(Exception::class); + $this->expectException(\Exception::class); $tt = new TimeTable(['1 minute' => '1 second']); } public function testInvalidTimeTableBecauseRescheduleTime() { - $this->expectException(Exception::class); + $this->expectException(\Exception::class); $tt = new TimeTable(['1 minute ago' => '1 second ago']); } public function testInvalidTimeTableBecauseRescheduleTimeIsGreaterThanTimeWindow() { - $this->expectException(Exception::class); + $this->expectException(\Exception::class); $tt = new TimeTable(['1 minute ago' => '2 minutes']); } @@ -110,10 +114,13 @@ private function givenJobThat(T\Moment $wasCreatedAt) $job = $this->getMockBuilder('Recruiter\JobAfterFailure') ->disableOriginalConstructor() ->setMethods(['createdAt', 'scheduleAt']) - ->getMock(); + ->getMock() + ; $job->expects($this->any()) ->method('createdAt') - ->will($this->returnValue($wasCreatedAt)); + ->will($this->returnValue($wasCreatedAt)) + ; + return $job; } @@ -123,10 +130,12 @@ private function jobThatWasCreated($relativeTime) $job = $this->getMockBuilder('Recruiter\JobAfterFailure') ->disableOriginalConstructor() ->setMethods(['createdAt', 'scheduleAt']) - ->getMock(); + ->getMock() + ; $job->expects($this->any()) ->method('createdAt') ->will($this->returnValue($wasCreatedAt)); + return $job; } } diff --git a/spec/Recruiter/SchedulePolicy/CronTest.php b/spec/Recruiter/SchedulePolicy/CronTest.php index 51dece3b..bac7a8cf 100644 --- a/spec/Recruiter/SchedulePolicy/CronTest.php +++ b/spec/Recruiter/SchedulePolicy/CronTest.php @@ -2,7 +2,6 @@ namespace Recruiter\SchedulePolicy; -use DateTime; use PHPUnit\Framework\TestCase; use Timeless\Moment; @@ -13,13 +12,13 @@ class CronTest extends TestCase */ public function testCronCanBeExportedAndImportedWithoutDataLoss(string $cronExpression, string $expectedDate) { - $cron = new Cron($cronExpression, DateTime::createFromFormat('Y-m-d H:i:s', '2019-01-15 15:00:00')); + $cron = new Cron($cronExpression, \DateTime::createFromFormat('Y-m-d H:i:s', '2019-01-15 15:00:00')); $cron = Cron::import($cron->export()); $this->assertEquals( - Moment::fromDateTime(new DateTime($expectedDate)), + Moment::fromDateTime(new \DateTime($expectedDate)), $cron->next(), - 'calculated schedule time is: ' . $cron->next()->format() + 'calculated schedule time is: ' . $cron->next()->format(), ); } diff --git a/spec/Recruiter/TaggableWorkableTest.php b/spec/Recruiter/TaggableWorkableTest.php index ee85fea5..c6ead3f8 100644 --- a/spec/Recruiter/TaggableWorkableTest.php +++ b/spec/Recruiter/TaggableWorkableTest.php @@ -2,11 +2,9 @@ namespace Recruiter; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; use Recruiter\Job\Repository; -use Timeless as T; -use Recruiter\Taggable; class TaggableWorkableTest extends TestCase { @@ -17,7 +15,8 @@ protected function setUp(): void $this->repository = $this ->getMockBuilder(Repository::class) ->disableOriginalConstructor() - ->getMock(); + ->getMock() + ; } public function testWorkableExportsTags() diff --git a/spec/Recruiter/WaitStrategyTest.php b/spec/Recruiter/WaitStrategyTest.php index 909aa0a9..4ff7a53e 100644 --- a/spec/Recruiter/WaitStrategyTest.php +++ b/spec/Recruiter/WaitStrategyTest.php @@ -15,8 +15,8 @@ class WaitStrategyTest extends TestCase protected function setUp(): void { $this->waited = T\milliseconds(0); - $this->howToWait = function($microseconds): void { - $this->waited = T\milliseconds($microseconds/1000); + $this->howToWait = function ($microseconds): void { + $this->waited = T\milliseconds($microseconds / 1000); }; $this->timeToWaitAtLeast = T\milliseconds(250); $this->timeToWaitAtMost = T\seconds(30); @@ -27,7 +27,7 @@ public function testStartsToWaitTheMinimumAmountOfTime() $ws = new WaitStrategy( $this->timeToWaitAtLeast, $this->timeToWaitAtMost, - $this->howToWait + $this->howToWait, ); $ws->wait(); $this->assertEquals($this->timeToWaitAtLeast, $this->waited); @@ -38,7 +38,7 @@ public function testBackingOffIncreasesTheIntervalExponentially() $ws = new WaitStrategy( $this->timeToWaitAtLeast, $this->timeToWaitAtMost, - $this->howToWait + $this->howToWait, ); $ws->wait(); $this->assertEquals($this->timeToWaitAtLeast, $this->waited); @@ -64,7 +64,7 @@ public function testGoingForwardLowersTheSleepingPeriod() $ws = new WaitStrategy( $this->timeToWaitAtLeast, $this->timeToWaitAtMost, - $this->howToWait + $this->howToWait, ); $ws->backOff(); $ws->goForward(); @@ -77,7 +77,7 @@ public function testTheSleepingPeriodCanBeResetToTheMinimum() $ws = new WaitStrategy( $this->timeToWaitAtLeast, $this->timeToWaitAtMost, - $this->howToWait + $this->howToWait, ); $ws->backOff(); $ws->backOff(); @@ -93,7 +93,7 @@ public function testGoingForwardCannotLowerTheIntervalBelowMinimum() $ws = new WaitStrategy( $this->timeToWaitAtLeast, $this->timeToWaitAtMost, - $this->howToWait + $this->howToWait, ); $ws->goForward(); $ws->goForward(); diff --git a/spec/Recruiter/Workable/FactoryMethodCommandTest.php b/spec/Recruiter/Workable/FactoryMethodCommandTest.php index 508e3991..b7263e2d 100644 --- a/spec/Recruiter/Workable/FactoryMethodCommandTest.php +++ b/spec/Recruiter/Workable/FactoryMethodCommandTest.php @@ -1,4 +1,5 @@ myObject() - ->myMethod('answer', 42); + ->myMethod('answer', 42) + ; $this->assertEquals('42', $workable->execute()); } @@ -17,10 +19,11 @@ public function testCanBeImportedAndExported() { $workable = FactoryMethodCommand::from('Recruiter\Workable\DummyFactory::create') ->myObject() - ->myMethod('answer', 42); + ->myMethod('answer', 42) + ; $this->assertEquals( $workable, - FactoryMethodCommand::import($workable->export()) + FactoryMethodCommand::import($workable->export()), ); } @@ -28,7 +31,8 @@ public function testPassesRetryStatisticsAsAnAdditionalArgumentToTheLastMethodTo { $workable = FactoryMethodCommand::from('Recruiter\Workable\DummyFactory::create') ->myObject() - ->myNeedyMethod(); + ->myNeedyMethod() + ; $this->assertEquals(2, $workable->execute(['retry_number' => 2])); } } diff --git a/spec/Recruiter/Workable/ShellCommandTest.php b/spec/Recruiter/Workable/ShellCommandTest.php index c2ad1bc8..2eba223f 100644 --- a/spec/Recruiter/Workable/ShellCommandTest.php +++ b/spec/Recruiter/Workable/ShellCommandTest.php @@ -1,4 +1,5 @@ assertEquals( $workable, - ShellCommand::import($workable->export()) + ShellCommand::import($workable->export()), ); } } diff --git a/spec/Recruiter/WorkablePersistenceTest.php b/spec/Recruiter/WorkablePersistenceTest.php index 95fba471..ac8e3e71 100644 --- a/spec/Recruiter/WorkablePersistenceTest.php +++ b/spec/Recruiter/WorkablePersistenceTest.php @@ -11,7 +11,7 @@ public function testCanBeExportedAndImported() $job = new SomethingWorkable(['key' => 'value']); $this->assertEquals( $job, - SomethingWorkable::import($job->export()) + SomethingWorkable::import($job->export()), ); } } diff --git a/spec/Recruiter/WorkerProcessTest.php b/spec/Recruiter/WorkerProcessTest.php index df9c1b30..f3d7bfdd 100644 --- a/spec/Recruiter/WorkerProcessTest.php +++ b/spec/Recruiter/WorkerProcessTest.php @@ -2,8 +2,8 @@ namespace Recruiter; -use PHPUnit\Framework\TestCase; use PHPUnit\Framework\MockObject\MockObject; +use PHPUnit\Framework\TestCase; use Recruiter\Worker\Process; use Recruiter\Worker\Repository; use Sink\BlackHole; @@ -19,7 +19,8 @@ protected function setUp(): void $this->repository = $this->getMockBuilder(Repository::class) ->disableOriginalConstructor() - ->getMock(); + ->getMock() + ; } public function testIfNotAliveWhenIsNotAliveReturnsItself() @@ -39,7 +40,8 @@ public function testRetireWorkerIfNotAlive() $this->repository ->expects($this->once()) ->method('retireWorkerWithPid') - ->with($this->pid); + ->with($this->pid) + ; $process = $this->givenWorkerProcessDead(); $process->cleanUp($this->repository); @@ -50,13 +52,13 @@ public function testDoNotRetireWorkerIfAlive() $this->repository ->expects($this->never()) ->method('retireWorkerWithPid') - ->with($this->pid); + ->with($this->pid) + ; $process = $this->givenWorkerProcessAlive(); $process->cleanUp($this->repository); } - private function givenWorkerProcessAlive() { return $this->givenWorkerProcess(true); @@ -72,11 +74,13 @@ private function givenWorkerProcess($alive) $process = $this->getMockBuilder('Recruiter\Worker\Process') ->setMethods(['isAlive']) ->setConstructorArgs([$this->pid]) - ->getMock(); + ->getMock() + ; $process->expects($this->any()) ->method('isAlive') - ->will($this->returnValue($alive)); + ->will($this->returnValue($alive)) + ; return $process; } diff --git a/spec/Sink/BlackHoleTest.php b/spec/Sink/BlackHoleTest.php index 82867707..097ec792 100644 --- a/spec/Sink/BlackHoleTest.php +++ b/spec/Sink/BlackHoleTest.php @@ -60,7 +60,7 @@ public function testIsAccessibleAsAnArrayAlwaysGetItself() $instance = new BlackHole(); $this->assertInstanceOf('Sink\BlackHole', $instance[42]); $this->assertInstanceOf('Sink\BlackHole', $instance['aString']); - $this->assertInstanceOf('Sink\BlackHole', $instance[[1,2,3]]); + $this->assertInstanceOf('Sink\BlackHole', $instance[[1, 2, 3]]); } /* public function testIsAccessibleAsAnArrayExists() */ diff --git a/spec/Timeless/IntervalParseTest.php b/spec/Timeless/IntervalParseTest.php index c3f8f210..8cc384f4 100644 --- a/spec/Timeless/IntervalParseTest.php +++ b/spec/Timeless/IntervalParseTest.php @@ -2,7 +2,6 @@ namespace Timeless; -use DateInterval; use PHPUnit\Framework\TestCase; class IntervalParseTest extends TestCase @@ -88,9 +87,9 @@ public function testParseShortFormat() public function testFromDateInterval() { - $this->assertEquals(days(2), Interval::fromDateInterval(new DateInterval('P2D'))); - $this->assertEquals(minutes(10), Interval::fromDateInterval(new DateInterval('PT10M'))); - $this->assertEquals(days(2)->add(minutes(10)), Interval::fromDateInterval(new DateInterval('P2DT10M'))); + $this->assertEquals(days(2), Interval::fromDateInterval(new \DateInterval('P2D'))); + $this->assertEquals(minutes(10), Interval::fromDateInterval(new \DateInterval('PT10M'))); + $this->assertEquals(days(2)->add(minutes(10)), Interval::fromDateInterval(new \DateInterval('P2DT10M'))); } public function testNumberAsIntervalFormat() diff --git a/spec/Timeless/MongoDateTest.php b/spec/Timeless/MongoDateTest.php index baecddf2..a7095a37 100644 --- a/spec/Timeless/MongoDateTest.php +++ b/spec/Timeless/MongoDateTest.php @@ -1,4 +1,5 @@ forAll( - Generator\choose(0, 1500 * 1000 * 1000) + Generator\choose(0, 1500 * 1000 * 1000), ) ->then(function ($milliseconds): void { $moment = new Moment($milliseconds); $this->assertEquals( $moment, - MongoDate::toMoment(MongoDate::from($moment)) + MongoDate::toMoment(MongoDate::from($moment)), ); - }); + }) + ; } } diff --git a/src/Recruiter/AlreadyRunningException.php b/src/Recruiter/AlreadyRunningException.php index a534409a..172e4bd2 100644 --- a/src/Recruiter/AlreadyRunningException.php +++ b/src/Recruiter/AlreadyRunningException.php @@ -2,8 +2,6 @@ namespace Recruiter; -use Exception; - -class AlreadyRunningException extends Exception +class AlreadyRunningException extends \Exception { } diff --git a/src/Recruiter/CannotRetireWorkerAtWorkException.php b/src/Recruiter/CannotRetireWorkerAtWorkException.php index 452d5359..5e7864cd 100644 --- a/src/Recruiter/CannotRetireWorkerAtWorkException.php +++ b/src/Recruiter/CannotRetireWorkerAtWorkException.php @@ -1,8 +1,7 @@ addArgument( 'shell_command', InputArgument::REQUIRED, - 'The command to run' + 'The command to run', ) ; } @@ -33,7 +34,8 @@ protected function execute(InputInterface $input, OutputInterface $output): int ShellCommand::fromCommandLine($input->getArgument('shell_command')) ->asJobOf($this->recruiter) ->inBackground() - ->execute(); + ->execute() + ; return self::SUCCESS; } diff --git a/src/Recruiter/Factory.php b/src/Recruiter/Factory.php index ddf73c86..d8164685 100644 --- a/src/Recruiter/Factory.php +++ b/src/Recruiter/Factory.php @@ -5,7 +5,6 @@ use MongoDB\Client; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use Recruiter\Infrastructure\Persistence\Mongodb\URI; -use UnexpectedValueException; class Factory { @@ -22,19 +21,13 @@ public function getMongoDb(URI $uri, array $options = []) 'document' => 'array', 'root' => 'array', ], - ], $options) + ], $options), ); $client->listDatabases(); // in order to avoid lazy connections and catch eventually connection exceptions here + return $client->selectDatabase($uri->database()); } catch (DriverRuntimeException $e) { - throw new UnexpectedValueException( - sprintf( - "'No MongoDB running at '%s'", - $uri->__toString() - ), - $e->getCode(), - $e - ); + throw new \UnexpectedValueException(sprintf("'No MongoDB running at '%s'", $uri->__toString()), $e->getCode(), $e); } } } diff --git a/src/Recruiter/Finalizable.php b/src/Recruiter/Finalizable.php index ccdec82d..6804f06c 100644 --- a/src/Recruiter/Finalizable.php +++ b/src/Recruiter/Finalizable.php @@ -1,17 +1,16 @@ addOption( 'group', 'g', InputOption::VALUE_REQUIRED, - 'limit analytics to a specific job group' + 'limit analytics to a specific job group', ) ; } @@ -72,7 +68,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int ->setRows([array_values($analytic)]) ; - for ($i = 0; $i < count($analytic); $i++) { + for ($i = 0; $i < count($analytic); ++$i) { $table->setColumnStyle($i, $rightAligned); $table->setColumnWidth($i, $columnsWidth); } diff --git a/src/Recruiter/Infrastructure/Command/Bko/JobRecoverCommand.php b/src/Recruiter/Infrastructure/Command/Bko/JobRecoverCommand.php index 6ec828e7..ff6b894f 100644 --- a/src/Recruiter/Infrastructure/Command/Bko/JobRecoverCommand.php +++ b/src/Recruiter/Infrastructure/Command/Bko/JobRecoverCommand.php @@ -1,9 +1,9 @@ addOption( 'scheduleAt', 's', InputOption::VALUE_REQUIRED, - 're-scheduling the job at specific datetime' + 're-scheduling the job at specific datetime', ) ->addArgument( 'jobId', InputArgument::REQUIRED, - 'the id of the job in archived collection to be recovered' + 'the id of the job in archived collection to be recovered', ) ; } @@ -80,14 +74,15 @@ protected function execute(InputInterface $input, OutputInterface $output): int if ($input->getOption('scheduleAt')) { /** @var string */ $scheduleAt = $input->getOption('scheduleAt'); - $job->scheduleAt(Moment::fromDateTime(new DateTime($scheduleAt))); + $job->scheduleAt(Moment::fromDateTime(new \DateTime($scheduleAt))); } else { $job->scheduleAt(T\now()); } $job ->scheduledBy('recovering-archived-job', $archivedJobId, -1) - ->save(); + ->save() + ; $output->writeln("Job recovered, new job id is `{$job->id()}`"); diff --git a/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php b/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php index 70add3e9..6cfca252 100644 --- a/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php +++ b/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php @@ -1,4 +1,5 @@ buildOutputData(); if (!$outputData) { $output->writeln('There are no schedulers yet.'); + return self::SUCCESS; } @@ -99,7 +92,7 @@ private function selectUrnToDelete(array $urns, InputInterface $input, OutputInt $question = new ChoiceQuestion( 'Please select the scheduler which you want delete', $urns, - null + null, ); $question->setErrorMessage('scheduler %s is invalid.'); @@ -137,7 +130,7 @@ protected function buildOutputData() $i = 0; $schedulers = $this->schedulerRepository->all(); - if (! $schedulers) { + if (!$schedulers) { return null; } @@ -146,7 +139,7 @@ protected function buildOutputData() $info = [ 'createdAt' => $data['created_at']->toDateTime()->format('c'), - 'lastScheduling' => ($data['last_scheduling']['scheduled_at'])->toDateTime()->format('c'), + 'lastScheduling' => $data['last_scheduling']['scheduled_at']->toDateTime()->format('c'), 'workable' => $data['job']['workable']['class'], 'policy' => $scheduler->schedulePolicy()->export(), ]; @@ -166,7 +159,7 @@ protected function buildOutputData() } $outputData[] = [ - '' => "" . $i++ . "", + '' => '' . $i++ . '', 'urn' => $data['urn'], 'info' => $infoString, ]; diff --git a/src/Recruiter/Infrastructure/Command/CleanerCommand.php b/src/Recruiter/Infrastructure/Command/CleanerCommand.php index 302d5b71..a7ce29f6 100644 --- a/src/Recruiter/Infrastructure/Command/CleanerCommand.php +++ b/src/Recruiter/Infrastructure/Command/CleanerCommand.php @@ -1,28 +1,27 @@ factory = $factory; @@ -84,7 +80,7 @@ public function execute(): bool $this->log(sprintf( '[%s] cleaned up %d old jobs from the archive' . PHP_EOL, $memoryUsage->format(), - $numberOfJobsCleaned + $numberOfJobsCleaned, ), LogLevel::INFO); $this->log(sprintf('going to sleep for %sms', $this->waitStrategy->current()), LogLevel::DEBUG); @@ -94,9 +90,10 @@ public function execute(): bool return $numberOfJobsCleaned > 0; } - public function shutdown(?Throwable $e = null): bool + public function shutdown(?\Throwable $e = null): bool { $this->log('ok, see you space cowboy...', LogLevel::INFO); + return true; } @@ -128,6 +125,7 @@ public function description(): string public function definition(): InputDefinition { $defaultMongoUri = getenv('MONGODB_URI') ?: 'mongodb://localhost:27017'; + return new InputDefinition([ new InputOption('target', 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', $defaultMongoUri), new InputOption('clean-after', 'c', InputOption::VALUE_REQUIRED, 'delete jobs after :period', '5days'), @@ -147,7 +145,7 @@ public function init(InputInterface $input): void $this->waitStrategy = new ExponentialBackoffStrategy( Interval::parse($input->getOption('wait-at-least'))->ms(), - Interval::parse($input->getOption('wait-at-most'))->ms() + Interval::parse($input->getOption('wait-at-most'))->ms(), ); $this->memoryLimit = new MemoryLimit($input->getOption('memory-limit')); $this->gracePeriod = Interval::parse($input->getOption('clean-after')); @@ -169,7 +167,7 @@ private function log(string $message, string $level = LogLevel::DEBUG): void 'program' => $this->name(), 'datetime' => date('c'), 'pid' => posix_getpid(), - ] + ], ); } } diff --git a/src/Recruiter/Infrastructure/Command/RecruiterCommand.php b/src/Recruiter/Infrastructure/Command/RecruiterCommand.php index 61f97b95..ee06dd98 100644 --- a/src/Recruiter/Infrastructure/Command/RecruiterCommand.php +++ b/src/Recruiter/Infrastructure/Command/RecruiterCommand.php @@ -1,11 +1,14 @@ factory = $factory; @@ -102,7 +96,7 @@ private function rollbackLockedJobs() private function assignJobsToWorkers(): array { $pickStartAt = microtime(true); - list($assignment, $actualNumber) = $this->recruiter->assignJobsToWorkers(); + [$assignment, $actualNumber] = $this->recruiter->assignJobsToWorkers(); $pickEndAt = microtime(true); foreach ($assignment as $worker => $job) { $this->log(sprintf(' tried to assign job `%s` to worker `%s`', $job, $worker), LogLevel::INFO); @@ -114,7 +108,7 @@ private function assignJobsToWorkers(): array $memoryUsage->format(), count($assignment), ($pickEndAt - $pickStartAt) * 1000, - $actualNumber + $actualNumber, ), LogLevel::DEBUG); $this->memoryLimit->ensure($memoryUsage); @@ -128,27 +122,27 @@ private function scheduleRepeatableJobs(): void $this->recruiter->scheduleRepeatableJobs(); $creationEndAt = microtime(true); - //FIXME:! log every job created? + // FIXME:! log every job created? /* foreach ($assignment as $worker => $job) { */ /* $this->log(sprintf(' tried to assign job `%s` to worker `%s`', $job, $worker)); */ /* } */ $this->log(sprintf( 'creation of jobs from crontab in %fms', - ($creationEndAt - $creationStartAt) * 1000 + ($creationEndAt - $creationStartAt) * 1000, )); } private function retireDeadWorkers() { $unlockedJobs = $this->recruiter->retireDeadWorkers( - new DateTimeImmutable(), - $this->consideredDeadAfter + new \DateTimeImmutable(), + $this->consideredDeadAfter, ); $this->log(sprintf('unlocked %d jobs due to dead workers', $unlockedJobs), LogLevel::DEBUG); } - public function shutdown(?Throwable $e = null): bool + public function shutdown(?\Throwable $e = null): bool { $this->recruiter->bye(); $this->log('ok, see you space cowboy...', LogLevel::INFO); @@ -184,6 +178,7 @@ public function description(): string public function definition(): InputDefinition { $defaultMongoUri = getenv('MONGODB_URI') ?: 'mongodb://localhost:27017'; + return new InputDefinition([ new InputOption('target', 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', $defaultMongoUri), new InputOption('backoff-to', 'b', InputOption::VALUE_REQUIRED, 'Upper limit of time to wait before next polling (milliseconds)', '1600ms'), @@ -207,7 +202,7 @@ public function init(InputInterface $input): void $this->waitStrategy = new ExponentialBackoffStrategy( Interval::parse($input->getOption('backoff-from'))->ms(), - Interval::parse($input->getOption('backoff-to'))->ms() + Interval::parse($input->getOption('backoff-to'))->ms(), ); $this->consideredDeadAfter = Interval::parse($input->getOption('considered-dead-after')); @@ -234,7 +229,7 @@ private function log(string $message, string $level = LogLevel::DEBUG): void 'program' => $this->name(), 'datetime' => date('c'), 'pid' => posix_getpid(), - ] + ], ); } diff --git a/src/Recruiter/Infrastructure/Command/WorkerCommand.php b/src/Recruiter/Infrastructure/Command/WorkerCommand.php index 626091e0..85447cb9 100644 --- a/src/Recruiter/Infrastructure/Command/WorkerCommand.php +++ b/src/Recruiter/Infrastructure/Command/WorkerCommand.php @@ -1,21 +1,20 @@ factory = $factory; @@ -81,7 +76,7 @@ public function execute(): bool return (bool) $doneSomeWork; } - public function shutdown(?Throwable $e = null): bool + public function shutdown(?\Throwable $e = null): bool { if ($this->worker->retireIfNotAssigned()) { $this->log(sprintf('worker `%s` retired', $this->worker->id()), LogLevel::INFO); @@ -120,6 +115,7 @@ public function description(): string public function definition(): InputDefinition { $defaultMongoUri = getenv('MONGODB_URI') ?: 'mongodb://localhost:27017'; + return new InputDefinition([ new InputOption('target', 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', $defaultMongoUri), new InputOption('backoff-to', 'b', InputOption::VALUE_REQUIRED, 'Upper limit of time to wait before next polling', '6400ms'), @@ -139,7 +135,7 @@ public function init(InputInterface $input): void $this->waitStrategy = new ExponentialBackoffStrategy( Interval::parse($input->getOption('backoff-from'))->ms(), - Interval::parse($input->getOption('backoff-to'))->ms() + Interval::parse($input->getOption('backoff-to'))->ms(), ); $memoryLimit = new MemoryLimit($input->getOption('memory-limit')); @@ -171,7 +167,7 @@ private function log(string $message, string $level = LogLevel::DEBUG): void 'datetime' => date('c'), 'pid' => posix_getpid(), 'workerId' => (string) $this->worker->id(), - ] + ], ); } diff --git a/src/Recruiter/Infrastructure/Filesystem/BootstrapFile.php b/src/Recruiter/Infrastructure/Filesystem/BootstrapFile.php index 9f12e88b..68cf7cab 100644 --- a/src/Recruiter/Infrastructure/Filesystem/BootstrapFile.php +++ b/src/Recruiter/Infrastructure/Filesystem/BootstrapFile.php @@ -1,13 +1,13 @@ filePath = $this->validate($filePath); } - public static function fromFilePath(string $filePath): Self + public static function fromFilePath(string $filePath): self { - return new Static($filePath); + return new static($filePath); } public function load(Recruiter $recruiter) @@ -38,7 +38,7 @@ private function validate($filePath): string } if (!is_readable($filePath)) { - $this->throwBecauseFile($filePath, "is not readable"); + $this->throwBecauseFile($filePath, 'is not readable'); } return $filePath; @@ -46,12 +46,6 @@ private function validate($filePath): string private function throwBecauseFile($filePath, $reason) { - throw new UnexpectedValueException( - sprintf( - "Bootstrap file has an invalid value: file '%s' %s", - $filePath, - $reason - ) - ); + throw new \UnexpectedValueException(sprintf("Bootstrap file has an invalid value: file '%s' %s", $filePath, $reason)); } } diff --git a/src/Recruiter/Infrastructure/Memory/MemoryLimit.php b/src/Recruiter/Infrastructure/Memory/MemoryLimit.php index e59fd636..e04e05ab 100644 --- a/src/Recruiter/Infrastructure/Memory/MemoryLimit.php +++ b/src/Recruiter/Infrastructure/Memory/MemoryLimit.php @@ -1,13 +1,13 @@ limit = ByteUnits\parse($limit); } catch (ByteUnits\ParseException $e) { - throw new UnexpectedValueException( - sprintf("Memory limit '%s' is an invalid value: %s", $limit, $e->getMessage()) - ); + throw new \UnexpectedValueException(sprintf("Memory limit '%s' is an invalid value: %s", $limit, $e->getMessage())); } } @@ -28,11 +26,7 @@ public function ensure($used) { $used = ByteUnits\box($used); if ($used->isGreaterThan($this->limit)) { - throw new MemoryLimitExceededException(sprintf( - 'Memory limit reached, %s is more than the force limit of %s', - $used->format(), - $this->limit->format() - )); + throw new MemoryLimitExceededException(sprintf('Memory limit reached, %s is more than the force limit of %s', $used->format(), $this->limit->format())); } } } diff --git a/src/Recruiter/Infrastructure/Memory/MemoryLimitExceededException.php b/src/Recruiter/Infrastructure/Memory/MemoryLimitExceededException.php index 8188be2c..4516250a 100644 --- a/src/Recruiter/Infrastructure/Memory/MemoryLimitExceededException.php +++ b/src/Recruiter/Infrastructure/Memory/MemoryLimitExceededException.php @@ -1,9 +1,9 @@ retryWithPolicy() : new RetryPolicy\DoNotDoItAgain(), new JobExecution(), - $repository + $repository, ); } @@ -45,7 +39,7 @@ public static function import($document, Repository $repository) WorkableInJob::import($document), RetryPolicyInJob::import($document), JobExecution::import($document), - $repository + $repository, ); } @@ -76,6 +70,7 @@ public function numberOfAttempts() public function retryWithPolicy(RetryPolicy $retryPolicy) { $this->retryPolicy = $retryPolicy; + return $this; } @@ -91,14 +86,13 @@ public function taggedAs(array $tags) public function inGroup($group) { if (is_array($group)) { - throw new RuntimeException( - "Group can be only single string, for other uses use `taggedAs` method. - Received group: `" . var_export($group, true) . "`" - ); + throw new \RuntimeException('Group can be only single string, for other uses use `taggedAs` method. + Received group: `' . var_export($group, true) . '`'); } if (!empty($group)) { $this->status['group'] = $group; } + return $this; } @@ -106,12 +100,14 @@ public function scheduleAt(Moment $at) { $this->status['locked'] = false; $this->status['scheduled_at'] = T\MongoDate::from($at); + return $this; } public function withUrn(string $urn) { $this->status['urn'] = $urn; + return $this; } @@ -131,7 +127,7 @@ public function scheduledBy(string $namespace, string $id, int $executions) public function methodToCallOnWorkable($method) { if (!method_exists($this->workable, $method)) { - throw new Exception("Unknown method '$method' on workable instance"); + throw new \Exception("Unknown method '$method' on workable instance"); } $this->status['workable']['method'] = $method; } @@ -145,7 +141,7 @@ public function execute(EventDispatcherInterface $eventDispatcher) $result = $this->workable->$methodToCall($this->retryStatistics()); $this->afterExecution($result, $eventDispatcher); } - } catch (Throwable $exception) { + } catch (\Throwable $exception) { $this->afterFailure($exception, $eventDispatcher); } @@ -184,19 +180,20 @@ public function export() $this->lastJobExecution->export(), $this->tagsToUseFor($this->workable), WorkableInJob::export($this->workable, $this->status['workable']['method']), - RetryPolicyInJob::export($this->retryPolicy) + RetryPolicyInJob::export($this->retryPolicy), ); } public function beforeExecution(EventDispatcherInterface $eventDispatcher) { - $this->status['attempts'] += 1; + ++$this->status['attempts']; $this->lastJobExecution = new JobExecution(); $this->lastJobExecution->started($this->scheduledAt()); $this->emit('job.started', $eventDispatcher); if ($this->hasBeenScheduled()) { $this->save(); } + return $this; } @@ -209,6 +206,7 @@ public function afterExecution($result, EventDispatcherInterface $eventDispatche if ($this->hasBeenScheduled()) { $this->archive('done'); } + return $this; } @@ -222,6 +220,7 @@ private function recoverFromCrash(EventDispatcherInterface $eventDispatcher) if ($this->lastJobExecution->isCrashed()) { return !$archived = $this->afterFailure(new WorkerDiedInTheLineOfDutyException(), $eventDispatcher); } + return true; } @@ -238,6 +237,7 @@ private function afterFailure($exception, $eventDispatcher) $this->emit('job.failure.last', $eventDispatcher); $this->triggerOnWorkable('afterLastFailure', $exception); } + return $archived; } @@ -250,7 +250,7 @@ private function emit($eventType, EventDispatcherInterface $eventDispatcher): vo } } - private function triggerOnWorkable($method, ?Throwable $e = null) + private function triggerOnWorkable($method, ?\Throwable $e = null) { if ($this->workable instanceof Finalizable) { $this->workable->$method($e); @@ -285,6 +285,7 @@ private function tagsToUseFor(Workable $workable) if (!empty($tagsToUse)) { return ['tags' => array_values(array_unique($tagsToUse))]; } + return []; } @@ -300,7 +301,7 @@ private static function initialize() 'group' => 'generic', ], WorkableInJob::initialize(), - RetryPolicyInJob::initialize() + RetryPolicyInJob::initialize(), ); } @@ -310,24 +311,23 @@ public static function pickReadyJobsForWorkers(MongoCollection $collection, $wor iterator_to_array( $collection ->find( - ( - Worker::canWorkOnAnyJobs($worksOn) ? - [ 'scheduled_at' => ['$lt' => T\MongoDate::now()], - 'locked' => false, - ] : - [ 'scheduled_at' => ['$lt' => T\MongoDate::now()], - 'locked' => false, - 'group' => $worksOn, - ] - ), + Worker::canWorkOnAnyJobs($worksOn) ? + ['scheduled_at' => ['$lt' => T\MongoDate::now()], + 'locked' => false, + ] : + ['scheduled_at' => ['$lt' => T\MongoDate::now()], + 'locked' => false, + 'group' => $worksOn, + ] + , [ 'projection' => ['_id' => 1], 'sort' => ['scheduled_at' => 1], 'limit' => count($workers), - ] - ) + ], + ), ), - '_id' + '_id', ); if (count($jobs) > 0) { @@ -347,13 +347,13 @@ public static function rollbackLockedNotIn(MongoCollection $collection, array $e '$set' => [ 'locked' => false, 'last_execution.crashed' => true, - ] - ] + ], + ], ); return $result->getModifiedCount(); } catch (BulkWriteException $e) { - throw new InvalidArgumentException("Not valid excluded jobs filter: " . var_export($excluded, true), -1, $e); + throw new \InvalidArgumentException('Not valid excluded jobs filter: ' . var_export($excluded, true), -1, $e); } } @@ -361,7 +361,7 @@ public static function lockAll(MongoCollection $collection, $jobs) { $collection->updateMany( ['_id' => ['$in' => array_values($jobs)]], - ['$set' => ['locked' => true]] + ['$set' => ['locked' => true]], ); } } diff --git a/src/Recruiter/Job/Event.php b/src/Recruiter/Job/Event.php index 094a0f3c..946d9489 100644 --- a/src/Recruiter/Job/Event.php +++ b/src/Recruiter/Job/Event.php @@ -1,4 +1,5 @@ jobExport) ? $this->jobExport['tags'] : []; + return in_array($wantedTag, $tags); } } diff --git a/src/Recruiter/Job/Repository.php b/src/Recruiter/Job/Repository.php index 169bda40..ef3dc499 100644 --- a/src/Recruiter/Job/Repository.php +++ b/src/Recruiter/Job/Repository.php @@ -1,12 +1,10 @@ map( $this->scheduled->find([], [ 'sort' => ['scheduled_at' => -1], - ]) + ]), ); } @@ -44,8 +42,8 @@ public function scheduled($id) $found = $this->map($this->scheduled->find(['_id' => $id])); - if (count($found) === 0) { - throw new Exception("Unable to find scheduled job with ObjectId('{$id}')"); + if (0 === count($found)) { + throw new \Exception("Unable to find scheduled job with ObjectId('{$id}')"); } return $found[0]; @@ -59,8 +57,8 @@ public function archived($id) $found = $this->map($this->archived->find(['_id' => $id])); - if (count($found) === 0) { - throw new Exception("Unable to find archived job with ObjectId('{$id}')"); + if (0 === count($found)) { + throw new \Exception("Unable to find archived job with ObjectId('{$id}')"); } return $found[0]; @@ -72,7 +70,7 @@ public function save(Job $job) $this->scheduled->replaceOne( ['_id' => $document['_id']], $document, - ['upsert' => true] + ['upsert' => true], ); } @@ -87,7 +85,7 @@ public function releaseAll($jobIds) { $result = $this->scheduled->updateMany( ['_id' => ['$in' => $jobIds]], - ['$set' => ['locked' => false, 'last_execution.crashed' => true]] + ['$set' => ['locked' => false, 'last_execution.crashed' => true]], ); return $result->getModifiedCount(); @@ -104,15 +102,15 @@ public function cleanArchived(T\Moment $upperLimit) [ 'last_execution.ended_at' => [ '$lte' => T\MongoDate::from($upperLimit), - ] + ], ], - ['projection' => ['_id' => 1]] + ['projection' => ['_id' => 1]], ); $deleted = 0; foreach ($documents as $document) { $this->archived->deleteOne(['_id' => $document['_id']]); - $deleted++; + ++$deleted; } return $deleted; @@ -123,7 +121,7 @@ public function cleanScheduled(T\Moment $upperLimit) $result = $this->scheduled->deleteMany([ 'created_at' => [ '$lte' => T\MongoDate::from($upperLimit), - ] + ], ]); return $result->getDeletedCount(); @@ -133,19 +131,19 @@ public function queued( $group = null, ?T\Moment $at = null, ?T\Moment $from = null, - array $query = [] + array $query = [], ) { - if ($at === null) { + if (null === $at) { $at = T\now(); } $query['scheduled_at']['$lte'] = T\MongoDate::from($at); - if ($from !== null) { + if (null !== $from) { $query['scheduled_at']['$gt'] = T\MongoDate::from($from); } - if ($group !== null) { + if (null !== $group) { $query['group'] = $group; } @@ -154,13 +152,13 @@ public function queued( public function postponed($group = null, ?T\Moment $at = null, array $query = []) { - if ($at === null) { + if (null === $at) { $at = T\now(); } $query['scheduled_at']['$gt'] = T\MongoDate::from($at); - if ($group !== null) { + if (null !== $group) { $query['group'] = $group; } @@ -169,7 +167,7 @@ public function postponed($group = null, ?T\Moment $at = null, array $query = [] public function scheduledCount($group = null, array $query = []) { - if ($group !== null) { + if (null !== $group) { $query['group'] = $group; } @@ -179,7 +177,7 @@ public function scheduledCount($group = null, array $query = []) public function queuedGroupedBy($field, array $query = [], $group = null) { $query['scheduled_at']['$lte'] = T\MongoDate::from(T\now()); - if ($group !== null) { + if (null !== $group) { $query['group'] = $group; } @@ -201,7 +199,7 @@ public function queuedGroupedBy($field, array $query = [], $group = null) public function recentHistory($group = null, ?T\Moment $at = null, array $query = []) { - if ($at === null) { + if (null === $at) { $at = T\now(); } $lastMinute = array_merge( @@ -209,11 +207,11 @@ public function recentHistory($group = null, ?T\Moment $at = null, array $query [ 'last_execution.ended_at' => [ '$gt' => T\MongoDate::from($at->before(T\minute(1))), - '$lte' => T\MongoDate::from($at) + '$lte' => T\MongoDate::from($at), ], - ] + ], ); - if ($group !== null) { + if (null !== $group) { $lastMinute['group'] = $group; } $cursor = $this->archived->aggregate($pipeline = [ @@ -237,22 +235,22 @@ public function recentHistory($group = null, ?T\Moment $at = null, array $query ]); $documents = $cursor->toArray(); - if (count($documents) === 0) { + if (0 === count($documents)) { $throughputPerMinute = 0.0; $averageLatency = 0.0; $averageExecutionTime = 0; - } elseif (count($documents) === 1) { + } elseif (1 === count($documents)) { $throughputPerMinute = (float) $documents[0]['throughput']; $averageLatency = $documents[0]['latency'] / 1000; $averageExecutionTime = $documents[0]['execution_time'] / 1000; } else { - throw new RuntimeException("Result was not ok: " . var_export($documents, true)); + throw new \RuntimeException('Result was not ok: ' . var_export($documents, true)); } return [ 'throughput' => [ 'value' => $throughputPerMinute, - 'value_per_second' => $throughputPerMinute/60.0, + 'value_per_second' => $throughputPerMinute / 60.0, ], 'latency' => [ 'average' => $averageLatency, @@ -266,35 +264,35 @@ public function recentHistory($group = null, ?T\Moment $at = null, array $query public function countSlowRecentJobs( T\Moment $lowerLimit, T\Moment $upperLimit, - $secondsToConsiderJobAsSlow = 5 + $secondsToConsiderJobAsSlow = 5, ): int { return count( $this->slowArchivedRecentJobs( $lowerLimit, $upperLimit, - $secondsToConsiderJobAsSlow - ) + $secondsToConsiderJobAsSlow, + ), ) + count( $this->slowScheduledRecentJobs( $lowerLimit, $upperLimit, - $secondsToConsiderJobAsSlow - ) + $secondsToConsiderJobAsSlow, + ), ); } public function countRecentJobsWithManyAttempts( T\Moment $lowerLimit, - T\Moment $upperLimit + T\Moment $upperLimit, ): int { return $this->countRecentArchivedOrScheduledJobsWithManyAttempts( $lowerLimit, $upperLimit, - 'archived' + 'archived', ) + $this->countRecentArchivedOrScheduledJobsWithManyAttempts( $lowerLimit, $upperLimit, - 'scheduled' + 'scheduled', ); } @@ -302,8 +300,8 @@ public function countDelayedScheduledJobs(T\Moment $lowerLimit): int { return $this->scheduled->count([ 'scheduled_at' => [ - '$lte' => T\MongoDate::from($lowerLimit) - ] + '$lte' => T\MongoDate::from($lowerLimit), + ], ]); } @@ -312,63 +310,65 @@ public function delayedScheduledJobs(T\Moment $lowerLimit) return $this->map( $this->scheduled->find([ 'scheduled_at' => [ - '$lte' => T\MongoDate::from($lowerLimit) - ] - ]) + '$lte' => T\MongoDate::from($lowerLimit), + ], + ]), ); } public function recentJobsWithManyAttempts( T\Moment $lowerLimit, - T\Moment $upperLimit + T\Moment $upperLimit, ) { $archived = $this->map( $this->recentArchivedOrScheduledJobsWithManyAttempts( $lowerLimit, $upperLimit, - 'archived' - ) + 'archived', + ), ); $scheduled = $this->map( $this->recentArchivedOrScheduledJobsWithManyAttempts( $lowerLimit, $upperLimit, - 'scheduled' - ) + 'scheduled', + ), ); + return array_merge($archived, $scheduled); } public function slowRecentJobs( T\Moment $lowerLimit, T\Moment $upperLimit, - $secondsToConsiderJobAsSlow = 5 + $secondsToConsiderJobAsSlow = 5, ) { - $archived= []; + $archived = []; $archivedArray = $this->slowArchivedRecentJobs( $lowerLimit, $upperLimit, - $secondsToConsiderJobAsSlow + $secondsToConsiderJobAsSlow, ); foreach ($archivedArray as $archivedJob) { $archived[] = Job::import($archivedJob, $this); } - $scheduled= []; + $scheduled = []; $scheduledArray = $this->slowScheduledRecentJobs( $lowerLimit, $upperLimit, - $secondsToConsiderJobAsSlow + $secondsToConsiderJobAsSlow, ); foreach ($scheduledArray as $scheduledJob) { $scheduled[] = Job::import($scheduledJob, $this); } + return array_merge($archived, $scheduled); } private function slowArchivedRecentJobs( T\Moment $lowerLimit, T\Moment $upperLimit, - $secondsToConsiderJobAsSlow + $secondsToConsiderJobAsSlow, ) { return $this->archived->aggregate([ [ @@ -376,7 +376,7 @@ private function slowArchivedRecentJobs( 'last_execution.ended_at' => [ '$gte' => T\MongoDate::from($lowerLimit), ], - ] + ], ], [ '$project' => [ @@ -384,8 +384,8 @@ private function slowArchivedRecentJobs( 'execution_time' => [ '$subtract' => [ '$last_execution.ended_at', - '$last_execution.started_at' - ] + '$last_execution.started_at', + ], ], 'done' => '$done', 'created_at' => '$created_at', @@ -397,13 +397,13 @@ private function slowArchivedRecentJobs( 'scheduled_at' => '$scheduled_at', 'last_execution' => '$last_execution', 'retry_policy' => '$retry_policy', - ] + ], ], [ '$match' => [ 'execution_time' => [ - '$gt' => $secondsToConsiderJobAsSlow*1000 - ] + '$gt' => $secondsToConsiderJobAsSlow * 1000, + ], ], ], ])->toArray(); @@ -412,14 +412,14 @@ private function slowArchivedRecentJobs( private function slowScheduledRecentJobs( T\Moment $lowerLimit, T\Moment $upperLimit, - $secondsToConsiderJobAsSlow + $secondsToConsiderJobAsSlow, ) { return $this->scheduled->aggregate([ [ '$match' => [ 'scheduled_at' => [ '$gte' => T\MongoDate::from($lowerLimit), - '$lte' => T\MongoDate::from($upperLimit) + '$lte' => T\MongoDate::from($upperLimit), ], 'last_execution.started_at' => [ '$exists' => true, @@ -427,7 +427,7 @@ private function slowScheduledRecentJobs( 'last_execution.ended_at' => [ '$exists' => true, ], - ] + ], ], [ '$project' => [ @@ -435,8 +435,8 @@ private function slowScheduledRecentJobs( 'execution_time' => [ '$subtract' => [ '$last_execution.ended_at', - '$last_execution.started_at' - ] + '$last_execution.started_at', + ], ], 'done' => '$done', 'created_at' => '$created_at', @@ -448,13 +448,13 @@ private function slowScheduledRecentJobs( 'scheduled_at' => '$scheduled_at', 'last_execution' => '$last_execution', 'retry_policy' => '$retry_policy', - ] + ], ], [ '$match' => [ 'execution_time' => [ - '$gt' => $secondsToConsiderJobAsSlow*1000 - ] + '$gt' => $secondsToConsiderJobAsSlow * 1000, + ], ], ], ])->toArray(); @@ -463,28 +463,28 @@ private function slowScheduledRecentJobs( private function countRecentArchivedOrScheduledJobsWithManyAttempts( T\Moment $lowerLimit, T\Moment $upperLimit, - $collectionName + $collectionName, ) { return count($this->recentArchivedOrScheduledJobsWithManyAttempts( $lowerLimit, $upperLimit, - $collectionName + $collectionName, )->toArray()); } private function recentArchivedOrScheduledJobsWithManyAttempts( T\Moment $lowerLimit, T\Moment $upperLimit, - $collectionName + $collectionName, ) { return $this->{$collectionName}->find([ 'last_execution.ended_at' => [ '$gte' => T\MongoDate::from($lowerLimit), - '$lte' => T\MongoDate::from($upperLimit) - ], - 'attempts' => [ - '$gt' => 1 - ] + '$lte' => T\MongoDate::from($upperLimit), + ], + 'attempts' => [ + '$gt' => 1, + ], ]); } diff --git a/src/Recruiter/JobAfterFailure.php b/src/Recruiter/JobAfterFailure.php index 9f1d60e7..ba6fdd93 100644 --- a/src/Recruiter/JobAfterFailure.php +++ b/src/Recruiter/JobAfterFailure.php @@ -2,9 +2,8 @@ namespace Recruiter; -use Timeless\Moment; use Timeless\Interval; -use Timeless\MongoDate; +use Timeless\Moment; class JobAfterFailure { @@ -76,8 +75,10 @@ public function archiveIfNotScheduled() { if (!$this->hasBeenScheduled && !$this->hasBeenArchived) { $this->archive('not-scheduled-by-retry-policy'); + return true; } + return false; } diff --git a/src/Recruiter/JobExecution.php b/src/Recruiter/JobExecution.php index 3e111d62..4c321b73 100644 --- a/src/Recruiter/JobExecution.php +++ b/src/Recruiter/JobExecution.php @@ -3,7 +3,6 @@ namespace Recruiter; use Timeless as T; -use Throwable; class JobExecution { @@ -25,7 +24,7 @@ public function started($scheduledAt = null) $this->startedAt = T\now(); } - public function failedWith(Throwable $exception) + public function failedWith(\Throwable $exception) { $this->endedAt = T\now(); $this->failedWith = $exception; @@ -57,9 +56,10 @@ public function duration() if ($this->startedAt && $this->endedAt && ($this->startedAt <= $this->endedAt)) { return T\seconds( $this->endedAt->seconds() - - $this->startedAt-> seconds() + $this->startedAt->seconds(), ); } + return T\seconds(0); } @@ -78,6 +78,7 @@ public static function import($document) $lastExecution->startedAt = T\MongoDate::toMoment($lastExecutionDocument['started_at']); } } + return $lastExecution; } @@ -111,7 +112,7 @@ public function export() private function traceOf($result) { $trace = 'ok'; - if ($result instanceof Throwable) { + if ($result instanceof \Throwable) { $trace = $result->getTraceAsString(); } elseif (is_object($result) && method_exists($result, 'trace')) { $trace = $result->trace(); @@ -120,6 +121,7 @@ private function traceOf($result) } elseif (is_string($result) || is_numeric($result)) { $trace = $result; } + return substr($trace, 0, 4096); } } diff --git a/src/Recruiter/JobToSchedule.php b/src/Recruiter/JobToSchedule.php index 880b209a..b6a21cff 100644 --- a/src/Recruiter/JobToSchedule.php +++ b/src/Recruiter/JobToSchedule.php @@ -2,8 +2,6 @@ namespace Recruiter; -use Recruiter\Job; -use Recruiter\RetryPolicy; use Symfony\Component\EventDispatcher\EventDispatcher; use Timeless as T; use Timeless\Interval; @@ -32,9 +30,10 @@ public function retryManyTimes($howManyTimes, Interval $timeToWaitBeforeRetry, $ $this->job->retryWithPolicy( $this->filterForRetriableExceptions( new RetryPolicy\RetryManyTimes($howManyTimes, $timeToWaitBeforeRetry), - $retriableExceptionTypes - ) + $retriableExceptionTypes, + ), ); + return $this; } @@ -43,9 +42,10 @@ public function retryWithPolicy(RetryPolicy $retryPolicy, $retriableExceptionTyp $this->job->retryWithPolicy( $this->filterForRetriableExceptions( $retryPolicy, - $retriableExceptionTypes - ) + $retriableExceptionTypes, + ), ); + return $this; } @@ -63,6 +63,7 @@ public function scheduleAt(Moment $momentInTime) { $this->mustBeScheduled = true; $this->job->scheduleAt($momentInTime); + return $this; } @@ -105,6 +106,7 @@ public function execute() } else { $this->job->execute($this->emptyEventDispatcher()); } + return (string) $this->job->id(); } @@ -116,6 +118,7 @@ private function emptyEventDispatcher() public function __call($name, $arguments) { $this->job->methodToCallOnWorkable($name); + return $this->execute(); } @@ -137,6 +140,7 @@ private function filterForRetriableExceptions($retryPolicy, $retriableExceptionT if (!empty($retriableExceptionTypes)) { $retryPolicy = new RetryPolicy\RetriableExceptionFilter($retryPolicy, $retriableExceptionTypes); } + return $retryPolicy; } } diff --git a/src/Recruiter/Recruiter.php b/src/Recruiter/Recruiter.php index f9dea2e8..d1e58dff 100644 --- a/src/Recruiter/Recruiter.php +++ b/src/Recruiter/Recruiter.php @@ -1,13 +1,9 @@ jobs) + Job::around($workable, $this->jobs), ); } @@ -63,7 +59,7 @@ public function queuedGroupedBy($field, array $query = [], $group = null) } /** - * @deprecated use the method `analytics` instead. + * @deprecated use the method `analytics` instead */ public function statistics($group = null, ?Moment $at = null, array $query = []) { @@ -84,7 +80,7 @@ public function analytics($group = null, ?Moment $at = null, array $query = []) 'zombies' => $totalsScheduledJobs - ($queued + $postponed), ], ], - $this->jobs->recentHistory($group, $at, $query) + $this->jobs->recentHistory($group, $at, $query), ); } @@ -95,11 +91,13 @@ public function getEventDispatcher() /** * @step - * @return integer how many + * + * @return int how many */ public function rollbackLockedJobs() { $assignedJobs = Worker::assignedJobs($this->db->selectCollection('roster')); + return Job::rollbackLockedNotIn($this->db->selectCollection('scheduled'), $assignedJobs); } @@ -134,17 +132,18 @@ public function bookJobsForWorkers() $bookedJobs = []; foreach (Worker::pickAvailableWorkers($roster, $workersPerUnit) as $resultRow) { - list ($worksOn, $workers) = $resultRow; + [$worksOn, $workers] = $resultRow; $result = Job::pickReadyJobsForWorkers($scheduled, $worksOn, $workers); if ($result) { - list($worksOn, $workers, $jobs) = $result; - list($assignments, $jobs, $workers) = $this->combineJobsWithWorkers($jobs, $workers); + [$worksOn, $workers, $jobs] = $result; + [$assignments, $jobs, $workers] = $this->combineJobsWithWorkers($jobs, $workers); Job::lockAll($scheduled, $jobs); $bookedJobs[] = [$jobs, $workers]; } } + return $bookedJobs; } @@ -157,14 +156,14 @@ public function assignLockedJobsToWorkers($bookedJobs) $totalActualAssignments = 0; $roster = $this->db->selectCollection('roster'); foreach ($bookedJobs as $row) { - list ($jobs, $workers, ) = $row; - list ($newAssignments, $actualAssignmentsNumber) = Worker::tryToAssignJobsToWorkers($roster, $jobs, $workers); + [$jobs, $workers] = $row; + [$newAssignments, $actualAssignmentsNumber] = Worker::tryToAssignJobsToWorkers($roster, $jobs, $workers); if (array_intersect_key($assignments, $newAssignments)) { - throw new RuntimeException("Conflicting assignments: current were " . var_export($assignments, true) . " and we want to also assign " . var_export($newAssignments, true)); + throw new \RuntimeException('Conflicting assignments: current were ' . var_export($assignments, true) . ' and we want to also assign ' . var_export($newAssignments, true)); } $assignments = array_merge( $assignments, - $newAssignments + $newAssignments, ); $totalActualAssignments += $actualAssignmentsNumber; } @@ -173,7 +172,7 @@ public function assignLockedJobsToWorkers($bookedJobs) array_map(function ($value) { return (string) $value; }, $assignments), - $totalActualAssignments + $totalActualAssignments, ]; } @@ -184,12 +183,13 @@ public function scheduledJob($id) /** * @step - * @return integer how many jobs were unlocked as a result + * + * @return int how many jobs were unlocked as a result */ - public function retireDeadWorkers(DateTimeImmutable $now, Interval $consideredDeadAfter) + public function retireDeadWorkers(\DateTimeImmutable $now, Interval $consideredDeadAfter) { return $this->jobs->releaseAll( - $jobsAssignedToDeadWorkers = Worker::retireDeadWorkers($this->workers, $now, $consideredDeadAfter) + $jobsAssignedToDeadWorkers = Worker::retireDeadWorkers($this->workers, $now, $consideredDeadAfter), ); } @@ -212,59 +212,59 @@ public function createCollectionsAndIndexes() 'locked' => 1, 'scheduled_at' => 1, ], - ['background' => true] + ['background' => true], ); $this->db->selectCollection('scheduled')->createIndex( [ 'locked' => 1, 'scheduled_at' => 1, ], - ['background' => true] + ['background' => true], ); $this->db->selectCollection('scheduled')->createIndex( [ 'locked' => 1, ], - ['background' => true] + ['background' => true], ); $this->db->selectCollection('scheduled')->createIndex( [ 'tags' => 1, ], - ['background' => true, 'sparse' => true] + ['background' => true, 'sparse' => true], ); $this->db->selectCollection('archived')->createIndex( [ 'created_at' => 1, ], - ['background' => true] + ['background' => true], ); $this->db->selectCollection('archived')->createIndex( [ 'created_at' => 1, 'group' => 1, ], - ['background' => true] + ['background' => true], ); $this->db->selectCollection('archived')->createIndex( [ 'last_execution.ended_at' => 1, ], - ['background' => true] + ['background' => true], ); $this->db->selectCollection('roster')->createIndex( [ 'available' => 1, ], - ['background' => true] + ['background' => true], ); $this->db->selectCollection('roster')->createIndex( [ 'last_seen_at' => 1, ], - ['background' => true] + ['background' => true], ); } @@ -273,6 +273,7 @@ private function combineJobsWithWorkers($jobs, $workers) $assignments = min(count($workers), count($jobs)); $workers = array_slice($workers, 0, $assignments); $jobs = array_slice($jobs, 0, $assignments); + return [$assignments, $jobs, $workers]; } } diff --git a/src/Recruiter/Repeatable.php b/src/Recruiter/Repeatable.php index 54a066d9..80cc6c0b 100644 --- a/src/Recruiter/Repeatable.php +++ b/src/Recruiter/Repeatable.php @@ -6,9 +6,7 @@ interface Repeatable extends Workable { /** * Assign an unique name to the scheduler in order to handle idempotency, - * only one scheduler with the same urn can exists - * - * @return string + * only one scheduler with the same urn can exists. */ public function urn(): string; @@ -19,8 +17,6 @@ public function urn(): string; * * true: only one job at a time can be queued * false: there may be more concurrent jobs at a time - * - * @return boolean */ public function unique(): bool; } diff --git a/src/Recruiter/RepeatableInJob.php b/src/Recruiter/RepeatableInJob.php index 40f7af62..5546e4f8 100644 --- a/src/Recruiter/RepeatableInJob.php +++ b/src/Recruiter/RepeatableInJob.php @@ -1,8 +1,7 @@ self::classNameOf($workable), 'parameters' => $workable->export(), 'method' => $methodToCall, - ] + ], ]; } @@ -61,6 +60,7 @@ private static function classNameOf($repeatable): string if (method_exists($repeatable, 'getClass')) { $repeatableClassName = $repeatable->getClass(); } + return $repeatableClassName; } } diff --git a/src/Recruiter/Retriable.php b/src/Recruiter/Retriable.php index 89e3d369..67080a88 100644 --- a/src/Recruiter/Retriable.php +++ b/src/Recruiter/Retriable.php @@ -5,9 +5,7 @@ interface Retriable { /** - * Declare what instance of `Recruiter\RetryPolicy` should be used for a `Recruiter\Workable` - * - * @return RetryPolicy + * Declare what instance of `Recruiter\RetryPolicy` should be used for a `Recruiter\Workable`. */ public function retryWithPolicy(): RetryPolicy; } diff --git a/src/Recruiter/RetryPolicy.php b/src/Recruiter/RetryPolicy.php index 0c462c65..4acb51b5 100644 --- a/src/Recruiter/RetryPolicy.php +++ b/src/Recruiter/RetryPolicy.php @@ -7,36 +7,29 @@ interface RetryPolicy /** * Decide whether or not to reschedule a job. If you want to reschedule the * job use the appropriate methods on job or do nothing to if you don't - * want to execute the job again + * want to execute the job again. * * This method can * - schedule the job * - archive the job * - do nothing (and the job will be archived anyway) * - * @param JobAfterFailure $job - * * @return void */ public function schedule(JobAfterFailure $job); /** - * Export retry policy parameters - * - * @return array + * Export retry policy parameters. */ public function export(): array; /** - * Import retry policy parameters + * Import retry policy parameters. * * @param array $parameters Previously exported parameters - * - * @return RetryPolicy */ public static function import(array $parameters): RetryPolicy; - /** * @return bool true if is the last retry */ diff --git a/src/Recruiter/RetryPolicy/DoNotDoItAgain.php b/src/Recruiter/RetryPolicy/DoNotDoItAgain.php index b7b28791..5ae8288a 100644 --- a/src/Recruiter/RetryPolicy/DoNotDoItAgain.php +++ b/src/Recruiter/RetryPolicy/DoNotDoItAgain.php @@ -3,9 +3,9 @@ namespace Recruiter\RetryPolicy; use Recruiter\Job; +use Recruiter\JobAfterFailure; use Recruiter\RetryPolicy; use Recruiter\RetryPolicyBehaviour; -use Recruiter\JobAfterFailure; class DoNotDoItAgain implements RetryPolicy { diff --git a/src/Recruiter/RetryPolicy/ExponentialBackoff.php b/src/Recruiter/RetryPolicy/ExponentialBackoff.php index 4264dd68..9db868fc 100644 --- a/src/Recruiter/RetryPolicy/ExponentialBackoff.php +++ b/src/Recruiter/RetryPolicy/ExponentialBackoff.php @@ -3,20 +3,18 @@ namespace Recruiter\RetryPolicy; use Recruiter\Job; +use Recruiter\JobAfterFailure; use Recruiter\RetryPolicy; use Recruiter\RetryPolicyBehaviour; -use Recruiter\JobAfterFailure; - use Timeless as T; use Timeless\Interval; class ExponentialBackoff implements RetryPolicy { + use RetryPolicyBehaviour; private $retryHowManyTimes; private $timeToInitiallyWaitBeforeRetry; - use RetryPolicyBehaviour; - public static function forTimes($retryHowManyTimes, $timeToInitiallyWaitBeforeRetry = 60) { return new static($retryHowManyTimes, $timeToInitiallyWaitBeforeRetry); @@ -38,8 +36,9 @@ public static function forAnInterval($interval, $timeToInitiallyWaitBeforeRetry) } $numberOfRetries = round( log($interval / $timeToInitiallyWaitBeforeRetry->seconds()) - / log(2) + / log(2), ); + return new static($numberOfRetries, $timeToInitiallyWaitBeforeRetry); } @@ -66,7 +65,7 @@ public function export(): array { return [ 'retry_how_many_times' => $this->retryHowManyTimes, - 'seconds_to_initially_wait_before_retry' => $this->timeToInitiallyWaitBeforeRetry->seconds() + 'seconds_to_initially_wait_before_retry' => $this->timeToInitiallyWaitBeforeRetry->seconds(), ]; } @@ -74,7 +73,7 @@ public static function import(array $parameters): RetryPolicy { return new self( $parameters['retry_how_many_times'], - T\seconds($parameters['seconds_to_initially_wait_before_retry']) + T\seconds($parameters['seconds_to_initially_wait_before_retry']), ); } diff --git a/src/Recruiter/RetryPolicy/RetriableException.php b/src/Recruiter/RetryPolicy/RetriableException.php index 59087f74..30546a73 100644 --- a/src/Recruiter/RetryPolicy/RetriableException.php +++ b/src/Recruiter/RetryPolicy/RetriableException.php @@ -1,9 +1,8 @@ exceptionClass = $exceptionClass; $this->retryPolicy = $retryPolicy; diff --git a/src/Recruiter/RetryPolicy/RetriableExceptionFilter.php b/src/Recruiter/RetryPolicy/RetriableExceptionFilter.php index 10a5913b..bc4e2203 100644 --- a/src/Recruiter/RetryPolicy/RetriableExceptionFilter.php +++ b/src/Recruiter/RetryPolicy/RetriableExceptionFilter.php @@ -2,11 +2,9 @@ namespace Recruiter\RetryPolicy; -use InvalidArgumentException; - use Recruiter\Job; -use Recruiter\RetryPolicy; use Recruiter\JobAfterFailure; +use Recruiter\RetryPolicy; class RetriableExceptionFilter implements RetryPolicy { @@ -14,7 +12,8 @@ class RetriableExceptionFilter implements RetryPolicy private $retriableExceptions; /** - * @param string $exceptionClass fully qualified class or interface name + * @param string $exceptionClass fully qualified class or interface name + * * @return self */ public static function onlyFor($exceptionClass, RetryPolicy $retryPolicy) @@ -43,8 +42,8 @@ public function export(): array 'retriable_exceptions' => $this->retriableExceptions, 'filtered_retry_policy' => [ 'class' => get_class($this->filteredRetryPolicy), - 'parameters' => $this->filteredRetryPolicy->export() - ] + 'parameters' => $this->filteredRetryPolicy->export(), + ], ]; } @@ -52,9 +51,10 @@ public static function import(array $parameters): RetryPolicy { $filteredRetryPolicy = $parameters['filtered_retry_policy']; $retriableExceptions = $parameters['retriable_exceptions']; + return new self( $filteredRetryPolicy['class']::import($filteredRetryPolicy['parameters']), - $retriableExceptions + $retriableExceptions, ); } @@ -67,11 +67,10 @@ private function ensureAreAllExceptions($exceptions) { foreach ($exceptions as $exception) { if (!is_a($exception, 'Throwable', true)) { - throw new InvalidArgumentException( - "Only subclasses of Exception can be retriable exceptions, '{$exception}' is not" - ); + throw new \InvalidArgumentException("Only subclasses of Exception can be retriable exceptions, '{$exception}' is not"); } } + return $exceptions; } @@ -81,10 +80,11 @@ private function isExceptionRetriable($exception) return array_any( $this->retriableExceptions, function ($retriableExceptionType) use ($exception) { - return ($exception instanceof $retriableExceptionType); - } + return $exception instanceof $retriableExceptionType; + }, ); } + return false; } } diff --git a/src/Recruiter/RetryPolicy/RetryForever.php b/src/Recruiter/RetryPolicy/RetryForever.php index 0eeb491e..57105fd1 100644 --- a/src/Recruiter/RetryPolicy/RetryForever.php +++ b/src/Recruiter/RetryPolicy/RetryForever.php @@ -1,19 +1,18 @@ $this->timeToWaitBeforeRetry->seconds() + 'seconds_to_wait_before_retry' => $this->timeToWaitBeforeRetry->seconds(), ]; } public static function import(array $parameters): RetryPolicy { return new self( - T\seconds($parameters['seconds_to_wait_before_retry']) + T\seconds($parameters['seconds_to_wait_before_retry']), ); } diff --git a/src/Recruiter/RetryPolicy/RetryManyTimes.php b/src/Recruiter/RetryPolicy/RetryManyTimes.php index 31e99ba2..fa599ebe 100644 --- a/src/Recruiter/RetryPolicy/RetryManyTimes.php +++ b/src/Recruiter/RetryPolicy/RetryManyTimes.php @@ -1,21 +1,20 @@ $this->retryHowManyTimes, - 'seconds_to_wait_before_retry' => $this->timeToWaitBeforeRetry->seconds() + 'seconds_to_wait_before_retry' => $this->timeToWaitBeforeRetry->seconds(), ]; } @@ -51,7 +50,7 @@ public static function import(array $parameters): RetryPolicy { return new self( $parameters['retry_how_many_times'], - T\seconds($parameters['seconds_to_wait_before_retry']) + T\seconds($parameters['seconds_to_wait_before_retry']), ); } diff --git a/src/Recruiter/RetryPolicy/SelectByException.php b/src/Recruiter/RetryPolicy/SelectByException.php index 0a2841db..98d814f4 100644 --- a/src/Recruiter/RetryPolicy/SelectByException.php +++ b/src/Recruiter/RetryPolicy/SelectByException.php @@ -3,14 +3,12 @@ namespace Recruiter\RetryPolicy; use Exception; -use InvalidArgumentException; use Recruiter\Job; use Recruiter\JobAfterFailure; use Recruiter\RetryPolicy; -use Throwable; /** - * Select retry policies based on the raised exception + * Select retry policies based on the raised exception. * * If a job fails with an exception it's possible to select a retry * policy instance based on the class of the exception. The exception @@ -41,9 +39,6 @@ public function __construct(array $exceptions) $this->exceptions = $exceptions; } - /** - * {@inheritDoc} - */ public function schedule(JobAfterFailure $job) { $exception = $job->causeOfFailure(); @@ -54,56 +49,49 @@ public function schedule(JobAfterFailure $job) } } - /** - * {@inheritDoc} - */ public function export(): array { return array_map( - function(RetriableException $retriableException) { + function (RetriableException $retriableException) { $retryPolicy = $retriableException->retryPolicy(); + return [ 'when' => $retriableException->exceptionClass(), 'then' => [ 'class' => get_class($retryPolicy), - 'parameters' => $retryPolicy->export() - ] + 'parameters' => $retryPolicy->export(), + ], ]; }, - $this->exceptions + $this->exceptions, ); } - /** - * {@inheritDoc} - */ public static function import(array $parameters): RetryPolicy { return new self( array_reduce( $parameters, - function($exceptions, $parameters) { + function ($exceptions, $parameters) { $exceptionClass = $parameters['when']; $retryPolicyClass = $parameters['then']['class']; $retryPolicyParameters = $parameters['then']['parameters']; $exceptions[] = new RetriableException($exceptionClass, $retryPolicyClass::import($retryPolicyParameters)); + return $exceptions; - } - ) + }, + ), ); } - /** - * {@inheritDoc} - */ public function isLastRetry(Job $job): bool { // I cannot answer to that so... true only if everybody says true return array_all( $this->exceptions, - function(RetriableException $retriableException) use ($job) { + function (RetriableException $retriableException) use ($job) { return $retriableException->retryPolicy()->isLastRetry($job); - } + }, ); } @@ -111,8 +99,9 @@ private function isRetriable($exception): bool { try { $this->retryPolicyFor($exception); + return true; - } catch (Exception $e) { + } catch (\Exception $e) { return false; } } @@ -127,14 +116,10 @@ private function retryPolicyFor(?object $exception): RetryPolicy return $retriableException->retryPolicy(); } } - if ($exception instanceof Throwable) { - throw new Exception( - 'Unable to find a RetryPolicy associated to exception: ' . get_class($exception), 0, $exception - ); + if ($exception instanceof \Throwable) { + throw new \Exception('Unable to find a RetryPolicy associated to exception: ' . get_class($exception), 0, $exception); } } - throw new Exception( - 'Unable to find a RetryPolicy associated to: ' . var_export($exception, true) - ); + throw new \Exception('Unable to find a RetryPolicy associated to: ' . var_export($exception, true)); } } diff --git a/src/Recruiter/RetryPolicy/SelectByExceptionBuilder.php b/src/Recruiter/RetryPolicy/SelectByExceptionBuilder.php index b02f6602..6fa68c5c 100644 --- a/src/Recruiter/RetryPolicy/SelectByExceptionBuilder.php +++ b/src/Recruiter/RetryPolicy/SelectByExceptionBuilder.php @@ -1,12 +1,9 @@ currentException = $exceptionClass; + return $this; } @@ -39,6 +37,7 @@ public function then(RetryPolicy $retryPolicy): self } $this->exceptions[] = new RetriableException($this->currentException, $retryPolicy); $this->currentException = null; + return $this; } @@ -50,6 +49,7 @@ public function build(): SelectByException if (empty($this->exceptions)) { throw new LogicException('No retry policies has been specified. Use `$builder->when($e)->then($r)`'); } + return new SelectByException($this->exceptions); } } diff --git a/src/Recruiter/RetryPolicy/TimeTable.php b/src/Recruiter/RetryPolicy/TimeTable.php index 0231b57c..b7d65d19 100644 --- a/src/Recruiter/RetryPolicy/TimeTable.php +++ b/src/Recruiter/RetryPolicy/TimeTable.php @@ -3,23 +3,19 @@ namespace Recruiter\RetryPolicy; use Recruiter\Job; +use Recruiter\JobAfterFailure; use Recruiter\RetryPolicy; use Recruiter\RetryPolicyBehaviour; -use Recruiter\JobAfterFailure; - use Timeless as T; -use Exception; - class TimeTable implements RetryPolicy { + use RetryPolicyBehaviour; /** @var array */ private $timeTable; private $howManyRetries; - use RetryPolicyBehaviour; - public function __construct(?array $timeTable) { if (is_null($timeTable)) { @@ -47,6 +43,7 @@ public function isLastRetry(Job $job): bool { $timeSpents = array_keys($this->timeTable); $timeSpent = end($timeSpents); + return !$this->hasBeenCreatedLessThan($job, $timeSpent); } @@ -63,14 +60,14 @@ public static function import(array $parameters): RetryPolicy private function hasBeenCreatedLessThan($job, $relativeTime) { return $job->createdAt()->isAfter( - T\Moment::fromTimestamp(strtotime($relativeTime, T\now()->seconds())) + T\Moment::fromTimestamp(strtotime($relativeTime, T\now()->seconds())), ); } private function rescheduleIn($job, $relativeTime) { $job->scheduleAt( - T\Moment::fromTimestamp(strtotime($relativeTime, T\now()->seconds())) + T\Moment::fromTimestamp(strtotime($relativeTime, T\now()->seconds())), ); } @@ -82,23 +79,18 @@ private static function estimateHowManyRetriesIn($timeTable) foreach ($timeTable as $timeWindow => $rescheduleTime) { $timeWindowInSeconds = ($now - strtotime($timeWindow, $now)) - $timeWindowInSeconds; if ($timeWindowInSeconds <= 0) { - throw new Exception( - "Time window `$timeWindow` is invalid, must be in the past" - ); + throw new \Exception("Time window `$timeWindow` is invalid, must be in the past"); } $rescheduleTimeInSeconds = (strtotime($rescheduleTime, $now) - $now); if ($rescheduleTimeInSeconds <= 0) { - throw new Exception( - "Reschedule time `$rescheduleTime` is invalid, must be in the future" - ); + throw new \Exception("Reschedule time `$rescheduleTime` is invalid, must be in the future"); } if ($rescheduleTimeInSeconds > $timeWindowInSeconds) { - throw new Exception( - "Reschedule time `$rescheduleTime` is invalid, must be greater than the time window" - ); + throw new \Exception("Reschedule time `$rescheduleTime` is invalid, must be greater than the time window"); } $howManyRetries += floor($timeWindowInSeconds / $rescheduleTimeInSeconds); } + return $howManyRetries; } } diff --git a/src/Recruiter/RetryPolicyBehaviour.php b/src/Recruiter/RetryPolicyBehaviour.php index 9944ce04..d934bec6 100644 --- a/src/Recruiter/RetryPolicyBehaviour.php +++ b/src/Recruiter/RetryPolicyBehaviour.php @@ -2,9 +2,7 @@ namespace Recruiter; -use Exception; use Recruiter\RetryPolicy\RetriableExceptionFilter; -use Recruiter\JobAfterFailure; trait RetryPolicyBehaviour { @@ -27,7 +25,7 @@ public function retryOnlyWhenExceptionsAre($retriableExceptionTypes) public function schedule(JobAfterFailure $job) { - throw new Exception('RetryPolicy::schedule(JobAfterFailure) need to be implemented'); + throw new \Exception('RetryPolicy::schedule(JobAfterFailure) need to be implemented'); } public function export(): array diff --git a/src/Recruiter/RetryPolicyInJob.php b/src/Recruiter/RetryPolicyInJob.php index 5f34814f..625645dc 100644 --- a/src/Recruiter/RetryPolicyInJob.php +++ b/src/Recruiter/RetryPolicyInJob.php @@ -2,26 +2,24 @@ namespace Recruiter; -use Exception; -use Recruiter\RetryPolicy; - class RetryPolicyInJob { public static function import($document) { if (!array_key_exists('retry_policy', $document)) { - throw new Exception('Unable to import Job without data about RetryPolicy object'); + throw new \Exception('Unable to import Job without data about RetryPolicy object'); } $dataAboutRetryPolicyObject = $document['retry_policy']; if (!array_key_exists('class', $dataAboutRetryPolicyObject)) { - throw new Exception('Unable to import Job without a class'); + throw new \Exception('Unable to import Job without a class'); } if (!class_exists($dataAboutRetryPolicyObject['class'])) { - throw new Exception('Unable to import Job with unknown RetryPolicy class'); + throw new \Exception('Unable to import Job with unknown RetryPolicy class'); } if (!method_exists($dataAboutRetryPolicyObject['class'], 'import')) { - throw new Exception('Unable to import RetryPolicy without method import'); + throw new \Exception('Unable to import RetryPolicy without method import'); } + return $dataAboutRetryPolicyObject['class']::import($dataAboutRetryPolicyObject['parameters']); } @@ -31,7 +29,7 @@ public static function export($retryPolicy) 'retry_policy' => [ 'class' => get_class($retryPolicy), 'parameters' => $retryPolicy->export(), - ] + ], ]; } diff --git a/src/Recruiter/SchedulePolicy.php b/src/Recruiter/SchedulePolicy.php index 21aeadd3..f10c701a 100644 --- a/src/Recruiter/SchedulePolicy.php +++ b/src/Recruiter/SchedulePolicy.php @@ -7,25 +7,19 @@ interface SchedulePolicy { /** - * Returns the next time the job is to be executed - * - * @return Moment + * Returns the next time the job is to be executed. */ public function next(): Moment; /** - * Export schedule policy parameters - * - * @return array + * Export schedule policy parameters. */ public function export(): array; /** - * Import schedule policy parameters + * Import schedule policy parameters. * * @param array $parameters Previously exported parameters - * - * @return SchedulePolicy */ public static function import(array $parameters): SchedulePolicy; } diff --git a/src/Recruiter/SchedulePolicy/Cron.php b/src/Recruiter/SchedulePolicy/Cron.php index 24afd0cc..8a3825fe 100644 --- a/src/Recruiter/SchedulePolicy/Cron.php +++ b/src/Recruiter/SchedulePolicy/Cron.php @@ -3,10 +3,7 @@ namespace Recruiter\SchedulePolicy; use Cron\CronExpression; -use DateInterval; -use DateTime; use Recruiter\SchedulePolicy; - use Timeless\Moment; class Cron implements SchedulePolicy @@ -14,7 +11,7 @@ class Cron implements SchedulePolicy private $cronExpression; private $now; - public function __construct(string $cronExpression, ?DateTime $now = null) + public function __construct(string $cronExpression, ?\DateTime $now = null) { $this->cronExpression = $cronExpression; $this->now = $now; @@ -23,7 +20,7 @@ public function __construct(string $cronExpression, ?DateTime $now = null) public function next(): Moment { return Moment::fromDateTime( - CronExpression::factory($this->cronExpression)->getNextRunDate($this->now ?? 'now') + CronExpression::factory($this->cronExpression)->getNextRunDate($this->now ?? 'now'), ); } @@ -39,8 +36,8 @@ public static function import(array $parameters): SchedulePolicy { $now = null; if (isset($parameters['now'])) { - $now = DateTime::createFromFormat('U', $parameters['now']); - $now = $now === false ? null : $now; + $now = \DateTime::createFromFormat('U', $parameters['now']); + $now = false === $now ? null : $now; } return new self($parameters['cron_expression'], $now); diff --git a/src/Recruiter/SchedulePolicy/EveryMinutes.php b/src/Recruiter/SchedulePolicy/EveryMinutes.php index 7245687e..908e5d55 100644 --- a/src/Recruiter/SchedulePolicy/EveryMinutes.php +++ b/src/Recruiter/SchedulePolicy/EveryMinutes.php @@ -2,9 +2,7 @@ namespace Recruiter\SchedulePolicy; -use DateInterval; use Recruiter\SchedulePolicy; - use Timeless\Moment; class EveryMinutes implements SchedulePolicy diff --git a/src/Recruiter/SchedulePolicyInJob.php b/src/Recruiter/SchedulePolicyInJob.php index d1671ce6..3f376346 100644 --- a/src/Recruiter/SchedulePolicyInJob.php +++ b/src/Recruiter/SchedulePolicyInJob.php @@ -2,26 +2,24 @@ namespace Recruiter; -use Exception; -use Recruiter\SchedulePolicy; - class SchedulePolicyInJob { public static function import($document): SchedulePolicy { if (!array_key_exists('schedule_policy', $document)) { - throw new Exception('Unable to import Job without data about SchedulePolicy object'); + throw new \Exception('Unable to import Job without data about SchedulePolicy object'); } $dataAboutSchedulePolicyObject = $document['schedule_policy']; if (!array_key_exists('class', $dataAboutSchedulePolicyObject)) { - throw new Exception('Unable to import Job without a SchedulePolicy class'); + throw new \Exception('Unable to import Job without a SchedulePolicy class'); } if (!class_exists($dataAboutSchedulePolicyObject['class'])) { - throw new Exception('Unable to import Job with unknown SchedulePolicy class'); + throw new \Exception('Unable to import Job with unknown SchedulePolicy class'); } if (!method_exists($dataAboutSchedulePolicyObject['class'], 'import')) { - throw new Exception('Unable to import SchedulePolicy without method import'); + throw new \Exception('Unable to import SchedulePolicy without method import'); } + return $dataAboutSchedulePolicyObject['class']::import($dataAboutSchedulePolicyObject['parameters']); } @@ -31,7 +29,7 @@ public static function export($schedulePolicy) 'schedule_policy' => [ 'class' => get_class($schedulePolicy), 'parameters' => $schedulePolicy->export(), - ] + ], ]; } diff --git a/src/Recruiter/Scheduler.php b/src/Recruiter/Scheduler.php index 3c5a73e1..b6d8dd36 100644 --- a/src/Recruiter/Scheduler.php +++ b/src/Recruiter/Scheduler.php @@ -2,16 +2,9 @@ namespace Recruiter; -use MongoDB\BSON\ObjectId; -use Recruiter\Scheduler\Repository; use Recruiter\Job\Repository as JobsRepository; -use Recruiter\RetryPolicy; -use RuntimeException; -use Symfony\Component\EventDispatcher\EventDispatcher; -use Throwable; +use Recruiter\Scheduler\Repository; use Timeless as T; -use Timeless\Interval; -use Timeless\Moment; class Scheduler { @@ -31,15 +24,14 @@ public static function around(Repeatable $repeatable, Repository $repository, Re { $retryPolicy = ($repeatable instanceof Retriable) ? $repeatable->retryWithPolicy() : - new RetryPolicy\DoNotDoItAgain() - ; + new RetryPolicy\DoNotDoItAgain(); return new self( self::initialize(), $repeatable, null, $retryPolicy, - $repository + $repository, ); } @@ -50,7 +42,7 @@ public static function import($document, Repository $repository) RepeatableInJob::import($document['job']), SchedulePolicyInJob::import($document), RetryPolicyInJob::import($document['job']), - $repository + $repository, ); } @@ -59,7 +51,7 @@ public function __construct( Repeatable $repeatable, ?SchedulePolicy $schedulePolicy, ?RetryPolicy $retryPolicy, - Repository $schedulers + Repository $schedulers, ) { $this->status = $status; $this->repeatable = $repeatable; @@ -103,9 +95,9 @@ public function export() [ 'job' => array_merge( WorkableInJob::export($this->repeatable, 'execute'), - RetryPolicyInJob::export($this->retryPolicy) + RetryPolicyInJob::export($this->retryPolicy), ), - ] + ], ); } @@ -128,8 +120,9 @@ private function aJobIsStillRunning(JobsRepository $jobs) try { $alreadyScheduledJob = $jobs->scheduled($this->status['last_scheduling']['job_id']); + return true; - } catch (Throwable $e) { + } catch (\Throwable $e) { return false; } } @@ -137,7 +130,7 @@ private function aJobIsStillRunning(JobsRepository $jobs) public function schedule(JobsRepository $jobs) { if (!$this->schedulePolicy) { - throw new RuntimeException('You need to assign a `SchedulePolicy` (use `repeatWithPolicy` to inject it) in order to schedule a job'); + throw new \RuntimeException('You need to assign a `SchedulePolicy` (use `repeatWithPolicy` to inject it) in order to schedule a job'); } $nextScheduling = $this->schedulePolicy->next(); @@ -151,7 +144,7 @@ public function schedule(JobsRepository $jobs) $this->status['last_scheduling']['scheduled_at'] = T\MongoDate::from($nextScheduling); $this->status['last_scheduling']['job_id'] = null; - $this->status['attempts'] += 1; + ++$this->status['attempts']; $this->schedulers->save($this); $jobToSchedule = (new JobToSchedule(Job::around($this->repeatable, $jobs))) @@ -169,7 +162,7 @@ public function retryWithPolicy(RetryPolicy $retryPolicy, $retriableExceptionTyp { $this->retryPolicy = $this->filterForRetriableExceptions( $retryPolicy, - $retriableExceptionTypes + $retriableExceptionTypes, ); return $this; diff --git a/src/Recruiter/Scheduler/Repository.php b/src/Recruiter/Scheduler/Repository.php index df95d856..61f8e3c0 100644 --- a/src/Recruiter/Scheduler/Repository.php +++ b/src/Recruiter/Scheduler/Repository.php @@ -1,14 +1,9 @@ map( $this->schedulers->find([], [ 'sort' => ['scheduled_at' => -1], - ]) + ]), ); } @@ -34,7 +29,7 @@ public function save(Scheduler $scheduler) $this->schedulers->replaceOne( ['_id' => $document['_id']], $document, - ['upsert' => true] + ['upsert' => true], ); } @@ -55,7 +50,7 @@ public function create(Scheduler $scheduler) $this->schedulers->updateOne( ['urn' => $scheduler->urn()], - ['$set' => $document] + ['$set' => $document], ); } } diff --git a/src/Recruiter/SynchronousExecutionReport.php b/src/Recruiter/SynchronousExecutionReport.php index 68132ed6..2775aa5e 100644 --- a/src/Recruiter/SynchronousExecutionReport.php +++ b/src/Recruiter/SynchronousExecutionReport.php @@ -1,10 +1,11 @@ data = $data; } - /** - *. @params array $data : key value array where key are the id of the job and value is the JobExecution + *. @params array $data : key value array where key are the id of the job and value is the JobExecution. */ public static function fromArray(array $data): SynchronousExecutionReport { diff --git a/src/Recruiter/Taggable.php b/src/Recruiter/Taggable.php index 6ab0d0e0..0e9354e3 100644 --- a/src/Recruiter/Taggable.php +++ b/src/Recruiter/Taggable.php @@ -5,7 +5,7 @@ interface Taggable { /** - * A Job can decide its own tags. Tags are useful to correlate jobs + * A Job can decide its own tags. Tags are useful to correlate jobs. * * @return array Strings to be used to tag the job */ diff --git a/src/Recruiter/WaitStrategy.php b/src/Recruiter/WaitStrategy.php index 4e41d029..0b71e42f 100644 --- a/src/Recruiter/WaitStrategy.php +++ b/src/Recruiter/WaitStrategy.php @@ -22,6 +22,7 @@ public function __construct(Interval $timeToWaitAtLeast, Interval $timeToWaitAtM public function reset() { $this->timeToWait = $this->timeToWaitAtLeast; + return $this; } @@ -29,8 +30,9 @@ public function goForward() { $this->timeToWait = max( $this->timeToWait / 2, - $this->timeToWaitAtLeast + $this->timeToWaitAtLeast, ); + return $this; } @@ -38,14 +40,16 @@ public function backOff() { $this->timeToWait = min( $this->timeToWait * 2, - $this->timeToWaitAtMost + $this->timeToWaitAtMost, ); + return $this; } public function wait() { call_user_func($this->howToWait, $this->timeToWait * 1000); + return $this; } diff --git a/src/Recruiter/Workable.php b/src/Recruiter/Workable.php index 4a9733df..937317a1 100644 --- a/src/Recruiter/Workable.php +++ b/src/Recruiter/Workable.php @@ -5,19 +5,17 @@ interface Workable { /** - * Turn this `Recruiter\Workable` instance into a `Recruiter\Job` instance + * Turn this `Recruiter\Workable` instance into a `Recruiter\Job` instance. */ public function asJobOf(Recruiter $recruiter): JobToSchedule; /** - * Export parameters that need to be persisted - * - * @return array + * Export parameters that need to be persisted. */ public function export(): array; /** - * Import an array of parameters as a Workable instance + * Import an array of parameters as a Workable instance. * * @param array $parameters Previously exported parameters */ diff --git a/src/Recruiter/Workable/AlwaysFail.php b/src/Recruiter/Workable/AlwaysFail.php index 485e967e..3a90f1fa 100644 --- a/src/Recruiter/Workable/AlwaysFail.php +++ b/src/Recruiter/Workable/AlwaysFail.php @@ -1,7 +1,7 @@ parameters['howManyItems']); } } - diff --git a/src/Recruiter/Workable/FactoryMethodCommand.php b/src/Recruiter/Workable/FactoryMethodCommand.php index 8a354534..deac6c23 100644 --- a/src/Recruiter/Workable/FactoryMethodCommand.php +++ b/src/Recruiter/Workable/FactoryMethodCommand.php @@ -1,17 +1,18 @@ steps[] = $step; + return $this; } diff --git a/src/Recruiter/Workable/FailsInConstructor.php b/src/Recruiter/Workable/FailsInConstructor.php index 9523f5b0..2d70eb0e 100644 --- a/src/Recruiter/Workable/FailsInConstructor.php +++ b/src/Recruiter/Workable/FailsInConstructor.php @@ -1,7 +1,7 @@ usToSleep + (rand(intval(-$this->usOfDelta), $this->usOfDelta))); + usleep($this->usToSleep + rand(intval(-$this->usOfDelta), $this->usOfDelta)); } public function export(): array @@ -41,7 +40,7 @@ public static function import(array $parameters): static { return new self( $parameters['us_to_sleep'], - $parameters['us_of_delta'] + $parameters['us_of_delta'], ); } } diff --git a/src/Recruiter/Workable/RecoverRepeatableFromException.php b/src/Recruiter/Workable/RecoverRepeatableFromException.php index 4fcce94c..4521f879 100644 --- a/src/Recruiter/Workable/RecoverRepeatableFromException.php +++ b/src/Recruiter/Workable/RecoverRepeatableFromException.php @@ -1,7 +1,7 @@ recoverForClass . PHP_EOL . - 'Original exception: ' . get_class($this->recoverForException) . PHP_EOL . - $this->recoverForException->getMessage() . PHP_EOL . - $this->recoverForException->getTraceAsString() . PHP_EOL - ); + throw new \Exception('This job failed while instantiating a workable of class: ' . $this->recoverForClass . PHP_EOL . 'Original exception: ' . get_class($this->recoverForException) . PHP_EOL . $this->recoverForException->getMessage() . PHP_EOL . $this->recoverForException->getTraceAsString() . PHP_EOL); } public function getClass() @@ -38,6 +33,7 @@ public function urn(): string { $recoverForInstance = new $this->recoverForClass($this->parameters); assert($recoverForInstance instanceof Repeatable); + return $recoverForInstance->urn(); } @@ -45,6 +41,7 @@ public function unique(): bool { $recoverForInstance = new $this->recoverForClass($this->parameters); assert($recoverForInstance instanceof Repeatable); + return $recoverForInstance->unique(); } } diff --git a/src/Recruiter/Workable/RecoverWorkableFromException.php b/src/Recruiter/Workable/RecoverWorkableFromException.php index 53e2fc67..6993f755 100644 --- a/src/Recruiter/Workable/RecoverWorkableFromException.php +++ b/src/Recruiter/Workable/RecoverWorkableFromException.php @@ -1,7 +1,7 @@ recoverForClass . PHP_EOL . - 'Original exception: ' . get_class($this->recoverForException) . PHP_EOL . - $this->recoverForException->getMessage() . PHP_EOL . - $this->recoverForException->getTraceAsString() . PHP_EOL - ); + throw new \Exception('This job failed while instantiating a workable of class: ' . $this->recoverForClass . PHP_EOL . 'Original exception: ' . get_class($this->recoverForException) . PHP_EOL . $this->recoverForException->getMessage() . PHP_EOL . $this->recoverForException->getTraceAsString() . PHP_EOL); } public function getClass() diff --git a/src/Recruiter/Workable/SampleRepeatableCommand.php b/src/Recruiter/Workable/SampleRepeatableCommand.php index 92a57b5c..62faad5c 100644 --- a/src/Recruiter/Workable/SampleRepeatableCommand.php +++ b/src/Recruiter/Workable/SampleRepeatableCommand.php @@ -3,15 +3,14 @@ namespace Recruiter\Workable; use Recruiter\Repeatable; -use Recruiter\SchedulePolicy\EveryMinutes; -use Recruiter\SchedulePolicy; +use Recruiter\RepeatableBehaviour; use Recruiter\Workable; use Recruiter\WorkableBehaviour; -use Recruiter\RepeatableBehaviour; class SampleRepeatableCommand implements Workable, Repeatable { - use WorkableBehaviour, RepeatableBehaviour; + use WorkableBehaviour; + use RepeatableBehaviour; public function execute() { diff --git a/src/Recruiter/Workable/ShellCommand.php b/src/Recruiter/Workable/ShellCommand.php index ee8c3543..2e763704 100644 --- a/src/Recruiter/Workable/ShellCommand.php +++ b/src/Recruiter/Workable/ShellCommand.php @@ -4,7 +4,6 @@ use Recruiter\Workable; use Recruiter\WorkableBehaviour; -use RuntimeException; class ShellCommand implements Workable { @@ -26,9 +25,10 @@ public function execute() { exec($this->commandLine, $output, $returnCode); $output = implode(PHP_EOL, $output); - if ($returnCode != 0) { - throw new RuntimeException("Command execution failed (return code $returnCode). Output: " . $output); + if (0 != $returnCode) { + throw new \RuntimeException("Command execution failed (return code $returnCode). Output: " . $output); } + return $output; } diff --git a/src/Recruiter/Workable/ThrowsFatalError.php b/src/Recruiter/Workable/ThrowsFatalError.php index f8a16eaa..a80a0211 100644 --- a/src/Recruiter/Workable/ThrowsFatalError.php +++ b/src/Recruiter/Workable/ThrowsFatalError.php @@ -1,4 +1,5 @@ self::classNameOf($workable), 'parameters' => $workable->export(), 'method' => $methodToCall, - ] + ], ]; } @@ -60,6 +59,7 @@ private static function classNameOf($workable) if (method_exists($workable, 'getClass')) { $workableClassName = $workable->getClass(); } + return $workableClassName; } } diff --git a/src/Recruiter/Worker.php b/src/Recruiter/Worker.php index 8ea0c97c..e9885622 100644 --- a/src/Recruiter/Worker.php +++ b/src/Recruiter/Worker.php @@ -2,8 +2,6 @@ namespace Recruiter; -use DateInterval; -use DateTimeImmutable; use MongoDB\BSON\ObjectId; use MongoDB\Collection as MongoCollection; use Recruiter\Infrastructure\Memory\MemoryLimit; @@ -11,7 +9,6 @@ use Recruiter\Worker\Repository; use Timeless as T; use Timeless\Interval; -use Timeless\Moment; class Worker { @@ -23,10 +20,11 @@ class Worker public static function workFor( Recruiter $recruiter, Repository $repository, - MemoryLimit $memoryLimit + MemoryLimit $memoryLimit, ) { $worker = new self(self::initialize(), $recruiter, $repository, $memoryLimit); $worker->save(); + return $worker; } @@ -34,7 +32,7 @@ public function __construct( $status, Recruiter $recruiter, Repository $repository, - MemoryLimit $memoryLimit + MemoryLimit $memoryLimit, ) { $this->status = $status; $this->recruiter = $recruiter; @@ -58,12 +56,14 @@ public function work() if ($this->hasBeenAssignedToDoSomething()) { $this->workOn( $job = $this->recruiter->scheduledJob( - $this->status['assigned_to'][(string)$this->status['_id']] - ) + $this->status['assigned_to'][(string) $this->status['_id']], + ), ); + return (string) $job->id(); } else { $this->stillHere(); + return false; } } @@ -132,7 +132,7 @@ private function afterExecutionOf($job) $this->id(), $job->id(), get_class($e), - $e->getMessage() + $e->getMessage(), ); $this->retireAfterMemoryLimitIsExceeded(); @@ -154,7 +154,6 @@ private function retireAfterMemoryLimitIsExceeded() $this->repository->retireWorkerWithId($this->id()); } - private function hasBeenAssignedToDoSomething() { if (is_null($this->status)) { @@ -164,6 +163,7 @@ private function hasBeenAssignedToDoSomething() // thing to do seems like terminate the process exit(1); } + return array_key_exists('assigned_to', $this->status); } @@ -192,13 +192,13 @@ private static function initialize() 'last_seen_at' => T\MongoDate::now(), 'created_at' => T\MongoDate::now(), 'working' => false, - 'pid' => getmypid() + 'pid' => getmypid(), ]; } public static function canWorkOnAnyJobs($worksOn) { - return $worksOn === '*'; + return '*' === $worksOn; } public static function pickAvailableWorkers(MongoCollection $collection, $workersPerUnit) @@ -210,7 +210,7 @@ public static function pickAvailableWorkers(MongoCollection $collection, $worker $workers, function ($worker) { return $worker['work_on']; - } + }, ); foreach ($unitsOfWorkers as $workOn => $workersInUnit) { $workersInUnit = array_column($workersInUnit, '_id'); @@ -218,6 +218,7 @@ function ($worker) { $result[] = [$workOn, $workersInUnit]; } } + return $result; } @@ -225,9 +226,9 @@ public static function tryToAssignJobsToWorkers(MongoCollection $collection, $jo { $assignment = array_combine( array_map(function ($id) { - return (string)$id; + return (string) $id; }, $workers), - $jobs + $jobs, ); $result = $collection->updateMany( @@ -235,8 +236,8 @@ public static function tryToAssignJobsToWorkers(MongoCollection $collection, $jo $update = ['$set' => [ 'available' => false, 'assigned_to' => $assignment, - 'assigned_since' => T\MongoDate::now() - ]] + 'assigned_since' => T\MongoDate::now(), + ]], ); return [$assignment, $result->getModifiedCount()]; @@ -258,7 +259,7 @@ public static function assignedJobs(MongoCollection $collection) return array_values(array_unique($jobs)); } - public static function retireDeadWorkers(Repository $roster, DateTimeImmutable $now, Interval $consideredDeadAfter) + public static function retireDeadWorkers(Repository $roster, \DateTimeImmutable $now, Interval $consideredDeadAfter) { $consideredDeadAt = $now->sub($consideredDeadAfter->toDateInterval()); $deadWorkers = $roster->deadWorkers($consideredDeadAt); @@ -266,8 +267,8 @@ public static function retireDeadWorkers(Repository $roster, DateTimeImmutable $ foreach ($deadWorkers as $deadWorker) { $roster->retireWorkerWithId($deadWorker['_id']); if (array_key_exists('assigned_to', $deadWorker)) { - if (array_key_exists((string)$deadWorker['_id'], $deadWorker['assigned_to'])) { - $jobsToReassign[] = $deadWorker['assigned_to'][(string)$deadWorker['_id']]; + if (array_key_exists((string) $deadWorker['_id'], $deadWorker['assigned_to'])) { + $jobsToReassign[] = $deadWorker['assigned_to'][(string) $deadWorker['_id']]; } } } diff --git a/src/Recruiter/Worker/Process.php b/src/Recruiter/Worker/Process.php index 064f52ee..2dd02853 100644 --- a/src/Recruiter/Worker/Process.php +++ b/src/Recruiter/Worker/Process.php @@ -3,7 +3,6 @@ namespace Recruiter\Worker; use Sink\BlackHole; -use Recruiter\Worker\Repository; class Process { @@ -31,6 +30,7 @@ public function ifDead() if ($this->isAlive()) { return new BlackHole(); } + return $this; } diff --git a/src/Recruiter/Worker/Repository.php b/src/Recruiter/Worker/Repository.php index b54ad992..1e04a7d4 100644 --- a/src/Recruiter/Worker/Repository.php +++ b/src/Recruiter/Worker/Repository.php @@ -5,7 +5,6 @@ use MongoDB; use MongoDB\BSON\UTCDateTime as MongoUTCDateTime; use Recruiter\Recruiter; -use Recruiter\Worker; class Repository { @@ -24,7 +23,7 @@ public function save($worker) $result = $this->roster->replaceOne( ['_id' => $document['_id']], $document, - ['upsert' => true] + ['upsert' => true], ); } @@ -32,14 +31,14 @@ public function atomicUpdate($worker, array $changeSet) { $this->roster->updateOne( ['_id' => $worker->id()], - ['$set' => $changeSet] + ['$set' => $changeSet], ); } public function refresh($worker) { $worker->updateWith( - $this->roster->findOne(['_id' => $worker->id()]) + $this->roster->findOne(['_id' => $worker->id()]), ); } @@ -47,9 +46,9 @@ public function deadWorkers($consideredDeadAt) { return $this->roster->find( ['last_seen_at' => [ - '$lt' => new MongoUTCDateTime($consideredDeadAt->format('U') * 1000)] + '$lt' => new MongoUTCDateTime($consideredDeadAt->format('U') * 1000)], ], - ['projection' => ['_id' => true, 'assigned_to' => true]] + ['projection' => ['_id' => true, 'assigned_to' => true]], ); } diff --git a/src/Recruiter/WorkerDiedInTheLineOfDutyException.php b/src/Recruiter/WorkerDiedInTheLineOfDutyException.php index 7a3d83e0..6543f13b 100644 --- a/src/Recruiter/WorkerDiedInTheLineOfDutyException.php +++ b/src/Recruiter/WorkerDiedInTheLineOfDutyException.php @@ -1,8 +1,7 @@ seconds()}S"); + return new \DateInterval("PT{$this->seconds()}S"); } public static function parse($string) @@ -163,7 +162,7 @@ public static function parse($string) 'years' => 'years', 'year' => 'years', 'y' => 'years', ]; $units = implode('|', array_keys($tokenToFunction)); - if (preg_match("/^[^\d]*(?P\d+)\s*(?P{$units})(?:\W.*|$)/", $string, $matches)) { + if (preg_match("/^[^\\d]*(?P\\d+)\\s*(?P{$units})(?:\\W.*|$)/", $string, $matches)) { $callable = 'Timeless\\' . $tokenToFunction[$matches['unit']]; if (is_callable($callable)) { return call_user_func($callable, $matches['quantity']); @@ -182,10 +181,11 @@ public static function parse($string) throw new InvalidIntervalFormat('You need to use strings'); } - public static function fromDateInterval(DateInterval $interval): self + public static function fromDateInterval(\DateInterval $interval): self { - $startTime = new DateTimeImmutable(); + $startTime = new \DateTimeImmutable(); $endTime = $startTime->add($interval); + return new self(($endTime->getTimestamp() - $startTime->getTimestamp()) * 1000); } } diff --git a/src/Timeless/InvalidIntervalFormat.php b/src/Timeless/InvalidIntervalFormat.php index 55ca83b4..01175de2 100644 --- a/src/Timeless/InvalidIntervalFormat.php +++ b/src/Timeless/InvalidIntervalFormat.php @@ -2,8 +2,6 @@ namespace Timeless; -use Exception; - -class InvalidIntervalFormat extends Exception +class InvalidIntervalFormat extends \Exception { } diff --git a/src/Timeless/Moment.php b/src/Timeless/Moment.php index fd6c96cf..4e394126 100644 --- a/src/Timeless/Moment.php +++ b/src/Timeless/Moment.php @@ -2,9 +2,6 @@ namespace Timeless; -use DateTime; -use DateTimeZone; - readonly class Moment { public static function fromTimestamp(int $ts): self @@ -12,7 +9,7 @@ public static function fromTimestamp(int $ts): self return new self($ts * 1000); } - public static function fromDateTime(DateTime $dateTime): self + public static function fromDateTime(\DateTime $dateTime): self { return self::fromTimestamp($dateTime->getTimestamp()); } @@ -68,11 +65,11 @@ public function toSecondPrecision(): Moment public function format(): string { - return new DateTime('@' . $this->s(), new DateTimeZone('UTC'))->format(DateTime::RFC3339); + return new \DateTime('@' . $this->s(), new \DateTimeZone('UTC'))->format(\DateTime::RFC3339); } - public function toDateTime(): DateTime + public function toDateTime(): \DateTime { - return new DateTime('@' . $this->s(), new DateTimeZone('UTC')); + return new \DateTime('@' . $this->s(), new \DateTimeZone('UTC')); } } diff --git a/src/Timeless/MongoDate.php b/src/Timeless/MongoDate.php index 255ccdc2..d61714de 100644 --- a/src/Timeless/MongoDate.php +++ b/src/Timeless/MongoDate.php @@ -1,4 +1,5 @@ Date: Sun, 3 Aug 2025 12:26:02 +0200 Subject: [PATCH 25/59] Use a dist file instead --- .gitignore | 1 + phpunit.xml => phpunit.dist.xml | 0 2 files changed, 1 insertion(+) rename phpunit.xml => phpunit.dist.xml (100%) diff --git a/.gitignore b/.gitignore index d1b4fe30..01beab6a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,5 @@ .*.cache +phpunit.xml composer.phar composer.lock vendor/ diff --git a/phpunit.xml b/phpunit.dist.xml similarity index 100% rename from phpunit.xml rename to phpunit.dist.xml From db6d5fa1ee8270b56607f7f4eac42c89b7bc6935 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 12:33:21 +0200 Subject: [PATCH 26/59] Remove setMethods --- spec/Recruiter/JobCallCustomMethodOnWorkableTest.php | 2 +- spec/Recruiter/RetryPolicy/TimeTableTest.php | 4 ++-- spec/Recruiter/WorkerProcessTest.php | 2 +- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php b/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php index a71b8d94..0aecfe08 100644 --- a/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php +++ b/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php @@ -16,7 +16,7 @@ protected function setUp(): void { $this->workable = $this ->getMockBuilder(Workable::class) - ->setMethods(['export', 'import', 'asJobOf', 'send']) + ->onlyMethods(['export', 'import', 'asJobOf', 'send']) ->getMock() ; diff --git a/spec/Recruiter/RetryPolicy/TimeTableTest.php b/spec/Recruiter/RetryPolicy/TimeTableTest.php index 2ce8ea2e..5e943fa0 100644 --- a/spec/Recruiter/RetryPolicy/TimeTableTest.php +++ b/spec/Recruiter/RetryPolicy/TimeTableTest.php @@ -113,7 +113,7 @@ private function givenJobThat(T\Moment $wasCreatedAt) { $job = $this->getMockBuilder('Recruiter\JobAfterFailure') ->disableOriginalConstructor() - ->setMethods(['createdAt', 'scheduleAt']) + ->onlyMethods(['createdAt', 'scheduleAt']) ->getMock() ; $job->expects($this->any()) @@ -129,7 +129,7 @@ private function jobThatWasCreated($relativeTime) $wasCreatedAt = T\Moment::fromTimestamp(strtotime($relativeTime)); $job = $this->getMockBuilder('Recruiter\JobAfterFailure') ->disableOriginalConstructor() - ->setMethods(['createdAt', 'scheduleAt']) + ->onlyMethods(['createdAt', 'scheduleAt']) ->getMock() ; $job->expects($this->any()) diff --git a/spec/Recruiter/WorkerProcessTest.php b/spec/Recruiter/WorkerProcessTest.php index f3d7bfdd..2f241a00 100644 --- a/spec/Recruiter/WorkerProcessTest.php +++ b/spec/Recruiter/WorkerProcessTest.php @@ -72,7 +72,7 @@ private function givenWorkerProcessDead() private function givenWorkerProcess($alive) { $process = $this->getMockBuilder('Recruiter\Worker\Process') - ->setMethods(['isAlive']) + ->onlyMethods(['isAlive']) ->setConstructorArgs([$this->pid]) ->getMock() ; From 333da6942cd303792a24fb47e36d8d5d2ea40015 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 13:00:02 +0200 Subject: [PATCH 27/59] Use ::class everywhere --- spec/Recruiter/CleanerTest.php | 3 ++- spec/Recruiter/Job/RepositoryTest.php | 6 ++++-- spec/Recruiter/JobCallCustomMethodOnWorkableTest.php | 3 ++- spec/Recruiter/JobTest.php | 3 ++- spec/Recruiter/JobToScheduleTest.php | 5 +++-- spec/Recruiter/RetryPolicy/ExponentialBackoffTest.php | 3 ++- .../RetryPolicy/RetriableExceptionFilterTest.php | 3 ++- spec/Recruiter/RetryPolicy/SelectByExceptionTest.php | 3 ++- spec/Recruiter/RetryPolicy/TimeTableTest.php | 10 ++++++---- spec/Recruiter/WorkerProcessTest.php | 2 +- 10 files changed, 26 insertions(+), 15 deletions(-) diff --git a/spec/Recruiter/CleanerTest.php b/spec/Recruiter/CleanerTest.php index d1d99c4c..0c8edb7f 100644 --- a/spec/Recruiter/CleanerTest.php +++ b/spec/Recruiter/CleanerTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Recruiter\Job\Repository; use Timeless as T; use Timeless\Interval; use Timeless\Moment; @@ -22,7 +23,7 @@ protected function setUp(): void $this->now = $this->clock->now(); $this->jobRepository = $this - ->getMockBuilder('Recruiter\Job\Repository') + ->getMockBuilder(Repository::class) ->disableOriginalConstructor() ->getMock() ; diff --git a/spec/Recruiter/Job/RepositoryTest.php b/spec/Recruiter/Job/RepositoryTest.php index 7b6aacb3..62ba7f0a 100644 --- a/spec/Recruiter/Job/RepositoryTest.php +++ b/spec/Recruiter/Job/RepositoryTest.php @@ -10,7 +10,9 @@ use Recruiter\Factory; use Recruiter\Infrastructure\Persistence\Mongodb\URI as MongoURI; use Recruiter\Job; +use Recruiter\JobExecution; use Recruiter\JobToSchedule; +use Recruiter\Workable; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Timeless as T; use Timeless\Interval; @@ -440,7 +442,7 @@ private function aJobToSchedule($job = null) private function workableMock() { return $this - ->getMockBuilder('Recruiter\Workable') + ->getMockBuilder(Workable::class) ->getMock() ; } @@ -460,7 +462,7 @@ private function workableMockWithCustomParameters($parameters) private function jobExecutionMock($executionParameters) { $jobExecutionMock = $this - ->getMockBuilder('Recruiter\JobExecution') + ->getMockBuilder(JobExecution::class) ->getMock() ; $jobExecutionMock->expects($this->once()) diff --git a/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php b/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php index 0aecfe08..b3a9d522 100644 --- a/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php +++ b/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php @@ -5,6 +5,7 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Recruiter\Job\Repository; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class JobCallCustomMethodOnWorkableTest extends TestCase { @@ -33,7 +34,7 @@ public function testConfigureMethodToCallOnWorkable() { $this->workable->expects($this->once())->method('send'); $this->job->methodToCallOnWorkable('send'); - $this->job->execute($this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface')); + $this->job->execute($this->createMock(EventDispatcherInterface::class)); } public function testRaiseExceptionWhenConfigureMethodToCallOnWorkableThatDoNotExists() diff --git a/spec/Recruiter/JobTest.php b/spec/Recruiter/JobTest.php index b419fc29..261f7575 100644 --- a/spec/Recruiter/JobTest.php +++ b/spec/Recruiter/JobTest.php @@ -7,6 +7,7 @@ use Recruiter\Infrastructure\Memory\MemoryLimit; use Recruiter\Job\Repository; use Recruiter\Workable\AlwaysFail; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; class JobTest extends TestCase { @@ -41,7 +42,7 @@ public function testRetryStatisticsOnSubsequentExecutions() { $job = Job::around(new AlwaysFail(), $this->repository); // maybe make the argument optional - $job->execute($this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface')); + $job->execute($this->createMock(EventDispatcherInterface::class)); $job = Job::import($job->export(), $this->repository); $retryStatistics = $job->retryStatistics(); $this->assertEquals(1, $retryStatistics['retry_number']); diff --git a/spec/Recruiter/JobToScheduleTest.php b/spec/Recruiter/JobToScheduleTest.php index b81c8afb..27b9496a 100644 --- a/spec/Recruiter/JobToScheduleTest.php +++ b/spec/Recruiter/JobToScheduleTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Timeless as T; class JobToScheduleTest extends TestCase @@ -123,7 +124,7 @@ public function testShouldExecuteJobWhenNotScheduled() (new JobToSchedule($this->job)) ->execute( - $this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'), + $this->createMock(EventDispatcherInterface::class), ) ; } @@ -153,7 +154,7 @@ public function testReturnsJobId() '42', (new JobToSchedule($this->job)) ->execute( - $this->createMock('Symfony\Component\EventDispatcher\EventDispatcherInterface'), + $this->createMock(EventDispatcherInterface::class), ), ); } diff --git a/spec/Recruiter/RetryPolicy/ExponentialBackoffTest.php b/spec/Recruiter/RetryPolicy/ExponentialBackoffTest.php index 102c100a..5c6e9e52 100644 --- a/spec/Recruiter/RetryPolicy/ExponentialBackoffTest.php +++ b/spec/Recruiter/RetryPolicy/ExponentialBackoffTest.php @@ -3,6 +3,7 @@ namespace Recruiter\RetryPolicy; use PHPUnit\Framework\TestCase; +use Recruiter\JobAfterFailure; use Timeless as T; class ExponentialBackoffTest extends TestCase @@ -53,7 +54,7 @@ public function testCanBeCreatedByTargetingAMaximumInterval() private function jobExecutedFor($times) { - $job = $this->getMockBuilder('Recruiter\JobAfterFailure')->disableOriginalConstructor()->getMock(); + $job = $this->getMockBuilder(JobAfterFailure::class)->disableOriginalConstructor()->getMock(); $job->expects($this->any()) ->method('numberOfAttempts') ->will($this->returnValue($times)) diff --git a/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php b/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php index 13ccdfd7..09d8ae91 100644 --- a/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php +++ b/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php @@ -4,6 +4,7 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; +use Recruiter\JobAfterFailure; use Recruiter\RetryPolicy; class RetriableExceptionFilterTest extends TestCase @@ -130,7 +131,7 @@ public function testRetriableExceptionsThatAreNotExceptions() private function jobFailedWithException($exception) { $jobAfterFailure = $this - ->getMockBuilder('Recruiter\JobAfterFailure') + ->getMockBuilder(JobAfterFailure::class) ->disableOriginalConstructor() ->getMock() ; diff --git a/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php b/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php index 91f62db4..45c5904e 100644 --- a/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php +++ b/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php @@ -3,6 +3,7 @@ namespace Recruiter\RetryPolicy; use PHPUnit\Framework\TestCase; +use Recruiter\JobAfterFailure; use Recruiter\RetryPolicy; use Timeless as T; @@ -66,7 +67,7 @@ public function testDefaultDoNotSchedule() private function jobFailedWith(\Exception $exception) { - $job = $this->getMockBuilder('Recruiter\JobAfterFailure')->disableOriginalConstructor()->getMock(); + $job = $this->getMockBuilder(JobAfterFailure::class)->disableOriginalConstructor()->getMock(); $job->expects($this->any()) ->method('causeOfFailure') ->will($this->returnValue($exception)) diff --git a/spec/Recruiter/RetryPolicy/TimeTableTest.php b/spec/Recruiter/RetryPolicy/TimeTableTest.php index 5e943fa0..d5361aaa 100644 --- a/spec/Recruiter/RetryPolicy/TimeTableTest.php +++ b/spec/Recruiter/RetryPolicy/TimeTableTest.php @@ -3,6 +3,8 @@ namespace Recruiter\RetryPolicy; use PHPUnit\Framework\TestCase; +use Recruiter\Job; +use Recruiter\JobAfterFailure; use Timeless as T; class TimeTableTest extends TestCase @@ -63,7 +65,7 @@ public function testShouldNotBeRescheduledWhenWasCreatedMoreThan24HoursAgo() public function testIsLastRetryReturnTrueIfJobWasCreatedMoreThanLastTimeSpen() { - $job = $this->createMock('Recruiter\Job'); + $job = $this->createMock(Job::class); $job->expects($this->any()) ->method('createdAt') ->will($this->returnValue(T\hours(3)->ago())) @@ -78,7 +80,7 @@ public function testIsLastRetryReturnTrueIfJobWasCreatedMoreThanLastTimeSpen() public function testIsLastRetryReturnFalseIfJobWasCreatedLessThanLastTimeSpen() { - $job = $this->createMock('Recruiter\Job'); + $job = $this->createMock(Job::class); $job->expects($this->any()) ->method('createdAt') ->will($this->returnValue(T\hours(3)->ago())) @@ -111,7 +113,7 @@ public function testInvalidTimeTableBecauseRescheduleTimeIsGreaterThanTimeWindow private function givenJobThat(T\Moment $wasCreatedAt) { - $job = $this->getMockBuilder('Recruiter\JobAfterFailure') + $job = $this->getMockBuilder(JobAfterFailure::class) ->disableOriginalConstructor() ->onlyMethods(['createdAt', 'scheduleAt']) ->getMock() @@ -127,7 +129,7 @@ private function givenJobThat(T\Moment $wasCreatedAt) private function jobThatWasCreated($relativeTime) { $wasCreatedAt = T\Moment::fromTimestamp(strtotime($relativeTime)); - $job = $this->getMockBuilder('Recruiter\JobAfterFailure') + $job = $this->getMockBuilder(JobAfterFailure::class) ->disableOriginalConstructor() ->onlyMethods(['createdAt', 'scheduleAt']) ->getMock() diff --git a/spec/Recruiter/WorkerProcessTest.php b/spec/Recruiter/WorkerProcessTest.php index 2f241a00..bcd50437 100644 --- a/spec/Recruiter/WorkerProcessTest.php +++ b/spec/Recruiter/WorkerProcessTest.php @@ -71,7 +71,7 @@ private function givenWorkerProcessDead() private function givenWorkerProcess($alive) { - $process = $this->getMockBuilder('Recruiter\Worker\Process') + $process = $this->getMockBuilder(Process::class) ->onlyMethods(['isAlive']) ->setConstructorArgs([$this->pid]) ->getMock() From 75e42ba364c4406c282f49b04667306533624ac1 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 13:00:10 +0200 Subject: [PATCH 28/59] Fix Coding Standards --- spec/Recruiter/Job/RepositoryTest.php | 5 +++-- spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php | 3 ++- spec/Recruiter/RetryPolicy/TimeTableTest.php | 3 ++- 3 files changed, 7 insertions(+), 4 deletions(-) diff --git a/spec/Recruiter/Job/RepositoryTest.php b/spec/Recruiter/Job/RepositoryTest.php index 62ba7f0a..e6dc1a25 100644 --- a/spec/Recruiter/Job/RepositoryTest.php +++ b/spec/Recruiter/Job/RepositoryTest.php @@ -490,7 +490,7 @@ private function jobMockWithAttemptsAndCustomParameters( 'ended_at' => T\MongoDate::from($endedAt), ], 'retry_policy' => [ - 'class' => 'Recruiter\\RetryPolicy\\DoNotDoItAgain', + 'class' => 'Recruiter\RetryPolicy\DoNotDoItAgain', 'parameters' => [], ], ]; @@ -507,7 +507,8 @@ private function jobMockWithAttemptsAndCustomParameters( ; $job->expects($this->once()) ->method('export') - ->willReturn($parameters); + ->willReturn($parameters) + ; return $job; } diff --git a/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php b/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php index 09d8ae91..553a26a6 100644 --- a/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php +++ b/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php @@ -139,7 +139,8 @@ private function jobFailedWithException($exception) $jobAfterFailure ->expects($this->any()) ->method('causeOfFailure') - ->will($this->returnValue($exception)); + ->will($this->returnValue($exception)) + ; return $jobAfterFailure; } diff --git a/spec/Recruiter/RetryPolicy/TimeTableTest.php b/spec/Recruiter/RetryPolicy/TimeTableTest.php index d5361aaa..a4bd304a 100644 --- a/spec/Recruiter/RetryPolicy/TimeTableTest.php +++ b/spec/Recruiter/RetryPolicy/TimeTableTest.php @@ -136,7 +136,8 @@ private function jobThatWasCreated($relativeTime) ; $job->expects($this->any()) ->method('createdAt') - ->will($this->returnValue($wasCreatedAt)); + ->will($this->returnValue($wasCreatedAt)) + ; return $job; } From 6e642792598c64030412e4f1809aba760a17cb93 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 14:17:05 +0200 Subject: [PATCH 29/59] Fix most of the tests --- .../JobCallCustomMethodOnWorkableTest.php | 2 +- .../Recruiter/JobSendEventsToWorkableTest.php | 32 ++++--- ...keRetryPolicyFromRetriableWorkableTest.php | 1 + spec/Recruiter/PickAvailableWorkersTest.php | 87 ++++++++++++++++--- .../Command/Bko/AnalyticsCommand.php | 2 +- 5 files changed, 97 insertions(+), 27 deletions(-) diff --git a/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php b/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php index b3a9d522..c96374ab 100644 --- a/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php +++ b/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php @@ -16,7 +16,7 @@ class JobCallCustomMethodOnWorkableTest extends TestCase protected function setUp(): void { $this->workable = $this - ->getMockBuilder(Workable::class) + ->getMockBuilder(DummyWorkableWithSendCustomMethod::class) ->onlyMethods(['export', 'import', 'asJobOf', 'send']) ->getMock() ; diff --git a/spec/Recruiter/JobSendEventsToWorkableTest.php b/spec/Recruiter/JobSendEventsToWorkableTest.php index 4bbb0c19..8bceaed7 100644 --- a/spec/Recruiter/JobSendEventsToWorkableTest.php +++ b/spec/Recruiter/JobSendEventsToWorkableTest.php @@ -2,7 +2,6 @@ namespace Recruiter; -use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Recruiter\Job\Event; @@ -26,25 +25,19 @@ protected function setUp(): void $this->dispatcher = $this->createMock(EventDispatcherInterface::class); } - /** - * @throws Exception - */ public function testTakeRetryPolicyFromRetriableInstance() { - $listener = $this->createPartialMock(EventListener::class, ['onEvent']); - $listener - ->expects($this->exactly(3)) - ->method('onEvent') - ->withConsecutive( - [$this->equalTo('job.started'), $this->anything()], - [$this->equalTo('job.ended'), $this->anything()], - [$this->equalTo('job.failure.last'), $this->anything()], - ) - ; + $listener = new EventListenerSpy(); $workable = new WorkableThatIsAlsoAnEventListener($listener); $job = Job::around($workable, $this->repository); $job->execute($this->dispatcher); + + $events = $listener->events; + $this->assertCount(3, $events); + $this->assertSame('job.started', $events[0][0]); + $this->assertSame('job.ended', $events[1][0]); + $this->assertSame('job.failure.last', $events[2][0]); } } @@ -54,6 +47,7 @@ class WorkableThatIsAlsoAnEventListener implements Workable, EventListener public function __construct(private readonly EventListener $listener) { + $this->parameters = []; } public function onEvent($channel, Event $ev) @@ -66,3 +60,13 @@ public function execute() throw new \Exception(); } } + +class EventListenerSpy implements EventListener +{ + public array $events = []; + + public function onEvent($channel, Event $ev): void + { + $this->events[] = [$channel, $ev]; + } +} diff --git a/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php b/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php index e2939e21..5385ce19 100644 --- a/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php +++ b/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php @@ -46,6 +46,7 @@ class WorkableThatIsAlsoRetriable implements Workable, Retriable public function __construct(private readonly RetryPolicy $retryWithPolicy) { + $this->parameters = []; } public function retryWithPolicy(): RetryPolicy diff --git a/spec/Recruiter/PickAvailableWorkersTest.php b/spec/Recruiter/PickAvailableWorkersTest.php index 54abd630..13c96ea9 100644 --- a/spec/Recruiter/PickAvailableWorkersTest.php +++ b/spec/Recruiter/PickAvailableWorkersTest.php @@ -2,8 +2,12 @@ namespace Recruiter; +use MongoDB\BSON\Int64; use MongoDB\BSON\ObjectId; use MongoDB\Collection; +use MongoDB\Driver\CursorInterface; +use MongoDB\Driver\Server; +use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; @@ -12,14 +16,12 @@ class PickAvailableWorkersTest extends TestCase private MockObject&Collection $repository; private int $workersPerUnit; + /** + * @throws Exception + */ protected function setUp(): void { - $this->repository = $this - ->getMockBuilder(Collection::class) - ->disableOriginalConstructor() - ->getMock() - ; - + $this->repository = $this->createMock(Collection::class); $this->workersPerUnit = 42; } @@ -32,7 +34,7 @@ public function testNoWorkersAreFound() $this->assertEquals([], $picked); } - public function testFewWorkersWithNoSpecifiSkill() + public function testFewWorkersWithNoSpecificSkill(): void { $callbackHasBeenCalled = false; $this->withAvailableWorkers(['*' => 3]); @@ -41,7 +43,7 @@ public function testFewWorkersWithNoSpecifiSkill() [$worksOn, $workers] = $picked[0]; $this->assertEquals('*', $worksOn); - $this->assertEquals(3, count($workers)); + $this->assertCount(3, $workers); } public function testFewWorkersWithSameSkill() @@ -86,7 +88,10 @@ public function testMoreWorkersThanAllowedPerUnit() $this->assertEquals($this->workersPerUnit, $totalWorkersGiven); } - private function withAvailableWorkers($workers) + /** + * @throws Exception + */ + private function withAvailableWorkers($workers): void { $workersThatShouldBeFound = []; foreach ($workers as $skill => $quantity) { @@ -102,7 +107,7 @@ private function withAvailableWorkers($workers) $this->repository ->expects($this->any()) ->method('find') - ->will($this->returnValue(new \ArrayIterator($workersThatShouldBeFound))) + ->willReturn(new FakeCursor($workersThatShouldBeFound)) ; } @@ -111,7 +116,7 @@ private function withNoAvailableWorkers() $this->repository ->expects($this->any()) ->method('find') - ->will($this->returnValue(new \ArrayIterator([]))) + ->willReturn(new FakeCursor()) ; } @@ -122,3 +127,63 @@ private function assertArrayAreEquals($expected, $given) $this->assertEquals($expected, $given); } } + +class FakeCursor implements CursorInterface, \Iterator +{ + private array $data; + + public function __construct(array $data = []) + { + $this->data = array_values($data); + } + + public function getId(): Int64 + { + return new Int64(42); + } + + public function getServer(): Server + { + throw new \LogicException('Not implemented'); + } + + public function isDead(): bool + { + throw new \LogicException('Not implemented'); + } + + public function setTypeMap(array $typemap): void + { + throw new \LogicException('Not implemented'); + } + + public function toArray(): array + { + throw new \LogicException('Not implemented'); + } + + public function current(): object|array|null + { + return current($this->data); + } + + public function next(): void + { + next($this->data); + } + + public function key(): ?int + { + return key($this->data); + } + + public function valid(): bool + { + return null !== key($this->data); + } + + public function rewind(): void + { + reset($this->data); + } +} diff --git a/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php b/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php index a569404b..5603ce6a 100644 --- a/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php +++ b/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php @@ -25,7 +25,7 @@ public function __construct(private readonly Factory $factory, private readonly parent::__construct(); } - protected function configure() + protected function configure(): void { $this ->setName('bko:analytics') From 8129805fa2fad47954e6a10a83ad0ba1cba668a6 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 14:45:12 +0200 Subject: [PATCH 30/59] Update language level --- spec/Recruiter/Acceptance/FaultToleranceTest.php | 2 +- src/Recruiter/Workable/ThrowsFatalError.php | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/spec/Recruiter/Acceptance/FaultToleranceTest.php b/spec/Recruiter/Acceptance/FaultToleranceTest.php index abe2bcfd..c4053ec8 100644 --- a/spec/Recruiter/Acceptance/FaultToleranceTest.php +++ b/spec/Recruiter/Acceptance/FaultToleranceTest.php @@ -63,7 +63,7 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDies() // executions. The problem is that the retry policy is // evaluated after the execution but fatal errors are not // catchable and so the job will stay scheduled forever - (new ThrowsFatalError()) + new ThrowsFatalError() ->asJobOf($this->recruiter) ->inBackground() ->retryWithPolicy(RetryManyTimes::forTimes(1, 0)) diff --git a/src/Recruiter/Workable/ThrowsFatalError.php b/src/Recruiter/Workable/ThrowsFatalError.php index a80a0211..257cb523 100644 --- a/src/Recruiter/Workable/ThrowsFatalError.php +++ b/src/Recruiter/Workable/ThrowsFatalError.php @@ -9,7 +9,7 @@ class ThrowsFatalError implements Workable { use WorkableBehaviour; - public function execute() + public function execute(): void { new ThisClassDoesnNotExists(); } From 5e1405b916247f39860adb3e1079df30c9af94e8 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 15:02:58 +0200 Subject: [PATCH 31/59] Introduce URI::fromEnvironment --- .../Acceptance/BaseAcceptanceTestCase.php | 3 +-- spec/Recruiter/FactoryTest.php | 2 +- spec/Recruiter/Job/RepositoryTest.php | 2 +- .../Command/Bko/AnalyticsCommand.php | 2 +- .../Command/Bko/JobRecoverCommand.php | 2 +- .../Command/Bko/RemoveSchedulerCommand.php | 2 +- .../Infrastructure/Command/CleanerCommand.php | 3 +-- .../Command/RecruiterCommand.php | 3 +-- .../Infrastructure/Command/WorkerCommand.php | 3 +-- .../Persistence/Mongodb/URI.php | 26 +++++++++---------- 10 files changed, 22 insertions(+), 26 deletions(-) diff --git a/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php b/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php index f88d26a1..fc526e25 100644 --- a/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php +++ b/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php @@ -32,8 +32,7 @@ abstract class BaseAcceptanceTestCase extends TestCase protected function setUp(): void { $factory = new Factory(); - $uri = getenv('MONGODB_URI') ?: URI::DEFAULT_URI; - $this->recruiterDb = $factory->getMongoDb(MongoURI::from($uri), []); + $this->recruiterDb = $factory->getMongoDb(URI::fromEnvironment(), []); $this->cleanDb(); $this->files = ['/tmp/recruiter.log', '/tmp/worker.log']; $this->cleanLogs(); diff --git a/spec/Recruiter/FactoryTest.php b/spec/Recruiter/FactoryTest.php index b59ac993..3128cdc4 100644 --- a/spec/Recruiter/FactoryTest.php +++ b/spec/Recruiter/FactoryTest.php @@ -14,7 +14,7 @@ class FactoryTest extends TestCase protected function setUp(): void { $this->factory = new Factory(); - $this->mongoURI = MongoURI::from(getenv('MONGODB_URI') ?: MongoURI::DEFAULT_URI); + $this->mongoURI = MongoURI::fromEnvironment(); } public function testShouldCreateAMongoDatabaseConnection() diff --git a/spec/Recruiter/Job/RepositoryTest.php b/spec/Recruiter/Job/RepositoryTest.php index e6dc1a25..63c237ad 100644 --- a/spec/Recruiter/Job/RepositoryTest.php +++ b/spec/Recruiter/Job/RepositoryTest.php @@ -31,7 +31,7 @@ class RepositoryTest extends TestCase protected function setUp(): void { $factory = new Factory(); - $this->recruiterDb = $factory->getMongoDb(MongoURI::from(getenv('MONGODB_URI') ?: MongoURI::DEFAULT_URI), []); + $this->recruiterDb = $factory->getMongoDb(MongoURI::fromEnvironment(), []); $this->recruiterDb->drop(); $this->repository = new Repository($this->recruiterDb); $this->clock = T\clock()->stop(); diff --git a/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php b/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php index 5603ce6a..6c3c588c 100644 --- a/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php +++ b/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php @@ -35,7 +35,7 @@ protected function configure(): void 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', - 'mongodb://localhost:27017/recruiter', + MongoURI::fromEnvironment(), ) ->addOption( 'group', diff --git a/src/Recruiter/Infrastructure/Command/Bko/JobRecoverCommand.php b/src/Recruiter/Infrastructure/Command/Bko/JobRecoverCommand.php index ff6b894f..46e53849 100644 --- a/src/Recruiter/Infrastructure/Command/Bko/JobRecoverCommand.php +++ b/src/Recruiter/Infrastructure/Command/Bko/JobRecoverCommand.php @@ -39,7 +39,7 @@ protected function configure() 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', - 'mongodb://localhost:27017/recruiter', + MongoURI::fromEnvironment(), ) ->addOption( 'scheduleAt', diff --git a/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php b/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php index 6cfca252..17d777eb 100644 --- a/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php +++ b/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php @@ -51,7 +51,7 @@ protected function configure() 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', - 'mongodb://localhost:27017/recruiter', + MongoURI::fromEnvironment(), ) ; } diff --git a/src/Recruiter/Infrastructure/Command/CleanerCommand.php b/src/Recruiter/Infrastructure/Command/CleanerCommand.php index a7ce29f6..8ea9053c 100644 --- a/src/Recruiter/Infrastructure/Command/CleanerCommand.php +++ b/src/Recruiter/Infrastructure/Command/CleanerCommand.php @@ -124,8 +124,7 @@ public function description(): string public function definition(): InputDefinition { - $defaultMongoUri = getenv('MONGODB_URI') ?: 'mongodb://localhost:27017'; - + $defaultMongoUri = MongoURI::fromEnvironment(); return new InputDefinition([ new InputOption('target', 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', $defaultMongoUri), new InputOption('clean-after', 'c', InputOption::VALUE_REQUIRED, 'delete jobs after :period', '5days'), diff --git a/src/Recruiter/Infrastructure/Command/RecruiterCommand.php b/src/Recruiter/Infrastructure/Command/RecruiterCommand.php index ee06dd98..46f3903d 100644 --- a/src/Recruiter/Infrastructure/Command/RecruiterCommand.php +++ b/src/Recruiter/Infrastructure/Command/RecruiterCommand.php @@ -177,8 +177,7 @@ public function description(): string public function definition(): InputDefinition { - $defaultMongoUri = getenv('MONGODB_URI') ?: 'mongodb://localhost:27017'; - + $defaultMongoUri = MongoURI::fromEnvironment(); return new InputDefinition([ new InputOption('target', 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', $defaultMongoUri), new InputOption('backoff-to', 'b', InputOption::VALUE_REQUIRED, 'Upper limit of time to wait before next polling (milliseconds)', '1600ms'), diff --git a/src/Recruiter/Infrastructure/Command/WorkerCommand.php b/src/Recruiter/Infrastructure/Command/WorkerCommand.php index 85447cb9..a96abd90 100644 --- a/src/Recruiter/Infrastructure/Command/WorkerCommand.php +++ b/src/Recruiter/Infrastructure/Command/WorkerCommand.php @@ -114,8 +114,7 @@ public function description(): string public function definition(): InputDefinition { - $defaultMongoUri = getenv('MONGODB_URI') ?: 'mongodb://localhost:27017'; - + $defaultMongoUri = MongoURI::fromEnvironment(); return new InputDefinition([ new InputOption('target', 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', $defaultMongoUri), new InputOption('backoff-to', 'b', InputOption::VALUE_REQUIRED, 'Upper limit of time to wait before next polling', '6400ms'), diff --git a/src/Recruiter/Infrastructure/Persistence/Mongodb/URI.php b/src/Recruiter/Infrastructure/Persistence/Mongodb/URI.php index 46b21325..b76da40c 100644 --- a/src/Recruiter/Infrastructure/Persistence/Mongodb/URI.php +++ b/src/Recruiter/Infrastructure/Persistence/Mongodb/URI.php @@ -4,25 +4,25 @@ namespace Recruiter\Infrastructure\Persistence\Mongodb; -/** - * Class URI. - */ -class URI +final readonly class URI implements \Stringable { - public const DEFAULT_URI = 'mongodb://127.0.0.1:27017/recruiter'; + public const string DEFAULT_URI = 'mongodb://127.0.0.1:27017/recruiter'; - /** - * @var string - */ - private $uri; + public function __construct(private string $uri) + { + } - public function __construct(string $uri) + public static function fromEnvironment(): self { - $this->uri = $uri; + return self::from(getenv('MONGODB_URI')); } - public static function from(?string $uri): self + public static function from(string|self|null $uri): self { + if ($uri instanceof self) { + return $uri; + } + if (!$uri) { $uri = self::DEFAULT_URI; } @@ -40,7 +40,7 @@ public function database(): string return substr($parsed['path'], 1); } - public function __toString() + public function __toString(): string { return $this->uri; } From 6ce9d2abb3147c71ad5b747b1b8a1adb0302235d Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 15:08:30 +0200 Subject: [PATCH 32/59] Fix outdated examples --- examples/bootstrap.php | 10 ++++++-- examples/jobAndWorkerTagged.php | 14 +++++------ ...obFailedBecauseOfNonRetriableException.php | 24 +++++++++---------- examples/jobRetriedManyTimesUntilArchived.php | 24 +++++++++---------- examples/oneTimeJob.php | 14 +++++------ 5 files changed, 46 insertions(+), 40 deletions(-) diff --git a/examples/bootstrap.php b/examples/bootstrap.php index c61635fd..bbc2269c 100644 --- a/examples/bootstrap.php +++ b/examples/bootstrap.php @@ -1,6 +1,12 @@ getEventDispatcher()->addListener('job.failure.last', function($event): void { - error_log("Job definitively failed: " . var_export($event->export(), true)); + +global $recruiter; +assert($recruiter instanceof Recruiter); + +$recruiter->getEventDispatcher()->addListener('job.failure.last', function ($event): void { + error_log('Job definitively failed: ' . var_export($event->export(), true)); }); diff --git a/examples/jobAndWorkerTagged.php b/examples/jobAndWorkerTagged.php index 632e2238..d8795837 100755 --- a/examples/jobAndWorkerTagged.php +++ b/examples/jobAndWorkerTagged.php @@ -3,17 +3,16 @@ require __DIR__ . '/../vendor/autoload.php'; -use Recruiter\Recruiter; use Recruiter\Factory; +use Recruiter\Infrastructure\Memory\MemoryLimit; +use Recruiter\Infrastructure\Persistence\Mongodb\URI as MongoURI; +use Recruiter\Recruiter; use Recruiter\Workable\LazyBones; -use Recruiter\Worker; -use Recruiter\Option\MemoryLimit; $factory = new Factory(); $db = $factory->getMongoDb( - $hosts = 'localhost:27017', + MongoURI::fromEnvironment(), $options = [], - $dbName = 'recruiter' ); $db->drop(); @@ -23,9 +22,10 @@ ->asJobOf($recruiter) ->inGroup('mail') ->inBackground() - ->execute(); + ->execute() +; -$memoryLimit = new MemoryLimit('memory-limit', '64MB'); +$memoryLimit = new MemoryLimit('64MB'); $worker = $recruiter->hire($memoryLimit); $worker->workOnJobsGroupedAs('mail'); $assignments = $recruiter->assignJobsToWorkers(); diff --git a/examples/jobFailedBecauseOfNonRetriableException.php b/examples/jobFailedBecauseOfNonRetriableException.php index a1e452c3..96f6aef7 100755 --- a/examples/jobFailedBecauseOfNonRetriableException.php +++ b/examples/jobFailedBecauseOfNonRetriableException.php @@ -3,20 +3,17 @@ require __DIR__ . '/../vendor/autoload.php'; -use Timeless as T; - -use Recruiter\Recruiter; use Recruiter\Factory; +use Recruiter\Infrastructure\Memory\MemoryLimit; +use Recruiter\Infrastructure\Persistence\Mongodb\URI as MongoURI; +use Recruiter\Recruiter; use Recruiter\Workable\AlwaysFail; -use Recruiter\RetryPolicy; -use Recruiter\Worker; -use Recruiter\Option\MemoryLimit; +use Timeless as T; $factory = new Factory(); $db = $factory->getMongoDb( - $hosts = 'localhost:27017', + MongoURI::fromEnvironment(), $options = [], - $dbName = 'recruiter' ); $db->drop(); @@ -24,16 +21,19 @@ (new AlwaysFail()) ->asJobOf($recruiter) - ->retryManyTimes(5, T\seconds(1), 'DomainException') + ->retryManyTimes(5, T\seconds(1), DomainException::class) ->inBackground() - ->execute(); + ->execute() +; -$memoryLimit = new MemoryLimit('memory-limit', '64MB'); +$memoryLimit = new MemoryLimit('64MB'); $worker = $recruiter->hire($memoryLimit); while (true) { printf("Try to do my work\n"); $assignments = $recruiter->assignJobsToWorkers(); - if ($assignments === 0) break; + if (0 === $assignments) { + break; + } $worker->work(); usleep(1200 * 1000); } diff --git a/examples/jobRetriedManyTimesUntilArchived.php b/examples/jobRetriedManyTimesUntilArchived.php index e713e515..8972afcc 100755 --- a/examples/jobRetriedManyTimesUntilArchived.php +++ b/examples/jobRetriedManyTimesUntilArchived.php @@ -3,37 +3,37 @@ require __DIR__ . '/../vendor/autoload.php'; -use Timeless as T; - -use Recruiter\Recruiter; use Recruiter\Factory; +use Recruiter\Infrastructure\Memory\MemoryLimit; +use Recruiter\Infrastructure\Persistence\Mongodb\URI as MongoURI; +use Recruiter\Recruiter; use Recruiter\Workable\AlwaysFail; -use Recruiter\RetryPolicy; -use Recruiter\Worker; -use Recruiter\Option\MemoryLimit; +use Timeless as T; $factory = new Factory(); $db = $factory->getMongoDb( - $hosts = 'localhost:27017', + MongoURI::fromEnvironment(), $options = [], - $dbName = 'recruiter' ); $db->drop(); $recruiter = new Recruiter($db); -(new AlwaysFail()) +new AlwaysFail() ->asJobOf($recruiter) ->retryManyTimes(5, T\second(1)) ->inBackground() - ->execute(); + ->execute() +; -$memoryLimit = new MemoryLimit('memory-limit', '64MB'); +$memoryLimit = new MemoryLimit('64MB'); $worker = $recruiter->hire($memoryLimit); while (true) { printf("Try to do my work\n"); $assignments = $recruiter->assignJobsToWorkers(); - if ($assignments === 0) break; + if (0 === count($assignments)) { + break; + } $worker->work(); usleep(1200 * 1000); } diff --git a/examples/oneTimeJob.php b/examples/oneTimeJob.php index 16561ff9..54a15ecb 100755 --- a/examples/oneTimeJob.php +++ b/examples/oneTimeJob.php @@ -3,17 +3,16 @@ require __DIR__ . '/../vendor/autoload.php'; -use Recruiter\Recruiter; use Recruiter\Factory; +use Recruiter\Infrastructure\Memory\MemoryLimit; +use Recruiter\Infrastructure\Persistence\Mongodb\URI as MongoURI; +use Recruiter\Recruiter; use Recruiter\Workable\LazyBones; -use Recruiter\Worker; -use Recruiter\Option\MemoryLimit; $factory = new Factory(); $db = $factory->getMongoDb( - $hosts = 'localhost:27017', + MongoURI::fromEnvironment(), $options = [], - $dbName = 'recruiter' ); $db->drop(); @@ -22,9 +21,10 @@ LazyBones::waitForMs(200, 100) ->asJobOf($recruiter) ->inBackground() - ->execute(); + ->execute() +; -$memoryLimit = new MemoryLimit('memory-limit', '64MB'); +$memoryLimit = new MemoryLimit('64MB'); $worker = $recruiter->hire($memoryLimit); $assignments = $recruiter->assignJobsToWorkers(); $worker->work(); From 5bdf1ce158b65f4d359eba176387627dbdf79f65 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 15:15:55 +0200 Subject: [PATCH 33/59] Ad explicit cast to string --- src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php | 2 +- src/Recruiter/Infrastructure/Command/Bko/JobRecoverCommand.php | 2 +- .../Infrastructure/Command/Bko/RemoveSchedulerCommand.php | 2 +- src/Recruiter/Infrastructure/Command/CleanerCommand.php | 2 +- src/Recruiter/Infrastructure/Command/RecruiterCommand.php | 2 +- src/Recruiter/Infrastructure/Command/WorkerCommand.php | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php b/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php index 6c3c588c..e29aec32 100644 --- a/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php +++ b/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php @@ -35,7 +35,7 @@ protected function configure(): void 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', - MongoURI::fromEnvironment(), + (string) MongoURI::fromEnvironment(), ) ->addOption( 'group', diff --git a/src/Recruiter/Infrastructure/Command/Bko/JobRecoverCommand.php b/src/Recruiter/Infrastructure/Command/Bko/JobRecoverCommand.php index 46e53849..960ba347 100644 --- a/src/Recruiter/Infrastructure/Command/Bko/JobRecoverCommand.php +++ b/src/Recruiter/Infrastructure/Command/Bko/JobRecoverCommand.php @@ -39,7 +39,7 @@ protected function configure() 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', - MongoURI::fromEnvironment(), + (string) MongoURI::fromEnvironment(), ) ->addOption( 'scheduleAt', diff --git a/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php b/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php index 17d777eb..a81bd66a 100644 --- a/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php +++ b/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php @@ -51,7 +51,7 @@ protected function configure() 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', - MongoURI::fromEnvironment(), + (string) MongoURI::fromEnvironment(), ) ; } diff --git a/src/Recruiter/Infrastructure/Command/CleanerCommand.php b/src/Recruiter/Infrastructure/Command/CleanerCommand.php index 8ea9053c..932c9b05 100644 --- a/src/Recruiter/Infrastructure/Command/CleanerCommand.php +++ b/src/Recruiter/Infrastructure/Command/CleanerCommand.php @@ -124,7 +124,7 @@ public function description(): string public function definition(): InputDefinition { - $defaultMongoUri = MongoURI::fromEnvironment(); + $defaultMongoUri = (string) MongoURI::fromEnvironment(); return new InputDefinition([ new InputOption('target', 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', $defaultMongoUri), new InputOption('clean-after', 'c', InputOption::VALUE_REQUIRED, 'delete jobs after :period', '5days'), diff --git a/src/Recruiter/Infrastructure/Command/RecruiterCommand.php b/src/Recruiter/Infrastructure/Command/RecruiterCommand.php index 46f3903d..027f7f01 100644 --- a/src/Recruiter/Infrastructure/Command/RecruiterCommand.php +++ b/src/Recruiter/Infrastructure/Command/RecruiterCommand.php @@ -177,7 +177,7 @@ public function description(): string public function definition(): InputDefinition { - $defaultMongoUri = MongoURI::fromEnvironment(); + $defaultMongoUri = (string) MongoURI::fromEnvironment(); return new InputDefinition([ new InputOption('target', 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', $defaultMongoUri), new InputOption('backoff-to', 'b', InputOption::VALUE_REQUIRED, 'Upper limit of time to wait before next polling (milliseconds)', '1600ms'), diff --git a/src/Recruiter/Infrastructure/Command/WorkerCommand.php b/src/Recruiter/Infrastructure/Command/WorkerCommand.php index a96abd90..5ba1fd2b 100644 --- a/src/Recruiter/Infrastructure/Command/WorkerCommand.php +++ b/src/Recruiter/Infrastructure/Command/WorkerCommand.php @@ -114,7 +114,7 @@ public function description(): string public function definition(): InputDefinition { - $defaultMongoUri = MongoURI::fromEnvironment(); + $defaultMongoUri = (string) MongoURI::fromEnvironment(); return new InputDefinition([ new InputOption('target', 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', $defaultMongoUri), new InputOption('backoff-to', 'b', InputOption::VALUE_REQUIRED, 'Upper limit of time to wait before next polling', '6400ms'), From a77993c1d286c8b326a137dfdf41e78e43a5d565 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 15:34:58 +0200 Subject: [PATCH 34/59] Fix some type hints and errors --- phpstan.neon | 2 -- src/Recruiter/Factory.php | 3 ++- src/Recruiter/Workable/ThrowsFatalError.php | 1 + 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index e80c96d0..3bb2b042 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -6,10 +6,8 @@ parameters: scanDirectories: - %currentWorkingDirectory%/vendor/giorgiosironi/eris/src/ ignoreErrors: - - '#Instantiated class Recruiter\\Workable\\ThisClassDoesnNotExists not found.#' - '#Function recruiter_became_master not found.#' - '#Function recruiter_stept_back not found.#' - '#Unsafe usage of new static\(\).#' - '#Call to an undefined static method Sink\\BlackHole::whateverStaticMethod\(\).#' - - '#Constructor of class Recruiter\\Workable\\FailsInConstructor has an unused parameter \$parameters.#' inferPrivatePropertyTypeFromConstructor: true diff --git a/src/Recruiter/Factory.php b/src/Recruiter/Factory.php index d8164685..f863c0c5 100644 --- a/src/Recruiter/Factory.php +++ b/src/Recruiter/Factory.php @@ -3,12 +3,13 @@ namespace Recruiter; use MongoDB\Client; +use MongoDB\Database; use MongoDB\Driver\Exception\RuntimeException as DriverRuntimeException; use Recruiter\Infrastructure\Persistence\Mongodb\URI; class Factory { - public function getMongoDb(URI $uri, array $options = []) + public function getMongoDb(URI $uri, array $options = []): Database { try { $optionsWithMajorityConcern = ['w' => 'majority']; diff --git a/src/Recruiter/Workable/ThrowsFatalError.php b/src/Recruiter/Workable/ThrowsFatalError.php index 257cb523..e86b0996 100644 --- a/src/Recruiter/Workable/ThrowsFatalError.php +++ b/src/Recruiter/Workable/ThrowsFatalError.php @@ -11,6 +11,7 @@ class ThrowsFatalError implements Workable public function execute(): void { + /** @phpstan-ignore-next-line */ new ThisClassDoesnNotExists(); } } From 7cdcd5d1183141c01c581619be6fc325d420ae74 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 15:35:27 +0200 Subject: [PATCH 35/59] Fix Coding Standards --- spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php | 1 - src/Recruiter/Infrastructure/Command/CleanerCommand.php | 1 + src/Recruiter/Infrastructure/Command/RecruiterCommand.php | 1 + src/Recruiter/Infrastructure/Command/WorkerCommand.php | 1 + 4 files changed, 3 insertions(+), 1 deletion(-) diff --git a/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php b/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php index fc526e25..5845b8bc 100644 --- a/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php +++ b/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php @@ -8,7 +8,6 @@ use Recruiter\Concurrency\Timeout; use Recruiter\Factory; use Recruiter\Infrastructure\Persistence\Mongodb\URI; -use Recruiter\Infrastructure\Persistence\Mongodb\URI as MongoURI; use Recruiter\Recruiter; use Recruiter\RetryPolicy; use Recruiter\Workable\ShellCommand; diff --git a/src/Recruiter/Infrastructure/Command/CleanerCommand.php b/src/Recruiter/Infrastructure/Command/CleanerCommand.php index 932c9b05..cbf85798 100644 --- a/src/Recruiter/Infrastructure/Command/CleanerCommand.php +++ b/src/Recruiter/Infrastructure/Command/CleanerCommand.php @@ -125,6 +125,7 @@ public function description(): string public function definition(): InputDefinition { $defaultMongoUri = (string) MongoURI::fromEnvironment(); + return new InputDefinition([ new InputOption('target', 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', $defaultMongoUri), new InputOption('clean-after', 'c', InputOption::VALUE_REQUIRED, 'delete jobs after :period', '5days'), diff --git a/src/Recruiter/Infrastructure/Command/RecruiterCommand.php b/src/Recruiter/Infrastructure/Command/RecruiterCommand.php index 027f7f01..67b907f5 100644 --- a/src/Recruiter/Infrastructure/Command/RecruiterCommand.php +++ b/src/Recruiter/Infrastructure/Command/RecruiterCommand.php @@ -178,6 +178,7 @@ public function description(): string public function definition(): InputDefinition { $defaultMongoUri = (string) MongoURI::fromEnvironment(); + return new InputDefinition([ new InputOption('target', 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', $defaultMongoUri), new InputOption('backoff-to', 'b', InputOption::VALUE_REQUIRED, 'Upper limit of time to wait before next polling (milliseconds)', '1600ms'), diff --git a/src/Recruiter/Infrastructure/Command/WorkerCommand.php b/src/Recruiter/Infrastructure/Command/WorkerCommand.php index 5ba1fd2b..d9c1331f 100644 --- a/src/Recruiter/Infrastructure/Command/WorkerCommand.php +++ b/src/Recruiter/Infrastructure/Command/WorkerCommand.php @@ -115,6 +115,7 @@ public function description(): string public function definition(): InputDefinition { $defaultMongoUri = (string) MongoURI::fromEnvironment(); + return new InputDefinition([ new InputOption('target', 't', InputOption::VALUE_REQUIRED, 'HOSTNAME[:PORT][/DB] MongoDB coordinates', $defaultMongoUri), new InputOption('backoff-to', 'b', InputOption::VALUE_REQUIRED, 'Upper limit of time to wait before next polling', '6400ms'), From a47bfa9a1e82d936484bf71bb8be4ef3a185b517 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 15:51:17 +0200 Subject: [PATCH 36/59] Add some type hints --- src/Recruiter/JobAfterFailure.php | 32 +++++++++++-------------------- 1 file changed, 11 insertions(+), 21 deletions(-) diff --git a/src/Recruiter/JobAfterFailure.php b/src/Recruiter/JobAfterFailure.php index ba6fdd93..4d552651 100644 --- a/src/Recruiter/JobAfterFailure.php +++ b/src/Recruiter/JobAfterFailure.php @@ -7,50 +7,40 @@ class JobAfterFailure { - /** @var Job */ - private $job; + private bool $hasBeenScheduled; - /** @var JobExecution */ - private $lastJobExecution; + private bool $hasBeenArchived; - /** @var bool */ - private $hasBeenScheduled; - - /** @var bool */ - private $hasBeenArchived; - - public function __construct(Job $job, JobExecution $lastJobExecution) + public function __construct(private readonly Job $job, private readonly JobExecution $lastJobExecution) { - $this->job = $job; - $this->lastJobExecution = $lastJobExecution; $this->hasBeenScheduled = false; $this->hasBeenArchived = false; } - public function createdAt() + public function createdAt(): Moment { return $this->job->createdAt(); } - public function inGroup($group) + public function inGroup($group): void { $this->job->inGroup($group); $this->job->save(); } - public function scheduleIn(Interval $in) + public function scheduleIn(Interval $in): void { $this->scheduleAt($in->fromNow()); } - public function scheduleAt(Moment $at) + public function scheduleAt(Moment $at): void { $this->hasBeenScheduled = true; $this->job->scheduleAt($at); $this->job->save(); } - public function archive($why) + public function archive($why): void { $this->hasBeenArchived = true; $this->job->archive($why); @@ -61,7 +51,7 @@ public function causeOfFailure() return $this->lastJobExecution->causeOfFailure(); } - public function lastExecutionDuration() + public function lastExecutionDuration(): Interval { return $this->lastJobExecution->duration(); } @@ -71,7 +61,7 @@ public function numberOfAttempts() return $this->job->numberOfAttempts(); } - public function archiveIfNotScheduled() + public function archiveIfNotScheduled(): bool { if (!$this->hasBeenScheduled && !$this->hasBeenArchived) { $this->archive('not-scheduled-by-retry-policy'); @@ -82,7 +72,7 @@ public function archiveIfNotScheduled() return false; } - public function hasBeenArchived() + public function hasBeenArchived(): bool { return $this->hasBeenArchived; } From 98b908aecf426a4314a97848aaaffc13a836075d Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 21:16:52 +0200 Subject: [PATCH 37/59] Move further steps with rector --- ...obFailedBecauseOfNonRetriableException.php | 2 +- rector.php | 6 +- .../Acceptance/BaseAcceptanceTestCase.php | 4 +- spec/Recruiter/Acceptance/EnduranceTest.php | 75 ++++++++----------- .../Acceptance/FaultToleranceTest.php | 12 +-- spec/Recruiter/Acceptance/HooksTest.php | 29 +++---- .../RepeatableJobsAreScheduledTest.php | 20 +++-- .../Acceptance/SyncronousExecutionTest.php | 6 +- ...rGuaranteedToExitWhenAMemoryLeakOccurs.php | 6 +- ...itWithFailureCodeInCaseOfExceptionTest.php | 4 +- ...WorkerGuaranteedToRetireAfterDeathTest.php | 2 +- .../Acceptance/WorkerRepositoryTest.php | 3 +- spec/Recruiter/CleanerTest.php | 6 +- spec/Recruiter/ExampleTest.php | 2 +- spec/Recruiter/FactoryTest.php | 8 +- ...rkableImplementsFinalizerInterfaceTest.php | 21 ++---- .../Infrastructure/Memory/MemoryLimitTest.php | 2 +- spec/Recruiter/Job/EventTest.php | 6 +- spec/Recruiter/Job/RepositoryTest.php | 30 ++++---- .../JobCallCustomMethodOnWorkableTest.php | 8 +- .../Recruiter/JobSendEventsToWorkableTest.php | 8 +- ...keRetryPolicyFromRetriableWorkableTest.php | 4 +- spec/Recruiter/JobTest.php | 6 +- .../JobToBePassedRetryStatisticsTest.php | 2 +- spec/Recruiter/JobToScheduleTest.php | 42 +++++------ spec/Recruiter/PickAvailableWorkersTest.php | 8 +- .../RetryPolicy/ExponentialBackoffTest.php | 8 +- .../RetriableExceptionFilterTest.php | 24 +++--- .../RetryPolicy/SelectByExceptionTest.php | 10 +-- spec/Recruiter/RetryPolicy/TimeTableTest.php | 24 +++--- spec/Recruiter/SchedulePolicy/CronTest.php | 2 +- spec/Recruiter/TaggableWorkableTest.php | 19 ++--- spec/Recruiter/WaitStrategyTest.php | 12 +-- .../Workable/FactoryMethodCommandTest.php | 6 +- spec/Recruiter/Workable/ShellCommandTest.php | 4 +- spec/Recruiter/WorkablePersistenceTest.php | 2 +- spec/Recruiter/WorkerProcessTest.php | 8 +- spec/Sink/BlackHoleTest.php | 32 ++++---- spec/Timeless/IntervalFormatTest.php | 4 +- spec/Timeless/IntervalParseTest.php | 10 +-- spec/Timeless/MongoDateTest.php | 2 +- src/Recruiter/Cleaner.php | 8 +- src/Recruiter/Finalizable.php | 8 +- src/Recruiter/FinalizableBehaviour.php | 8 +- .../Command/Bko/AnalyticsCommand.php | 4 +- .../Command/Bko/RemoveSchedulerCommand.php | 14 +--- .../Infrastructure/Command/CleanerCommand.php | 45 ++--------- .../Command/RecruiterCommand.php | 47 ++---------- .../Infrastructure/Command/WorkerCommand.php | 13 +--- .../Filesystem/BootstrapFile.php | 15 +--- src/Recruiter/Job.php | 43 ++++++----- src/Recruiter/Job/EventListener.php | 2 +- src/Recruiter/JobExecution.php | 36 ++++----- src/Recruiter/JobToSchedule.php | 24 +++--- src/Recruiter/Recruiter.php | 67 ++++++++--------- src/Recruiter/RepeatableInJob.php | 2 +- src/Recruiter/RetryPolicy.php | 4 +- src/Recruiter/RetryPolicy/DoNotDoItAgain.php | 2 +- .../RetryPolicy/ExponentialBackoff.php | 17 ++--- .../RetryPolicy/RetriableException.php | 6 +- .../RetryPolicy/RetriableExceptionFilter.php | 14 ++-- src/Recruiter/RetryPolicy/RetryForever.php | 8 +- src/Recruiter/RetryPolicy/RetryManyTimes.php | 11 ++- .../RetryPolicy/SelectByException.php | 32 ++++---- src/Recruiter/RetryPolicy/TimeTable.php | 22 +++--- src/Recruiter/RetryPolicyBehaviour.php | 2 +- src/Recruiter/RetryPolicyInJob.php | 2 +- src/Recruiter/SchedulePolicy/Cron.php | 7 +- src/Recruiter/SchedulePolicyInJob.php | 2 +- src/Recruiter/Scheduler.php | 28 +------ src/Recruiter/Scheduler/Repository.php | 12 ++- src/Recruiter/SynchronousExecutionReport.php | 12 +-- src/Recruiter/Taggable.php | 4 +- src/Recruiter/WaitStrategy.php | 4 +- src/Recruiter/Workable/AlwaysFail.php | 2 +- .../Workable/FactoryMethodCommand.php | 11 +-- src/Recruiter/Workable/LazyBones.php | 2 +- .../RecoverRepeatableFromException.php | 11 +-- .../Workable/RecoverWorkableFromException.php | 11 +-- .../Workable/SampleRepeatableCommand.php | 2 +- src/Recruiter/Workable/ShellCommand.php | 5 +- src/Recruiter/WorkableBehaviour.php | 2 +- src/Recruiter/WorkableInJob.php | 2 +- src/Recruiter/Worker.php | 27 ++----- src/Recruiter/Worker/Process.php | 13 ++-- src/Recruiter/Worker/Repository.php | 4 +- src/Recruiter/functions.php | 4 +- src/Sink/BlackHole.php | 2 +- 88 files changed, 449 insertions(+), 663 deletions(-) diff --git a/examples/jobFailedBecauseOfNonRetriableException.php b/examples/jobFailedBecauseOfNonRetriableException.php index 96f6aef7..18e088bf 100755 --- a/examples/jobFailedBecauseOfNonRetriableException.php +++ b/examples/jobFailedBecauseOfNonRetriableException.php @@ -19,7 +19,7 @@ $recruiter = new Recruiter($db); -(new AlwaysFail()) +new AlwaysFail() ->asJobOf($recruiter) ->retryManyTimes(5, T\seconds(1), DomainException::class) ->inBackground() diff --git a/rector.php b/rector.php index b2252895..1d25ee45 100644 --- a/rector.php +++ b/rector.php @@ -11,7 +11,7 @@ __DIR__ . '/src', ]) // uncomment to reach your current PHP version - // ->withPhpSets() - ->withTypeCoverageLevel(0) + ->withPhpSets() + ->withTypeCoverageLevel(2) ->withDeadCodeLevel(0) - ->withCodeQualityLevel(0); + ->withCodeQualityLevel(1); diff --git a/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php b/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php index 5845b8bc..11ec14dd 100644 --- a/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php +++ b/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php @@ -171,9 +171,7 @@ protected function stopProcessWithSignal(array $processAndPipes, $signal): void [$process, $pipes, $name] = $processAndPipes; proc_terminate($process, $signal); $this->lastStatus = proc_get_status($process); - Timeout::inSeconds(30, function () use ($signal) { - return 'termination of process: ' . var_export($this->lastStatus, true) . " after sending the `$signal` signal to it"; - }) + Timeout::inSeconds(30, fn () => 'termination of process: ' . var_export($this->lastStatus, true) . " after sending the `$signal` signal to it") ->until(function () use ($process) { $this->lastStatus = proc_get_status($process); diff --git a/spec/Recruiter/Acceptance/EnduranceTest.php b/spec/Recruiter/Acceptance/EnduranceTest.php index a0406630..650bb6ee 100644 --- a/spec/Recruiter/Acceptance/EnduranceTest.php +++ b/spec/Recruiter/Acceptance/EnduranceTest.php @@ -19,6 +19,7 @@ class EnduranceTest extends BaseAcceptanceTestCase private Repository $jobRepository; private string $actionLog; + #[\Override] protected function setUp(): void { parent::setUp(); @@ -27,51 +28,43 @@ protected function setUp(): void $this->files[] = $this->actionLog; } - public function testNotWithstandingCrashesJobsAreEventuallyPerformed() + public function testNotWithstandingCrashesJobsAreEventuallyPerformed(): void { $this ->limitTo(100) ->forAll( Generator\bind( Generator\choose(1, 4), - function ($workers) { - return Generator\tuple( - Generator\constant($workers), - Generator\seq(Generator\oneOf( - Generator\map( - function ($durationAndTag) { - [$duration, $tag] = $durationAndTag; + fn ($workers) => Generator\tuple( + Generator\constant($workers), + Generator\seq(Generator\oneOf( + Generator\map( + function ($durationAndTag) { + [$duration, $tag] = $durationAndTag; - return ['enqueueJob', $duration, $tag]; - }, - Generator\tuple( - Generator\nat(), - Generator\elements(['generic', 'fast-lane']), - ), + return ['enqueueJob', $duration, $tag]; + }, + Generator\tuple( + Generator\nat(), + Generator\elements(['generic', 'fast-lane']), ), - Generator\map( - function ($workerIndex) { - return ['restartWorkerGracefully', $workerIndex]; - }, - Generator\choose(0, $workers - 1), - ), - Generator\map( - function ($workerIndex) { - return ['restartWorkerByKilling', $workerIndex]; - }, - Generator\choose(0, $workers - 1), - ), - Generator\constant('restartRecruiterGracefully'), - Generator\constant('restartRecruiterByKilling'), - Generator\map( - function ($milliseconds) { - return ['sleep', $milliseconds]; - }, - Generator\choose(1, 1000), - ), - )), - ); - }, + ), + Generator\map( + fn ($workerIndex) => ['restartWorkerGracefully', $workerIndex], + Generator\choose(0, $workers - 1), + ), + Generator\map( + fn ($workerIndex) => ['restartWorkerByKilling', $workerIndex], + Generator\choose(0, $workers - 1), + ), + Generator\constant('restartRecruiterGracefully'), + Generator\constant('restartRecruiterByKilling'), + Generator\map( + fn ($milliseconds) => ['sleep', $milliseconds], + Generator\choose(1, 1000), + ), + )), + ), ), ) ->hook(Listener\log('/tmp/recruiter-test-iterations.log')) @@ -98,13 +91,9 @@ function ($milliseconds) { $estimatedTime = max(count($actions) * 4, 60); Timeout::inSeconds( $estimatedTime, - function () { - return "all $this->jobs jobs to be performed. Now is " . date('c') . ' Logs: ' . $this->files(); - }, + fn () => "all $this->jobs jobs to be performed. Now is " . date('c') . ' Logs: ' . $this->files(), ) - ->until(function () { - return $this->jobRepository->countArchived() === $this->jobs; - }) + ->until(fn () => $this->jobRepository->countArchived() === $this->jobs) ; $at = T\now(); diff --git a/spec/Recruiter/Acceptance/FaultToleranceTest.php b/spec/Recruiter/Acceptance/FaultToleranceTest.php index c4053ec8..6c1140c7 100644 --- a/spec/Recruiter/Acceptance/FaultToleranceTest.php +++ b/spec/Recruiter/Acceptance/FaultToleranceTest.php @@ -10,7 +10,7 @@ class FaultToleranceTest extends BaseAcceptanceTestCase { - public function testRecruiterCrashAfterLockingJobsBeforeAssignmentAndIsRestarted() + public function testRecruiterCrashAfterLockingJobsBeforeAssignmentAndIsRestarted(): void { $memoryLimit = new MemoryLimit('64MB'); $this->enqueueJob(); @@ -22,9 +22,9 @@ public function testRecruiterCrashAfterLockingJobsBeforeAssignmentAndIsRestarted $this->assertEquals(1, $totalNumber); } - public function testRetryPolicyMustBeAppliedEvenWhenWorkerDiesInConstructor() + public function testRetryPolicyMustBeAppliedEvenWhenWorkerDiesInConstructor(): void { - (new FailsInConstructor([], false)) + new FailsInConstructor([], false) ->asJobOf($this->recruiter) ->inBackground() ->retryWithPolicy(RetryManyTimes::forTimes(1, 0)) @@ -39,7 +39,7 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDiesInConstructor() sleep(2); $jobDocument = current($this->scheduled->find()->toArray()); $this->assertEquals(1, $jobDocument['attempts']); - $this->assertEquals('Recruiter\Workable\FailsInConstructor', $jobDocument['workable']['class']); + $this->assertEquals(FailsInConstructor::class, $jobDocument['workable']['class']); $this->assertStringContainsString('This job failed while instantiating a workable', $jobDocument['last_execution']['message']); $this->assertStringContainsString('I am supposed to fail in constructor code for testing purpose', $jobDocument['last_execution']['message']); @@ -48,7 +48,7 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDiesInConstructor() sleep(2); $jobDocument = current($this->archived->find()->toArray()); $this->assertEquals(2, $jobDocument['attempts']); - $this->assertEquals('Recruiter\Workable\FailsInConstructor', $jobDocument['workable']['class']); + $this->assertEquals(FailsInConstructor::class, $jobDocument['workable']['class']); $this->assertStringContainsString('This job failed while instantiating a workable', $jobDocument['last_execution']['message']); $this->assertStringContainsString('I am supposed to fail in constructor code for testing purpose', $jobDocument['last_execution']['message']); @@ -56,7 +56,7 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDiesInConstructor() $this->assertEquals(0, count($assignments)); } - public function testRetryPolicyMustBeAppliedEvenWhenWorkerDies() + public function testRetryPolicyMustBeAppliedEvenWhenWorkerDies(): void { // This job will fail with a fatal error and we want it to be // retried at most 1 time, this means at most 2 diff --git a/spec/Recruiter/Acceptance/HooksTest.php b/spec/Recruiter/Acceptance/HooksTest.php index 07b03d30..f52bb3ce 100644 --- a/spec/Recruiter/Acceptance/HooksTest.php +++ b/spec/Recruiter/Acceptance/HooksTest.php @@ -13,13 +13,14 @@ class HooksTest extends BaseAcceptanceTestCase private MemoryLimit $memoryLimit; private array $events; + #[\Override] protected function setUp(): void { $this->memoryLimit = new MemoryLimit('64MB'); parent::setUp(); } - public function testAfterFailureWithoutRetryEventIsFired() + public function testAfterFailureWithoutRetryEventIsFired(): void { $this->events = []; $this->recruiter @@ -32,7 +33,7 @@ function (Event $event): void { ) ; - $job = (new AlwaysFail()) + $job = new AlwaysFail() ->asJobOf($this->recruiter) ->inBackground() ->execute() @@ -43,11 +44,11 @@ function (Event $event): void { $worker->work(); $this->assertEquals(1, count($this->events)); - $this->assertInstanceOf('Recruiter\Job\Event', $this->events[0]); + $this->assertInstanceOf(Event::class, $this->events[0]); $this->assertEquals('not-scheduled-by-retry-policy', $this->events[0]->export()['why']); } - public function testAfterLastFailureEventIsFired() + public function testAfterLastFailureEventIsFired(): void { $this->events = []; $this->recruiter @@ -60,7 +61,7 @@ function (Event $event): void { ) ; - $job = (new AlwaysFail()) + $job = new AlwaysFail() ->asJobOf($this->recruiter) ->retryWithPolicy(RetryManyTimes::forTimes(1, 0)) ->inBackground() @@ -81,11 +82,11 @@ function (Event $event): void { $runAJob(2, $worker); $this->assertEquals(1, count($this->events)); - $this->assertInstanceOf('Recruiter\Job\Event', $this->events[0]); + $this->assertInstanceOf(Event::class, $this->events[0]); $this->assertEquals('tried-too-many-times', $this->events[0]->export()['why']); } - public function testJobStartedIsFired() + public function testJobStartedIsFired(): void { $this->events = []; $this->recruiter @@ -98,7 +99,7 @@ function (Event $event): void { ) ; - $job = (new AlwaysSucceed()) + $job = new AlwaysSucceed() ->asJobOf($this->recruiter) ->inBackground() ->execute() @@ -109,10 +110,10 @@ function (Event $event): void { $worker->work(); $this->assertEquals(1, count($this->events)); - $this->assertInstanceOf('Recruiter\Job\Event', $this->events[0]); + $this->assertInstanceOf(Event::class, $this->events[0]); } - public function testJobEndedIsFired() + public function testJobEndedIsFired(): void { $this->events = []; $this->recruiter @@ -125,13 +126,13 @@ function (Event $event): void { ) ; - (new AlwaysSucceed()) + new AlwaysSucceed() ->asJobOf($this->recruiter) ->inBackground() ->execute() ; - (new AlwaysFail()) + new AlwaysFail() ->asJobOf($this->recruiter) ->inBackground() ->execute() @@ -144,7 +145,7 @@ function (Event $event): void { $worker->work(); $this->assertEquals(2, count($this->events)); - $this->assertInstanceOf('Recruiter\Job\Event', $this->events[0]); - $this->assertInstanceOf('Recruiter\Job\Event', $this->events[1]); + $this->assertInstanceOf(Event::class, $this->events[0]); + $this->assertInstanceOf(Event::class, $this->events[1]); } } diff --git a/spec/Recruiter/Acceptance/RepeatableJobsAreScheduledTest.php b/spec/Recruiter/Acceptance/RepeatableJobsAreScheduledTest.php index e847ea58..f5d692cc 100644 --- a/spec/Recruiter/Acceptance/RepeatableJobsAreScheduledTest.php +++ b/spec/Recruiter/Acceptance/RepeatableJobsAreScheduledTest.php @@ -15,7 +15,7 @@ class RepeatableJobsAreScheduledTest extends BaseAcceptanceTestCase { use ArraySubsetAsserts; - public function testARepeatableJobIsScheduledAtExpectedScheduledTime() + public function testARepeatableJobIsScheduledAtExpectedScheduledTime(): void { $expectedScheduleDate = strtotime('2019-05-16T14:00:00'); $schedulePolicy = new FixedSchedulePolicy($expectedScheduleDate); @@ -34,7 +34,7 @@ public function testARepeatableJobIsScheduledAtExpectedScheduledTime() 'attempts' => 0, 'group' => 'generic', 'workable' => [ - 'class' => 'Recruiter\Workable\SampleRepeatableCommand', + 'class' => SampleRepeatableCommand::class, 'parameters' => [], 'method' => 'execute', ], @@ -47,7 +47,7 @@ public function testARepeatableJobIsScheduledAtExpectedScheduledTime() 'executions' => 1, ], 'retry_policy' => [ - 'class' => 'Recruiter\RetryPolicy\ExponentialBackoff', + 'class' => ExponentialBackoff::class, 'parameters' => [ 'retry_how_many_times' => 2, 'seconds_to_initially_wait_before_retry' => 5, @@ -56,7 +56,7 @@ public function testARepeatableJobIsScheduledAtExpectedScheduledTime() ], $jobData); } - public function testOnlyASingleJobAreScheduledForTheSameSchedulingTime() + public function testOnlyASingleJobAreScheduledForTheSameSchedulingTime(): void { $expectedScheduleDate = strtotime('2019-05-16T14:00:00'); $schedulePolicy = new FixedSchedulePolicy($expectedScheduleDate); @@ -75,7 +75,7 @@ public function testOnlyASingleJobAreScheduledForTheSameSchedulingTime() ); } - public function testAJobIsScheduledForEverySchedulingTime() + public function testAJobIsScheduledForEverySchedulingTime(): void { $expectedScheduleDates = [ strtotime('2019-05-16T14:00:00'), @@ -93,7 +93,7 @@ public function testAJobIsScheduledForEverySchedulingTime() $this->assertEquals(1, $jobs[1]->export()['scheduled']['executions']); } - public function testANewJobIsNotScheduledIfItShouldBeUniqueAndTheOldOneIsStillRunning() + public function testANewJobIsNotScheduledIfItShouldBeUniqueAndTheOldOneIsStillRunning(): void { $schedulePolicy = new FixedSchedulePolicy([ strtotime('2019-05-16T14:00:00'), @@ -108,7 +108,7 @@ public function testANewJobIsNotScheduledIfItShouldBeUniqueAndTheOldOneIsStillRu $this->assertEquals(1, count($jobs)); } - public function testSchedulersAreUniqueOnUrn() + public function testSchedulersAreUniqueOnUrn(): void { $aSchedulerAlreadyHaveSomeAttempts = 3; $this->IHaveAScheduleWithALongStory('unique-urn', $aSchedulerAlreadyHaveSomeAttempts); @@ -149,7 +149,7 @@ private function scheduleAJob(string $urn, ?SchedulePolicy $schedulePolicy = nul $schedulePolicy = new FixedSchedulePolicy(strtotime('2023-02-18T17:00:00')); } - $scheduler = (new SampleRepeatableCommand()) + $scheduler = new SampleRepeatableCommand() ->asRepeatableJobOf($this->recruiter) ->repeatWithPolicy($schedulePolicy) ->retryWithPolicy(ExponentialBackoff::forTimes(2, 5)) @@ -185,16 +185,14 @@ private function fetchSchedulers() class FixedSchedulePolicy implements SchedulePolicy { private array $timestamps; - private int $index; - public function __construct($timestamps, $index = 0) + public function __construct($timestamps, private int $index = 0) { if (!is_array($timestamps)) { $timestamps = [$timestamps]; } $this->timestamps = $timestamps; - $this->index = $index; } public function next(): Moment diff --git a/spec/Recruiter/Acceptance/SyncronousExecutionTest.php b/spec/Recruiter/Acceptance/SyncronousExecutionTest.php index 34f3dde6..382c0524 100644 --- a/spec/Recruiter/Acceptance/SyncronousExecutionTest.php +++ b/spec/Recruiter/Acceptance/SyncronousExecutionTest.php @@ -8,7 +8,7 @@ class SyncronousExecutionTest extends BaseAcceptanceTestCase { - public function testJobsAreExecutedInOrderOfScheduling() + public function testJobsAreExecutedInOrderOfScheduling(): void { $this->enqueueAnAnswerJob(43, T\now()->after(T\seconds(30))); @@ -22,9 +22,9 @@ public function testJobsAreExecutedInOrderOfScheduling() $this->assertEquals(43, end($results)->result()); } - public function testAReportIsReturnedInOrderToSortOutIfAnErrorOccured() + public function testAReportIsReturnedInOrderToSortOutIfAnErrorOccured(): void { - (new AlwaysFail()) + new AlwaysFail() ->asJobOf($this->recruiter) ->inBackground() ->execute() diff --git a/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWhenAMemoryLeakOccurs.php b/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWhenAMemoryLeakOccurs.php index 0ab1fb42..301363c5 100644 --- a/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWhenAMemoryLeakOccurs.php +++ b/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWhenAMemoryLeakOccurs.php @@ -13,12 +13,12 @@ class WorkerGuaranteedToExitWhenAMemoryLeakOccurs extends BaseAcceptanceTestCase * * @dataProvider provideMemoryConsumptions */ - public function testWorkerKillItselfAfterAMemoryLeakButNotAfterABigMemoryConsumptionWithoutLeak($withMemoryLeak, $howManyItems, $memoryLimit, $expectedWorkerAlive) + public function testWorkerKillItselfAfterAMemoryLeakButNotAfterABigMemoryConsumptionWithoutLeak($withMemoryLeak, $howManyItems, $memoryLimit, $expectedWorkerAlive): void { - (new ConsumingMemoryCommand([ + new ConsumingMemoryCommand([ 'withMemoryLeak' => $withMemoryLeak, 'howManyItems' => $howManyItems, - ])) + ]) ->asJobOf($this->recruiter) ->inBackground() ->execute() diff --git a/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest.php b/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest.php index 3d11950e..5070e67b 100644 --- a/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest.php +++ b/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest.php @@ -9,9 +9,9 @@ class WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest extends BaseAcc /** * @group acceptance */ - public function testInCaseOfExceptionTheExitCodeOfWorkerProcessIsNotZero() + public function testInCaseOfExceptionTheExitCodeOfWorkerProcessIsNotZero(): void { - (new ThrowsFatalError()) + new ThrowsFatalError() ->asJobOf($this->recruiter) ->inBackground() ->execute() diff --git a/spec/Recruiter/Acceptance/WorkerGuaranteedToRetireAfterDeathTest.php b/spec/Recruiter/Acceptance/WorkerGuaranteedToRetireAfterDeathTest.php index 83996c13..8e6c93fe 100644 --- a/spec/Recruiter/Acceptance/WorkerGuaranteedToRetireAfterDeathTest.php +++ b/spec/Recruiter/Acceptance/WorkerGuaranteedToRetireAfterDeathTest.php @@ -7,7 +7,7 @@ class WorkerGuaranteedToRetireAfterDeathTest extends BaseAcceptanceTestCase /** * @group acceptance */ - public function testRetireAfterAskedToStop() + public function testRetireAfterAskedToStop(): void { $numberOfWorkersBefore = $this->numberOfWorkers(); $processAndPipes = $this->startWorker(); diff --git a/spec/Recruiter/Acceptance/WorkerRepositoryTest.php b/spec/Recruiter/Acceptance/WorkerRepositoryTest.php index cb1949d0..0c27dd63 100644 --- a/spec/Recruiter/Acceptance/WorkerRepositoryTest.php +++ b/spec/Recruiter/Acceptance/WorkerRepositoryTest.php @@ -8,6 +8,7 @@ class WorkerRepositoryTest extends BaseAcceptanceTestCase { private Repository $repository; + #[\Override] protected function setUp(): void { parent::setUp(); @@ -20,7 +21,7 @@ protected function setUp(): void /** * @group acceptance */ - public function testRetireWorkerWithPid() + public function testRetireWorkerWithPid(): void { $this->givenWorkerWithPid(10); $this->assertEquals(1, $this->numberOfWorkers()); diff --git a/spec/Recruiter/CleanerTest.php b/spec/Recruiter/CleanerTest.php index 0c8edb7f..8ae984a4 100644 --- a/spec/Recruiter/CleanerTest.php +++ b/spec/Recruiter/CleanerTest.php @@ -38,12 +38,12 @@ protected function tearDown(): void T\clock()->start(); } - public function testShouldCreateCleaner() + public function testShouldCreateCleaner(): void { - $this->assertInstanceOf('Recruiter\Cleaner', $this->cleaner); + $this->assertInstanceOf(Cleaner::class, $this->cleaner); } - public function testDelegatesTheCleanupOfArchivedJobsToTheJobsRepository() + public function testDelegatesTheCleanupOfArchivedJobsToTheJobsRepository(): void { $expectedUpperLimit = $this->now->before($this->interval); diff --git a/spec/Recruiter/ExampleTest.php b/spec/Recruiter/ExampleTest.php index 9b9b279f..9b4eecf5 100644 --- a/spec/Recruiter/ExampleTest.php +++ b/spec/Recruiter/ExampleTest.php @@ -6,7 +6,7 @@ class ExampleTest extends TestCase { - public function testMustPass() + public function testMustPass(): void { $this->assertTrue(true); } diff --git a/spec/Recruiter/FactoryTest.php b/spec/Recruiter/FactoryTest.php index 3128cdc4..9f7fa61d 100644 --- a/spec/Recruiter/FactoryTest.php +++ b/spec/Recruiter/FactoryTest.php @@ -17,21 +17,21 @@ protected function setUp(): void $this->mongoURI = MongoURI::fromEnvironment(); } - public function testShouldCreateAMongoDatabaseConnection() + public function testShouldCreateAMongoDatabaseConnection(): void { $this->assertInstanceOf( - 'MongoDB\Database', + Database::class, $this->creationOfDefaultMongoDb(), ); } - public function testWriteConcernIsMajorityByDefault() + public function testWriteConcernIsMajorityByDefault(): void { $mongoDb = $this->creationOfDefaultMongoDb(); $this->assertEquals('majority', $mongoDb->getWriteConcern()->getW()); } - public function testShouldOverwriteTheWriteConcernPassedInTheOptions() + public function testShouldOverwriteTheWriteConcernPassedInTheOptions(): void { $mongoDb = $this->factory->getMongoDb( $this->mongoURI, diff --git a/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php b/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php index a89b6c6c..daceaafb 100644 --- a/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php +++ b/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php @@ -52,11 +52,9 @@ public function testFinalizableFailureMethodsAreCalledWhenJobFails(): void $this->assertSame($exception, $calls[2][1]); } - public function testFinalizableSuccessfullMethodsAreCalledWhenJobIsDone() + public function testFinalizableSuccessfullMethodsAreCalledWhenJobIsDone(): void { - $workable = new FinalizableWorkable(function () { - return true; - }, $this->listener); + $workable = new FinalizableWorkable(fn () => true, $this->listener); $job = Job::around($workable, $this->repository); $job->execute($this->dispatcher); @@ -85,38 +83,35 @@ class FinalizableWorkable implements Workable, Finalizable private $whatToDo; - private $listener; - - public function __construct(callable $whatToDo, $listener) + public function __construct(callable $whatToDo, private $listener) { $this->parameters = []; - $this->listener = $listener; $this->whatToDo = $whatToDo; } - public function execute() + public function execute(): mixed { $whatToDo = $this->whatToDo; return $whatToDo(); } - public function afterSuccess() + public function afterSuccess(): void { $this->listener->methodWasCalled(__FUNCTION__); } - public function afterFailure(\Exception $e) + public function afterFailure(\Exception $e): void { $this->listener->methodWasCalled(__FUNCTION__, $e); } - public function afterLastFailure(\Exception $e) + public function afterLastFailure(\Exception $e): void { $this->listener->methodWasCalled(__FUNCTION__, $e); } - public function finalize(?\Exception $e = null) + public function finalize(?\Exception $e = null): void { $this->listener->methodWasCalled(__FUNCTION__, $e); } diff --git a/spec/Recruiter/Infrastructure/Memory/MemoryLimitTest.php b/spec/Recruiter/Infrastructure/Memory/MemoryLimitTest.php index 75507e1d..52012ec7 100644 --- a/spec/Recruiter/Infrastructure/Memory/MemoryLimitTest.php +++ b/spec/Recruiter/Infrastructure/Memory/MemoryLimitTest.php @@ -8,7 +8,7 @@ class MemoryLimitTest extends TestCase { - public function testThrowsAnExceptionWhenMemoryLimitIsExceeded() + public function testThrowsAnExceptionWhenMemoryLimitIsExceeded(): void { $this->expectException(MemoryLimitExceededException::class); $memoryLimit = new MemoryLimit(1); diff --git a/spec/Recruiter/Job/EventTest.php b/spec/Recruiter/Job/EventTest.php index 4feb8e8b..33e5cb62 100644 --- a/spec/Recruiter/Job/EventTest.php +++ b/spec/Recruiter/Job/EventTest.php @@ -6,7 +6,7 @@ class EventTest extends TestCase { - public function testHasTagReturnsTrueWhenTheExportedJobContainsTheTag() + public function testHasTagReturnsTrueWhenTheExportedJobContainsTheTag(): void { $event = new Event([ 'group' => 'generic', @@ -18,7 +18,7 @@ public function testHasTagReturnsTrueWhenTheExportedJobContainsTheTag() $this->assertTrue($event->hasTag('billing-notification')); } - public function testHasTagReturnsFalseWhenTheExportedJobDoesNotContainTheTag() + public function testHasTagReturnsFalseWhenTheExportedJobDoesNotContainTheTag(): void { $event = new Event([ 'group' => 'generic', @@ -30,7 +30,7 @@ public function testHasTagReturnsFalseWhenTheExportedJobDoesNotContainTheTag() $this->assertFalse($event->hasTag('inexistant-tag')); } - public function testHasTagReturnsFalseWhenTheExportedJobDoesNotContainTags() + public function testHasTagReturnsFalseWhenTheExportedJobDoesNotContainTags(): void { $event = new Event([ ]); diff --git a/spec/Recruiter/Job/RepositoryTest.php b/spec/Recruiter/Job/RepositoryTest.php index 63c237ad..915e2685 100644 --- a/spec/Recruiter/Job/RepositoryTest.php +++ b/spec/Recruiter/Job/RepositoryTest.php @@ -43,7 +43,7 @@ protected function tearDown(): void T\clock()->start(); } - public function testCountsQueuedJobsAsOfNow() + public function testCountsQueuedJobsAsOfNow(): void { $this->aJobToSchedule()->inGroup('generic')->inBackground()->execute(); $this->aJobToSchedule()->inGroup('generic')->inBackground()->execute(); @@ -53,7 +53,7 @@ public function testCountsQueuedJobsAsOfNow() $this->assertEquals(1, $this->repository->queued('fast-lane')); } - public function testCountsQueuedJobsWithCornerCaseTagging() + public function testCountsQueuedJobsWithCornerCaseTagging(): void { $this->aJobToSchedule()->inBackground()->execute(); $this->aJobToSchedule()->inGroup([])->inBackground()->execute(); @@ -63,7 +63,7 @@ public function testCountsQueuedJobsWithCornerCaseTagging() $this->assertEquals(4, $this->repository->queued('generic')); } - public function testCountsQueudJobsWithScheduledAtGreatherThanASpecificDate() + public function testCountsQueudJobsWithScheduledAtGreatherThanASpecificDate(): void { $this->aJobToSchedule()->inBackground()->execute(); $time1 = $this->clock->now(); @@ -79,14 +79,14 @@ public function testCountsQueudJobsWithScheduledAtGreatherThanASpecificDate() ); } - public function testCountsPostponedJobs() + public function testCountsPostponedJobs(): void { $this->aJobToSchedule()->inBackground()->execute(); $this->aJobToSchedule()->scheduleIn(T\hour(24))->execute(); $this->assertEquals(1, $this->repository->postponed('generic')); } - public function testRecentHistory() + public function testRecentHistory(): void { $ed = $this->eventDispatcher; $this->repository->archive($this->aJob()->beforeExecution($ed)->afterExecution(42, $ed)); @@ -109,7 +109,7 @@ public function testRecentHistory() ); } - public function testCountQueuedJobsGroupingByASpecificKeyword() + public function testCountQueuedJobsGroupingByASpecificKeyword(): void { $workable1 = $this->workableMock(); $workable2 = $this->workableMock(); @@ -142,7 +142,7 @@ public function testCountQueuedJobsGroupingByASpecificKeyword() ); } - public function testGetDelayedScheduledJobs() + public function testGetDelayedScheduledJobs(): void { $workable1 = $this->workableMockWithCustomParameters([ 'job1' => 'delayed_and_unpicked', @@ -168,7 +168,7 @@ public function testGetDelayedScheduledJobs() $this->assertEquals(2, $jobsFounds); } - public function testCountDelayedScheduledJobs() + public function testCountDelayedScheduledJobs(): void { $this->aJobToSchedule($this->aJob())->inBackground()->execute(); $this->aJobToSchedule($this->aJob())->inBackground()->execute(); @@ -179,7 +179,7 @@ public function testCountDelayedScheduledJobs() $this->assertEquals(2, $this->repository->countDelayedScheduledJobs($lowerLimit)); } - public function testCountRecentJobsWithManyAttempts() + public function testCountRecentJobsWithManyAttempts(): void { $ed = $this->eventDispatcher; $this->repository->archive($this->aJob()->beforeExecution($ed)->beforeExecution($ed)->afterExecution(42, $ed)); @@ -204,7 +204,7 @@ public function testCountRecentJobsWithManyAttempts() $this->assertEquals(4, $this->repository->countRecentJobsWithManyAttempts($lowerLimit, $upperLimit)); } - public function testGetRecentJobsWithManyAttempts() + public function testGetRecentJobsWithManyAttempts(): void { $ed = $this->eventDispatcher; $workable1 = $this->workableMockWithCustomParameters([ @@ -251,7 +251,7 @@ public function testGetRecentJobsWithManyAttempts() $this->assertEquals(4, $jobsFounds); } - public function testCountSlowRecentJobs() + public function testCountSlowRecentJobs(): void { $ed = $this->eventDispatcher; $elapseTimeInSecondsBeforeJobsExecutionEnd = 6; @@ -314,7 +314,7 @@ public function testCountSlowRecentJobs() $this->assertEquals(4, $this->repository->countSlowRecentJobs($lowerLimit, $upperLimit)); } - public function testGetSlowRecentJobs() + public function testGetSlowRecentJobs(): void { $ed = $this->eventDispatcher; $elapseTimeInSecondsBeforeJobsExecutionEnd = 6; @@ -397,7 +397,7 @@ public function testGetSlowRecentJobs() $this->assertEquals(4, $jobsFounds); } - public function testCleanOldArchived() + public function testCleanOldArchived(): void { $ed = $this->eventDispatcher; $this->repository->archive($this->aJob()->beforeExecution($ed)->afterExecution(42, $ed)); @@ -407,7 +407,7 @@ public function testCleanOldArchived() $this->assertEquals(0, $this->repository->countArchived()); } - public function testCleaningOfOldArchivedCanBeLimitedByTime() + public function testCleaningOfOldArchivedCanBeLimitedByTime(): void { $ed = $this->eventDispatcher; $this->repository->archive($this->aJob()->beforeExecution($ed)->afterExecution(42, $ed)); @@ -490,7 +490,7 @@ private function jobMockWithAttemptsAndCustomParameters( 'ended_at' => T\MongoDate::from($endedAt), ], 'retry_policy' => [ - 'class' => 'Recruiter\RetryPolicy\DoNotDoItAgain', + 'class' => \Recruiter\RetryPolicy\DoNotDoItAgain::class, 'parameters' => [], ], ]; diff --git a/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php b/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php index c96374ab..6c8970f8 100644 --- a/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php +++ b/spec/Recruiter/JobCallCustomMethodOnWorkableTest.php @@ -30,20 +30,20 @@ protected function setUp(): void $this->job = Job::around($this->workable, $this->repository); } - public function testConfigureMethodToCallOnWorkable() + public function testConfigureMethodToCallOnWorkable(): void { $this->workable->expects($this->once())->method('send'); $this->job->methodToCallOnWorkable('send'); $this->job->execute($this->createMock(EventDispatcherInterface::class)); } - public function testRaiseExceptionWhenConfigureMethodToCallOnWorkableThatDoNotExists() + public function testRaiseExceptionWhenConfigureMethodToCallOnWorkableThatDoNotExists(): void { $this->expectException(\Exception::class); $this->job->methodToCallOnWorkable('methodThatDoNotExists'); } - public function testCustomMethodIsSaved() + public function testCustomMethodIsSaved(): void { $this->job->methodToCallOnWorkable('send'); $jobExportedToDocument = $this->job->export(); @@ -52,7 +52,7 @@ public function testCustomMethodIsSaved() $this->assertEquals('send', $jobExportedToDocument['workable']['method']); } - public function testCustomMethodIsConservedAfterImport() + public function testCustomMethodIsConservedAfterImport(): void { $workable = new DummyWorkableWithSendCustomMethod(); $job = Job::around($workable, $this->repository); diff --git a/spec/Recruiter/JobSendEventsToWorkableTest.php b/spec/Recruiter/JobSendEventsToWorkableTest.php index 8bceaed7..e2bc698b 100644 --- a/spec/Recruiter/JobSendEventsToWorkableTest.php +++ b/spec/Recruiter/JobSendEventsToWorkableTest.php @@ -25,7 +25,7 @@ protected function setUp(): void $this->dispatcher = $this->createMock(EventDispatcherInterface::class); } - public function testTakeRetryPolicyFromRetriableInstance() + public function testTakeRetryPolicyFromRetriableInstance(): void { $listener = new EventListenerSpy(); $workable = new WorkableThatIsAlsoAnEventListener($listener); @@ -50,12 +50,12 @@ public function __construct(private readonly EventListener $listener) $this->parameters = []; } - public function onEvent($channel, Event $ev) + public function onEvent($channel, Event $ev): void { - return $this->listener->onEvent($channel, $ev); + $this->listener->onEvent($channel, $ev); } - public function execute() + public function execute(): never { throw new \Exception(); } diff --git a/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php b/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php index 5385ce19..cd056f79 100644 --- a/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php +++ b/spec/Recruiter/JobTakeRetryPolicyFromRetriableWorkableTest.php @@ -28,7 +28,7 @@ protected function setUp(): void /** * @throws Exception */ - public function testTakeRetryPolicyFromRetriableInstance() + public function testTakeRetryPolicyFromRetriableInstance(): void { $retryPolicy = $this->createMock(BaseRetryPolicy::class); $retryPolicy->expects($this->once())->method('schedule'); @@ -54,7 +54,7 @@ public function retryWithPolicy(): RetryPolicy return $this->retryWithPolicy; } - public function execute() + public function execute(): never { throw new \Exception(); } diff --git a/spec/Recruiter/JobTest.php b/spec/Recruiter/JobTest.php index 261f7575..09f3cc8d 100644 --- a/spec/Recruiter/JobTest.php +++ b/spec/Recruiter/JobTest.php @@ -22,7 +22,7 @@ protected function setUp(): void ; } - public function testRetryStatisticsOnFirstExecution() + public function testRetryStatisticsOnFirstExecution(): void { $job = Job::around(new AlwaysFail(), $this->repository); $retryStatistics = $job->retryStatistics(); @@ -38,7 +38,7 @@ public function testRetryStatisticsOnFirstExecution() /** * @depends testRetryStatisticsOnFirstExecution */ - public function testRetryStatisticsOnSubsequentExecutions() + public function testRetryStatisticsOnSubsequentExecutions(): void { $job = Job::around(new AlwaysFail(), $this->repository); // maybe make the argument optional @@ -58,7 +58,7 @@ public function testRetryStatisticsOnSubsequentExecutions() $this->assertMatchesRegularExpression('/.*AlwaysFail->execute.*/', $lastExecution['trace']); } - public function testArrayAsGroupIsNotAllowed() + public function testArrayAsGroupIsNotAllowed(): void { $this->expectException(\RuntimeException::class); $memoryLimit = new MemoryLimit(1); diff --git a/spec/Recruiter/JobToBePassedRetryStatisticsTest.php b/spec/Recruiter/JobToBePassedRetryStatisticsTest.php index 4f98057b..ec72fd4f 100644 --- a/spec/Recruiter/JobToBePassedRetryStatisticsTest.php +++ b/spec/Recruiter/JobToBePassedRetryStatisticsTest.php @@ -25,7 +25,7 @@ protected function setUp(): void /** * @throws Exception */ - public function testTakeRetryPolicyFromRetriableInstance() + public function testTakeRetryPolicyFromRetriableInstance(): void { $workable = new WorkableThatUsesRetryStatistics(); diff --git a/spec/Recruiter/JobToScheduleTest.php b/spec/Recruiter/JobToScheduleTest.php index 27b9496a..ce35099b 100644 --- a/spec/Recruiter/JobToScheduleTest.php +++ b/spec/Recruiter/JobToScheduleTest.php @@ -4,7 +4,6 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; -use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Timeless as T; class JobToScheduleTest extends TestCase @@ -27,7 +26,7 @@ protected function tearDown(): void $this->clock->start(); } - public function testInBackgroundShouldScheduleJobNow() + public function testInBackgroundShouldScheduleJobNow(): void { $this->job ->expects($this->once()) @@ -37,13 +36,13 @@ public function testInBackgroundShouldScheduleJobNow() ) ; - (new JobToSchedule($this->job)) + new JobToSchedule($this->job) ->inBackground() ->execute() ; } - public function testScheduledInShouldScheduleInCertainAmountOfTime() + public function testScheduledInShouldScheduleInCertainAmountOfTime(): void { $amountOfTime = T\minutes(10); $this->job @@ -54,13 +53,13 @@ public function testScheduledInShouldScheduleInCertainAmountOfTime() ) ; - (new JobToSchedule($this->job)) + new JobToSchedule($this->job) ->scheduleIn($amountOfTime) ->execute() ; } - public function testConfigureRetryPolicy() + public function testConfigureRetryPolicy(): void { $doNotDoItAgain = new RetryPolicy\DoNotDoItAgain(); @@ -70,29 +69,29 @@ public function testConfigureRetryPolicy() ->with($doNotDoItAgain) ; - (new JobToSchedule($this->job)) + new JobToSchedule($this->job) ->inBackground() ->retryWithPolicy($doNotDoItAgain) ->execute() ; } - public function tesShortcutToConfigureJobToNotBeRetried() + public function tesShortcutToConfigureJobToNotBeRetried(): void { $this->job ->expects($this->once()) ->method('retryWithPolicy') - ->with($this->isInstanceOf('Recruiter\RetryPolicy\DoNotDoItAgain')) + ->with($this->isInstanceOf(RetryPolicy\DoNotDoItAgain::class)) ; - (new JobToSchedule($this->job)) + new JobToSchedule($this->job) ->inBackground() ->doNotRetry() ->execute() ; } - public function testShouldNotExecuteJobWhenScheduled() + public function testShouldNotExecuteJobWhenScheduled(): void { $this->job ->expects($this->once()) @@ -104,13 +103,13 @@ public function testShouldNotExecuteJobWhenScheduled() ->method('execute') ; - (new JobToSchedule($this->job)) + new JobToSchedule($this->job) ->inBackground() ->execute() ; } - public function testShouldExecuteJobWhenNotScheduled() + public function testShouldExecuteJobWhenNotScheduled(): void { $this->job ->expects($this->never()) @@ -122,14 +121,10 @@ public function testShouldExecuteJobWhenNotScheduled() ->method('execute') ; - (new JobToSchedule($this->job)) - ->execute( - $this->createMock(EventDispatcherInterface::class), - ) - ; + new JobToSchedule($this->job)->execute(); } - public function testConfigureMethodToCallOnWorkableInJob() + public function testConfigureMethodToCallOnWorkableInJob(): void { $this->job ->expects($this->once()) @@ -137,12 +132,12 @@ public function testConfigureMethodToCallOnWorkableInJob() ->with('send') ; - (new JobToSchedule($this->job)) + new JobToSchedule($this->job) ->send() ; } - public function testReturnsJobId() + public function testReturnsJobId(): void { $this->job ->expects($this->any()) @@ -152,10 +147,7 @@ public function testReturnsJobId() $this->assertEquals( '42', - (new JobToSchedule($this->job)) - ->execute( - $this->createMock(EventDispatcherInterface::class), - ), + new JobToSchedule($this->job)->execute(), ); } } diff --git a/spec/Recruiter/PickAvailableWorkersTest.php b/spec/Recruiter/PickAvailableWorkersTest.php index 13c96ea9..2170aa23 100644 --- a/spec/Recruiter/PickAvailableWorkersTest.php +++ b/spec/Recruiter/PickAvailableWorkersTest.php @@ -25,7 +25,7 @@ protected function setUp(): void $this->workersPerUnit = 42; } - public function testNoWorkersAreFound() + public function testNoWorkersAreFound(): void { $this->withNoAvailableWorkers(); @@ -46,7 +46,7 @@ public function testFewWorkersWithNoSpecificSkill(): void $this->assertCount(3, $workers); } - public function testFewWorkersWithSameSkill() + public function testFewWorkersWithSameSkill(): void { $callbackHasBeenCalled = false; $this->withAvailableWorkers(['send-emails' => 3]); @@ -58,7 +58,7 @@ public function testFewWorkersWithSameSkill() $this->assertEquals(3, count($workers)); } - public function testFewWorkersWithSomeDifferentSkills() + public function testFewWorkersWithSomeDifferentSkills(): void { $this->withAvailableWorkers(['send-emails' => 3, 'count-transactions' => 3]); $picked = Worker::pickAvailableWorkers($this->repository, $this->workersPerUnit); @@ -74,7 +74,7 @@ public function testFewWorkersWithSomeDifferentSkills() $this->assertEquals(6, $totalWorkersGiven); } - public function testMoreWorkersThanAllowedPerUnit() + public function testMoreWorkersThanAllowedPerUnit(): void { $this->withAvailableWorkers(['send-emails' => $this->workersPerUnit + 10]); diff --git a/spec/Recruiter/RetryPolicy/ExponentialBackoffTest.php b/spec/Recruiter/RetryPolicy/ExponentialBackoffTest.php index 5c6e9e52..d9f3f966 100644 --- a/spec/Recruiter/RetryPolicy/ExponentialBackoffTest.php +++ b/spec/Recruiter/RetryPolicy/ExponentialBackoffTest.php @@ -8,7 +8,7 @@ class ExponentialBackoffTest extends TestCase { - public function testOnTheFirstFailureUsesTheSpecifiedInterval() + public function testOnTheFirstFailureUsesTheSpecifiedInterval(): void { $job = $this->jobExecutedFor(1); $retryPolicy = new ExponentialBackoff(100, T\seconds(5)); @@ -20,7 +20,7 @@ public function testOnTheFirstFailureUsesTheSpecifiedInterval() $retryPolicy->schedule($job); } - public function testAfterEachFailureDoublesTheAmountOfTimeToWaitBetweenRetries() + public function testAfterEachFailureDoublesTheAmountOfTimeToWaitBetweenRetries(): void { $job = $this->jobExecutedFor(2); $retryPolicy = new ExponentialBackoff(100, T\seconds(5)); @@ -32,7 +32,7 @@ public function testAfterEachFailureDoublesTheAmountOfTimeToWaitBetweenRetries() $retryPolicy->schedule($job); } - public function testAfterTooManyFailuresGivesUp() + public function testAfterTooManyFailuresGivesUp(): void { $job = $this->jobExecutedFor(101); $retryPolicy = new ExponentialBackoff(100, T\seconds(5)); @@ -44,7 +44,7 @@ public function testAfterTooManyFailuresGivesUp() $retryPolicy->schedule($job); } - public function testCanBeCreatedByTargetingAMaximumInterval() + public function testCanBeCreatedByTargetingAMaximumInterval(): void { $this->assertEquals( ExponentialBackoff::forAnInterval(1025, T\seconds(1)), diff --git a/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php b/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php index 553a26a6..750063dc 100644 --- a/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php +++ b/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php @@ -19,10 +19,10 @@ protected function setUp(): void $this->filteredRetryPolicy = $this->createMock(RetryPolicy::class); } - public function testCallScheduleOnRetriableException() + public function testCallScheduleOnRetriableException(): void { $exception = $this->createMock(\Exception::class); - $classOfException = get_class($exception); + $classOfException = $exception::class; $filter = new RetriableExceptionFilter($this->filteredRetryPolicy, [$classOfException]); $this->filteredRetryPolicy @@ -33,10 +33,10 @@ public function testCallScheduleOnRetriableException() $filter->schedule($this->jobFailedWithException($exception)); } - public function testDoNotCallScheduleOnNonRetriableException() + public function testDoNotCallScheduleOnNonRetriableException(): void { $exception = $this->createMock(\Exception::class); - $classOfException = get_class($exception); + $classOfException = $exception::class; $filter = new RetriableExceptionFilter($this->filteredRetryPolicy, [$classOfException]); $this->filteredRetryPolicy @@ -47,10 +47,10 @@ public function testDoNotCallScheduleOnNonRetriableException() $filter->schedule($this->jobFailedWithException(new \Exception('Test'))); } - public function testWhenExceptionIsNotRetriableThenArchiveTheJob() + public function testWhenExceptionIsNotRetriableThenArchiveTheJob(): void { $exception = $this->createMock(\Exception::class); - $classOfException = get_class($exception); + $classOfException = $exception::class; $filter = new RetriableExceptionFilter($this->filteredRetryPolicy, [$classOfException]); $job = $this->jobFailedWithException(new \Exception('Test')); @@ -62,7 +62,7 @@ public function testWhenExceptionIsNotRetriableThenArchiveTheJob() $filter->schedule($job); } - public function testAllExceptionsAreRetriableByDefault() + public function testAllExceptionsAreRetriableByDefault(): void { $this->filteredRetryPolicy ->expects($this->once()) @@ -73,7 +73,7 @@ public function testAllExceptionsAreRetriableByDefault() $filter->schedule($this->jobFailedWithException(new \Exception('Test'))); } - public function testJobFailedWithSomethingThatIsNotAnException() + public function testJobFailedWithSomethingThatIsNotAnException(): void { $jobAfterFailure = $this->jobFailedWithException(null); $jobAfterFailure @@ -85,7 +85,7 @@ public function testJobFailedWithSomethingThatIsNotAnException() $filter->schedule($jobAfterFailure); } - public function testExportFilteredRetryPolicy() + public function testExportFilteredRetryPolicy(): void { $this->filteredRetryPolicy ->expects($this->once()) @@ -99,7 +99,7 @@ public function testExportFilteredRetryPolicy() [ 'retriable_exceptions' => ['Exception'], 'filtered_retry_policy' => [ - 'class' => get_class($this->filteredRetryPolicy), + 'class' => $this->filteredRetryPolicy::class, 'parameters' => ['key' => 'value'], ], ], @@ -107,7 +107,7 @@ public function testExportFilteredRetryPolicy() ); } - public function testImportRetryPolicy() + public function testImportRetryPolicy(): void { $filteredRetryPolicy = new DoNotDoItAgain(); $filter = new RetriableExceptionFilter($filteredRetryPolicy); @@ -119,7 +119,7 @@ public function testImportRetryPolicy() $this->assertEquals($exported, $filter->export()); } - public function testRetriableExceptionsThatAreNotExceptions() + public function testRetriableExceptionsThatAreNotExceptions(): void { $this->expectException(\InvalidArgumentException::class); $this->expectExceptionMessage("Only subclasses of Exception can be retriable exceptions, 'StdClass' is not"); diff --git a/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php b/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php index 45c5904e..515a6e3f 100644 --- a/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php +++ b/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php @@ -9,7 +9,7 @@ class SelectByExceptionTest extends TestCase { - public function testCanBeBuilt() + public function testCanBeBuilt(): void { $retryPolicy = SelectByException::create() ->when(\InvalidArgumentException::class)->then(new DoNotDoItAgain()) @@ -20,7 +20,7 @@ public function testCanBeBuilt() $this->assertInstanceOf(RetryPolicy::class, $retryPolicy); } - public function testCanBeExportedAndImported() + public function testCanBeExportedAndImported(): void { $retryPolicy = SelectByException::create() ->when(\InvalidArgumentException::class)->then(new DoNotDoItAgain()) @@ -35,11 +35,11 @@ public function testCanBeExportedAndImported() $this->assertEquals($retryPolicyExported, $retryPolicyImported->export()); } - public function testSelectByException() + public function testSelectByException(): void { $exception = new \InvalidArgumentException('something'); $retryPolicy = new SelectByException([ - new RetriableException(get_class($exception), RetryForever::afterSeconds(10)), + new RetriableException($exception::class, RetryForever::afterSeconds(10)), ]); $job = $this->jobFailedWith($exception); @@ -51,7 +51,7 @@ public function testSelectByException() $retryPolicy->schedule($job); } - public function testDefaultDoNotSchedule() + public function testDefaultDoNotSchedule(): void { $exception = new \Exception('something'); $retryPolicy = new SelectByException([ diff --git a/spec/Recruiter/RetryPolicy/TimeTableTest.php b/spec/Recruiter/RetryPolicy/TimeTableTest.php index a4bd304a..4110e6a9 100644 --- a/spec/Recruiter/RetryPolicy/TimeTableTest.php +++ b/spec/Recruiter/RetryPolicy/TimeTableTest.php @@ -20,7 +20,7 @@ protected function setUp(): void ]); } - public function testShouldRescheduleInOneMinuteWhenWasCreatedLessThanFiveMinutesAgo() + public function testShouldRescheduleInOneMinuteWhenWasCreatedLessThanFiveMinutesAgo(): void { $expectedToBeScheduledAt = T\minute(1)->fromNow()->toSecondPrecision(); $wasCreatedAt = T\seconds(10)->ago(); @@ -32,7 +32,7 @@ public function testShouldRescheduleInOneMinuteWhenWasCreatedLessThanFiveMinutes $this->scheduler->schedule($job); } - public function testShouldRescheduleInFiveMinutesWhenWasCreatedLessThanOneHourAgo() + public function testShouldRescheduleInFiveMinutesWhenWasCreatedLessThanOneHourAgo(): void { $expectedToBeScheduledAt = T\minutes(5)->fromNow()->toSecondPrecision(); $wasCreatedAt = T\minutes(30)->ago(); @@ -44,7 +44,7 @@ public function testShouldRescheduleInFiveMinutesWhenWasCreatedLessThanOneHourAg $this->scheduler->schedule($job); } - public function testShouldRescheduleInFiveMinutesWhenWasCreatedLessThan24HoursAgo() + public function testShouldRescheduleInFiveMinutesWhenWasCreatedLessThan24HoursAgo(): void { $expectedToBeScheduledAt = T\hour(1)->fromNow()->toSecondPrecision(); $wasCreatedAt = T\hours(3)->ago(); @@ -56,14 +56,14 @@ public function testShouldRescheduleInFiveMinutesWhenWasCreatedLessThan24HoursAg $this->scheduler->schedule($job); } - public function testShouldNotBeRescheduledWhenWasCreatedMoreThan24HoursAgo() + public function testShouldNotBeRescheduledWhenWasCreatedMoreThan24HoursAgo(): void { $job = $this->jobThatWasCreated('2 days ago'); $job->expects($this->never())->method('scheduleAt'); $this->scheduler->schedule($job); } - public function testIsLastRetryReturnTrueIfJobWasCreatedMoreThanLastTimeSpen() + public function testIsLastRetryReturnTrueIfJobWasCreatedMoreThanLastTimeSpen(): void { $job = $this->createMock(Job::class); $job->expects($this->any()) @@ -78,7 +78,7 @@ public function testIsLastRetryReturnTrueIfJobWasCreatedMoreThanLastTimeSpen() $this->assertTrue($tt->isLastRetry($job)); } - public function testIsLastRetryReturnFalseIfJobWasCreatedLessThanLastTimeSpen() + public function testIsLastRetryReturnFalseIfJobWasCreatedLessThanLastTimeSpen(): void { $job = $this->createMock(Job::class); $job->expects($this->any()) @@ -93,19 +93,19 @@ public function testIsLastRetryReturnFalseIfJobWasCreatedLessThanLastTimeSpen() $this->assertFalse($tt->isLastRetry($job)); } - public function testInvalidTimeTableBecauseTimeWindow() + public function testInvalidTimeTableBecauseTimeWindow(): void { $this->expectException(\Exception::class); $tt = new TimeTable(['1 minute' => '1 second']); } - public function testInvalidTimeTableBecauseRescheduleTime() + public function testInvalidTimeTableBecauseRescheduleTime(): void { $this->expectException(\Exception::class); $tt = new TimeTable(['1 minute ago' => '1 second ago']); } - public function testInvalidTimeTableBecauseRescheduleTimeIsGreaterThanTimeWindow() + public function testInvalidTimeTableBecauseRescheduleTimeIsGreaterThanTimeWindow(): void { $this->expectException(\Exception::class); $tt = new TimeTable(['1 minute ago' => '2 minutes']); @@ -120,13 +120,13 @@ private function givenJobThat(T\Moment $wasCreatedAt) ; $job->expects($this->any()) ->method('createdAt') - ->will($this->returnValue($wasCreatedAt)) + ->willReturn($wasCreatedAt) ; return $job; } - private function jobThatWasCreated($relativeTime) + private function jobThatWasCreated(string $relativeTime): JobAfterFailure { $wasCreatedAt = T\Moment::fromTimestamp(strtotime($relativeTime)); $job = $this->getMockBuilder(JobAfterFailure::class) @@ -136,7 +136,7 @@ private function jobThatWasCreated($relativeTime) ; $job->expects($this->any()) ->method('createdAt') - ->will($this->returnValue($wasCreatedAt)) + ->willReturn($wasCreatedAt) ; return $job; diff --git a/spec/Recruiter/SchedulePolicy/CronTest.php b/spec/Recruiter/SchedulePolicy/CronTest.php index bac7a8cf..3d6804c6 100644 --- a/spec/Recruiter/SchedulePolicy/CronTest.php +++ b/spec/Recruiter/SchedulePolicy/CronTest.php @@ -10,7 +10,7 @@ class CronTest extends TestCase /** * @dataProvider cronExpressions */ - public function testCronCanBeExportedAndImportedWithoutDataLoss(string $cronExpression, string $expectedDate) + public function testCronCanBeExportedAndImportedWithoutDataLoss(string $cronExpression, string $expectedDate): void { $cron = new Cron($cronExpression, \DateTime::createFromFormat('Y-m-d H:i:s', '2019-01-15 15:00:00')); $cron = Cron::import($cron->export()); diff --git a/spec/Recruiter/TaggableWorkableTest.php b/spec/Recruiter/TaggableWorkableTest.php index c6ead3f8..65e96aa4 100644 --- a/spec/Recruiter/TaggableWorkableTest.php +++ b/spec/Recruiter/TaggableWorkableTest.php @@ -19,7 +19,7 @@ protected function setUp(): void ; } - public function testWorkableExportsTags() + public function testWorkableExportsTags(): void { $workable = new WorkableTaggable(['a', 'b']); $job = Job::around($workable, $this->repository); @@ -29,7 +29,7 @@ public function testWorkableExportsTags() $this->assertEquals(['a', 'b'], $exported['tags']); } - public function testCanSetTagsOnJobs() + public function testCanSetTagsOnJobs(): void { $workable = new WorkableTaggable([]); $job = Job::around($workable, $this->repository); @@ -40,7 +40,7 @@ public function testCanSetTagsOnJobs() $this->assertEquals(['c'], $exported['tags']); } - public function testTagsAreMergedTogether() + public function testTagsAreMergedTogether(): void { $workable = new WorkableTaggable(['a', 'b']); $job = Job::around($workable, $this->repository); @@ -51,7 +51,7 @@ public function testTagsAreMergedTogether() $this->assertEquals(['a', 'b', 'c'], $exported['tags']); } - public function testTagsAreUnique() + public function testTagsAreUnique(): void { $workable = new WorkableTaggable(['c']); $job = Job::around($workable, $this->repository); @@ -62,7 +62,7 @@ public function testTagsAreUnique() $this->assertEquals(['c'], $exported['tags']); } - public function testEmptyTagsAreNotExported() + public function testEmptyTagsAreNotExported(): void { $workable = new WorkableTaggable([]); $job = Job::around($workable, $this->repository); @@ -71,7 +71,7 @@ public function testEmptyTagsAreNotExported() $this->assertArrayNotHasKey('tags', $exported); } - public function testTagsAreImported() + public function testTagsAreImported(): void { $workable = new WorkableTaggable(['a', 'b']); $job = Job::around($workable, $this->repository); @@ -96,14 +96,11 @@ class WorkableTaggable implements Workable, Taggable { use WorkableBehaviour; - private $tags; - - public function __construct(array $tags) + public function __construct(private array $tags) { - $this->tags = $tags; } - public function taggedAs() + public function taggedAs(): array { return $this->tags; } diff --git a/spec/Recruiter/WaitStrategyTest.php b/spec/Recruiter/WaitStrategyTest.php index 4ff7a53e..62b3578f 100644 --- a/spec/Recruiter/WaitStrategyTest.php +++ b/spec/Recruiter/WaitStrategyTest.php @@ -22,7 +22,7 @@ protected function setUp(): void $this->timeToWaitAtMost = T\seconds(30); } - public function testStartsToWaitTheMinimumAmountOfTime() + public function testStartsToWaitTheMinimumAmountOfTime(): void { $ws = new WaitStrategy( $this->timeToWaitAtLeast, @@ -33,7 +33,7 @@ public function testStartsToWaitTheMinimumAmountOfTime() $this->assertEquals($this->timeToWaitAtLeast, $this->waited); } - public function testBackingOffIncreasesTheIntervalExponentially() + public function testBackingOffIncreasesTheIntervalExponentially(): void { $ws = new WaitStrategy( $this->timeToWaitAtLeast, @@ -48,7 +48,7 @@ public function testBackingOffIncreasesTheIntervalExponentially() $this->assertEquals($this->timeToWaitAtLeast->multiplyBy(4), $this->waited); } - public function testBackingOffCannotIncreaseTheIntervalOverAMaximum() + public function testBackingOffCannotIncreaseTheIntervalOverAMaximum(): void { $ws = new WaitStrategy(T\seconds(1), T\seconds(2), $this->howToWait); $ws->backOff(); @@ -59,7 +59,7 @@ public function testBackingOffCannotIncreaseTheIntervalOverAMaximum() $this->assertEquals(T\seconds(2), $this->waited); } - public function testGoingForwardLowersTheSleepingPeriod() + public function testGoingForwardLowersTheSleepingPeriod(): void { $ws = new WaitStrategy( $this->timeToWaitAtLeast, @@ -72,7 +72,7 @@ public function testGoingForwardLowersTheSleepingPeriod() $this->assertEquals($this->timeToWaitAtLeast, $this->waited); } - public function testTheSleepingPeriodCanBeResetToTheMinimum() + public function testTheSleepingPeriodCanBeResetToTheMinimum(): void { $ws = new WaitStrategy( $this->timeToWaitAtLeast, @@ -88,7 +88,7 @@ public function testTheSleepingPeriodCanBeResetToTheMinimum() $this->assertEquals($this->timeToWaitAtLeast, $this->waited); } - public function testGoingForwardCannotLowerTheIntervalBelowMinimum() + public function testGoingForwardCannotLowerTheIntervalBelowMinimum(): void { $ws = new WaitStrategy( $this->timeToWaitAtLeast, diff --git a/spec/Recruiter/Workable/FactoryMethodCommandTest.php b/spec/Recruiter/Workable/FactoryMethodCommandTest.php index b7263e2d..05950dd4 100644 --- a/spec/Recruiter/Workable/FactoryMethodCommandTest.php +++ b/spec/Recruiter/Workable/FactoryMethodCommandTest.php @@ -6,7 +6,7 @@ class FactoryMethodCommandTest extends TestCase { - public function testExecutedACommandReachableFromAStaticFactoryMethod() + public function testExecutedACommandReachableFromAStaticFactoryMethod(): void { $workable = FactoryMethodCommand::from('Recruiter\Workable\DummyFactory::create') ->myObject() @@ -15,7 +15,7 @@ public function testExecutedACommandReachableFromAStaticFactoryMethod() $this->assertEquals('42', $workable->execute()); } - public function testCanBeImportedAndExported() + public function testCanBeImportedAndExported(): void { $workable = FactoryMethodCommand::from('Recruiter\Workable\DummyFactory::create') ->myObject() @@ -27,7 +27,7 @@ public function testCanBeImportedAndExported() ); } - public function testPassesRetryStatisticsAsAnAdditionalArgumentToTheLastMethodToCall() + public function testPassesRetryStatisticsAsAnAdditionalArgumentToTheLastMethodToCall(): void { $workable = FactoryMethodCommand::from('Recruiter\Workable\DummyFactory::create') ->myObject() diff --git a/spec/Recruiter/Workable/ShellCommandTest.php b/spec/Recruiter/Workable/ShellCommandTest.php index 2eba223f..a5cbc362 100644 --- a/spec/Recruiter/Workable/ShellCommandTest.php +++ b/spec/Recruiter/Workable/ShellCommandTest.php @@ -6,13 +6,13 @@ class ShellCommandTest extends TestCase { - public function testExecutesACommandOnTheShell() + public function testExecutesACommandOnTheShell(): void { $workable = ShellCommand::fromCommandLine('echo 42'); $this->assertEquals('42', $workable->execute()); } - public function testCanBeImportedAndExported() + public function testCanBeImportedAndExported(): void { $workable = ShellCommand::fromCommandLine('echo 42'); $this->assertEquals( diff --git a/spec/Recruiter/WorkablePersistenceTest.php b/spec/Recruiter/WorkablePersistenceTest.php index ac8e3e71..283b528f 100644 --- a/spec/Recruiter/WorkablePersistenceTest.php +++ b/spec/Recruiter/WorkablePersistenceTest.php @@ -6,7 +6,7 @@ class WorkablePersistenceTest extends TestCase { - public function testCanBeExportedAndImported() + public function testCanBeExportedAndImported(): void { $job = new SomethingWorkable(['key' => 'value']); $this->assertEquals( diff --git a/spec/Recruiter/WorkerProcessTest.php b/spec/Recruiter/WorkerProcessTest.php index bcd50437..07a036b6 100644 --- a/spec/Recruiter/WorkerProcessTest.php +++ b/spec/Recruiter/WorkerProcessTest.php @@ -23,19 +23,19 @@ protected function setUp(): void ; } - public function testIfNotAliveWhenIsNotAliveReturnsItself() + public function testIfNotAliveWhenIsNotAliveReturnsItself(): void { $process = $this->givenWorkerProcessDead(); $this->assertInstanceOf(Process::class, $process->ifDead()); } - public function testIfNotAliveWhenIsAliveReturnsBlackHole() + public function testIfNotAliveWhenIsAliveReturnsBlackHole(): void { $process = $this->givenWorkerProcessAlive(); $this->assertInstanceOf(BlackHole::class, $process->ifDead()); } - public function testRetireWorkerIfNotAlive() + public function testRetireWorkerIfNotAlive(): void { $this->repository ->expects($this->once()) @@ -47,7 +47,7 @@ public function testRetireWorkerIfNotAlive() $process->cleanUp($this->repository); } - public function testDoNotRetireWorkerIfAlive() + public function testDoNotRetireWorkerIfAlive(): void { $this->repository ->expects($this->never()) diff --git a/spec/Sink/BlackHoleTest.php b/spec/Sink/BlackHoleTest.php index 097ec792..2fa25654 100644 --- a/spec/Sink/BlackHoleTest.php +++ b/spec/Sink/BlackHoleTest.php @@ -6,61 +6,61 @@ class BlackHoleTest extends TestCase { - public function testMethodCall() + public function testMethodCall(): void { $instance = new BlackHole(); - $this->assertInstanceOf('Sink\BlackHole', $instance->whateverMethod()); + $this->assertInstanceOf(BlackHole::class, $instance->whateverMethod()); } - public function testGetter() + public function testGetter(): void { $instance = new BlackHole(); - $this->assertInstanceOf('Sink\BlackHole', $instance->whateverProperty); + $this->assertInstanceOf(BlackHole::class, $instance->whateverProperty); } - public function testSetterReturnsTheValue() + public function testSetterReturnsTheValue(): void { $instance = new BlackHole(); $this->assertEquals(42, $instance->whateverProperty = 42); } - public function testNothingIsSet() + public function testNothingIsSet(): void { $instance = new BlackHole(); $instance->whateverProperty = 42; $this->assertFalse(isset($instance->whateverProperty)); } - public function testToString() + public function testToString(): void { $instance = new BlackHole(); $this->assertEquals('', (string) $instance); } - public function testInvoke() + public function testInvoke(): void { $instance = new BlackHole(); - $this->assertInstanceOf('Sink\BlackHole', $instance()); + $this->assertInstanceOf(BlackHole::class, $instance()); } - public function testCallStatic() + public function testCallStatic(): void { $instance = BlackHole::whateverStaticMethod(); - $this->assertInstanceOf('Sink\BlackHole', $instance); + $this->assertInstanceOf(BlackHole::class, $instance); } - public function testIsIterableButItIsAlwaysEmpty() + public function testIsIterableButItIsAlwaysEmpty(): void { $instance = new BlackHole(); $this->assertEmpty(iterator_to_array($instance)); } - public function testIsAccessibleAsAnArrayAlwaysGetItself() + public function testIsAccessibleAsAnArrayAlwaysGetItself(): void { $instance = new BlackHole(); - $this->assertInstanceOf('Sink\BlackHole', $instance[42]); - $this->assertInstanceOf('Sink\BlackHole', $instance['aString']); - $this->assertInstanceOf('Sink\BlackHole', $instance[[1, 2, 3]]); + $this->assertInstanceOf(BlackHole::class, $instance[42]); + $this->assertInstanceOf(BlackHole::class, $instance['aString']); + $this->assertInstanceOf(BlackHole::class, $instance[[1, 2, 3]]); } /* public function testIsAccessibleAsAnArrayExists() */ diff --git a/spec/Timeless/IntervalFormatTest.php b/spec/Timeless/IntervalFormatTest.php index 5a697c11..208368bf 100644 --- a/spec/Timeless/IntervalFormatTest.php +++ b/spec/Timeless/IntervalFormatTest.php @@ -6,7 +6,7 @@ class IntervalFormatTest extends TestCase { - public function testFormatExtended() + public function testFormatExtended(): void { $this->assertEquals('4 milliseconds', milliseconds(4)->format('milliseconds')); $this->assertEquals('1 second', milliseconds(1000)->format('seconds')); @@ -19,7 +19,7 @@ public function testFormatExtended() $this->assertEquals('1 year', months(12)->format('years')); } - public function testFormatShort() + public function testFormatShort(): void { $this->assertEquals('4ms', milliseconds(4)->format('ms')); $this->assertEquals('1s', milliseconds(1000)->format('s')); diff --git a/spec/Timeless/IntervalParseTest.php b/spec/Timeless/IntervalParseTest.php index 8cc384f4..bbd276d1 100644 --- a/spec/Timeless/IntervalParseTest.php +++ b/spec/Timeless/IntervalParseTest.php @@ -6,7 +6,7 @@ class IntervalParseTest extends TestCase { - public function testParseExtendedFormat() + public function testParseExtendedFormat(): void { $this->assertEquals(milliseconds(4), Interval::parse('4 milliseconds')); $this->assertEquals(milliseconds(4), Interval::parse('4milliseconds')); @@ -65,7 +65,7 @@ public function testParseExtendedFormat() $this->assertEquals(years(1), Interval::parse('1 year')); } - public function testParseShortFormat() + public function testParseShortFormat(): void { $this->assertEquals(milliseconds(4), Interval::parse('4 ms')); $this->assertEquals(milliseconds(4), Interval::parse('4ms')); @@ -85,21 +85,21 @@ public function testParseShortFormat() $this->assertEquals(years(4), Interval::parse('4y')); } - public function testFromDateInterval() + public function testFromDateInterval(): void { $this->assertEquals(days(2), Interval::fromDateInterval(new \DateInterval('P2D'))); $this->assertEquals(minutes(10), Interval::fromDateInterval(new \DateInterval('PT10M'))); $this->assertEquals(days(2)->add(minutes(10)), Interval::fromDateInterval(new \DateInterval('P2DT10M'))); } - public function testNumberAsIntervalFormat() + public function testNumberAsIntervalFormat(): void { $this->expectException(InvalidIntervalFormat::class); $this->expectExceptionMessage("Maybe you mean '5 seconds' or something like that?"); Interval::parse(5); } - public function testBadString() + public function testBadString(): void { $this->expectException(InvalidIntervalFormat::class); Interval::parse('whatever'); diff --git a/spec/Timeless/MongoDateTest.php b/spec/Timeless/MongoDateTest.php index a7095a37..ad7bb26e 100644 --- a/spec/Timeless/MongoDateTest.php +++ b/spec/Timeless/MongoDateTest.php @@ -10,7 +10,7 @@ class MongoDateTest extends TestCase { use Eris\TestTrait; - public function testConvertsBackAndForthMongoDatesWithoutLosingMillisecondPrecision() + public function testConvertsBackAndForthMongoDatesWithoutLosingMillisecondPrecision(): void { $this ->forAll( diff --git a/src/Recruiter/Cleaner.php b/src/Recruiter/Cleaner.php index e6ef6097..4b7a03c9 100644 --- a/src/Recruiter/Cleaner.php +++ b/src/Recruiter/Cleaner.php @@ -8,14 +8,8 @@ class Cleaner { - /** - * @var Repository - */ - private $repository; - - public function __construct(Repository $repository) + public function __construct(private readonly Repository $repository) { - $this->repository = $repository; } public function cleanArchived(Interval $gracePeriod) diff --git a/src/Recruiter/Finalizable.php b/src/Recruiter/Finalizable.php index 6804f06c..c7433dcc 100644 --- a/src/Recruiter/Finalizable.php +++ b/src/Recruiter/Finalizable.php @@ -6,11 +6,11 @@ interface Finalizable { - public function afterSuccess(); + public function afterSuccess(): void; - public function afterFailure(\Exception $e); + public function afterFailure(\Exception $e): void; - public function afterLastFailure(\Exception $e); + public function afterLastFailure(\Exception $e): void; - public function finalize(?\Exception $e = null); + public function finalize(?\Exception $e = null): void; } diff --git a/src/Recruiter/FinalizableBehaviour.php b/src/Recruiter/FinalizableBehaviour.php index c50a10d7..ce5bcc18 100644 --- a/src/Recruiter/FinalizableBehaviour.php +++ b/src/Recruiter/FinalizableBehaviour.php @@ -6,19 +6,19 @@ trait FinalizableBehaviour { - public function afterSuccess() + public function afterSuccess(): void { } - public function afterFailure(\Exception $e) + public function afterFailure(\Exception $e): void { } - public function afterLastFailure(\Exception $e) + public function afterLastFailure(\Exception $e): void { } - public function finalize(?\Exception $e = null) + public function finalize(?\Exception $e = null): void { } } diff --git a/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php b/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php index e29aec32..94e5fb2c 100644 --- a/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php +++ b/src/Recruiter/Infrastructure/Command/Bko/AnalyticsCommand.php @@ -87,8 +87,8 @@ private function calculateColumnsWidth(array $analytics): int $maxColumns = max($maxColumns, count($analytic)); } - // casual constants, found by try and error - $terminalWidth = (new Terminal())->getWidth() - (($maxColumns + 2) * 2); + // casual constants, found by trial and error + $terminalWidth = new Terminal()->getWidth() - (($maxColumns + 2) * 2); return intval(floor($terminalWidth / $maxColumns)); } diff --git a/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php b/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php index a81bd66a..29369c47 100644 --- a/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php +++ b/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php @@ -19,26 +19,14 @@ class RemoveSchedulerCommand extends Command { - /** - * @var Factory - */ - private $factory; - - /** - * @var LoggerInterface - */ - private $logger; - /** * @var SchedulerRepository */ private $schedulerRepository; - public function __construct(Factory $factory, LoggerInterface $logger) + public function __construct(private readonly Factory $factory, private readonly LoggerInterface $logger) { parent::__construct(); - $this->factory = $factory; - $this->logger = $logger; } protected function configure() diff --git a/src/Recruiter/Infrastructure/Command/CleanerCommand.php b/src/Recruiter/Infrastructure/Command/CleanerCommand.php index cbf85798..ee0ff3d3 100644 --- a/src/Recruiter/Infrastructure/Command/CleanerCommand.php +++ b/src/Recruiter/Infrastructure/Command/CleanerCommand.php @@ -26,45 +26,14 @@ class CleanerCommand implements RobustCommand { - /** - * @var Factory - */ - private $factory; - - /** - * @var Cleaner - */ - private $cleaner; - - /** - * @var LeadershipStrategy - */ - private $leadershipStrategy; - - /** - * @var WaitStrategy - */ - private $waitStrategy; - - /** - * @var MemoryLimit - */ - private $memoryLimit; - - /** - * @var Interval - */ - private $gracePeriod; - - /** - * @var LoggerInterface - */ - private $logger; - - public function __construct($factory, LoggerInterface $logger) + private Cleaner $cleaner; + private LeadershipStrategy $leadershipStrategy; + private WaitStrategy $waitStrategy; + private MemoryLimit $memoryLimit; + private Interval $gracePeriod; + + public function __construct(private readonly Factory $factory, private readonly LoggerInterface $logger) { - $this->factory = $factory; - $this->logger = $logger; } public static function toRobustCommand(Factory $factory, LoggerInterface $logger): RobustCommandRunner diff --git a/src/Recruiter/Infrastructure/Command/RecruiterCommand.php b/src/Recruiter/Infrastructure/Command/RecruiterCommand.php index 67b907f5..287ab84c 100644 --- a/src/Recruiter/Infrastructure/Command/RecruiterCommand.php +++ b/src/Recruiter/Infrastructure/Command/RecruiterCommand.php @@ -27,45 +27,14 @@ class RecruiterCommand implements RobustCommand, LeadershipEventsHandler { - /** - * @var Factory - */ - private $factory; + private Recruiter $recruiter; + private Interval $consideredDeadAfter; + private LeadershipStrategy $leadershipStrategy; + private WaitStrategy $waitStrategy; + private MemoryLimit $memoryLimit; - /** - * @var Recruiter - */ - private $recruiter; - - /** - * @var Interval - */ - private $consideredDeadAfter; - - /** - * @var LeadershipStrategy - */ - private $leadershipStrategy; - - /** - * @var WaitStrategy - */ - private $waitStrategy; - - /** - * @var MemoryLimit - */ - private $memoryLimit; - - /** - * @var LoggerInterface - */ - private $logger; - - public function __construct($factory, LoggerInterface $logger) + public function __construct(private readonly Factory $factory, private readonly LoggerInterface $logger) { - $this->factory = $factory; - $this->logger = $logger; } public static function toRobustCommand(Factory $factory, LoggerInterface $logger): RobustCommandRunner @@ -83,7 +52,7 @@ public function execute(): bool return count($assignment) > 0; } - private function rollbackLockedJobs() + private function rollbackLockedJobs(): void { $rollbackStartAt = microtime(true); $rolledBack = $this->recruiter->rollbackLockedJobs(); @@ -133,7 +102,7 @@ private function scheduleRepeatableJobs(): void )); } - private function retireDeadWorkers() + private function retireDeadWorkers(): void { $unlockedJobs = $this->recruiter->retireDeadWorkers( new \DateTimeImmutable(), diff --git a/src/Recruiter/Infrastructure/Command/WorkerCommand.php b/src/Recruiter/Infrastructure/Command/WorkerCommand.php index d9c1331f..81927a4a 100644 --- a/src/Recruiter/Infrastructure/Command/WorkerCommand.php +++ b/src/Recruiter/Infrastructure/Command/WorkerCommand.php @@ -26,11 +26,6 @@ class WorkerCommand implements RobustCommand { - /** - * @var Factory - */ - private $factory; - /** * @var Worker */ @@ -47,14 +42,10 @@ class WorkerCommand implements RobustCommand private $waitStrategy; /** - * @var LoggerInterface + * @param Factory $factory */ - private $logger; - - public function __construct($factory, LoggerInterface $logger) + public function __construct(private $factory, private readonly LoggerInterface $logger) { - $this->factory = $factory; - $this->logger = $logger; } public static function toRobustCommand(Factory $factory, LoggerInterface $logger): RobustCommandRunner diff --git a/src/Recruiter/Infrastructure/Filesystem/BootstrapFile.php b/src/Recruiter/Infrastructure/Filesystem/BootstrapFile.php index 68cf7cab..860d6fb1 100644 --- a/src/Recruiter/Infrastructure/Filesystem/BootstrapFile.php +++ b/src/Recruiter/Infrastructure/Filesystem/BootstrapFile.php @@ -11,14 +11,9 @@ */ class BootstrapFile { - /** - * @var string - */ - private $filePath; - - public function __construct(string $filePath) + public function __construct(private readonly string $filePath) { - $this->filePath = $this->validate($filePath); + $this->validate($filePath); } public static function fromFilePath(string $filePath): self @@ -31,7 +26,7 @@ public function load(Recruiter $recruiter) return require $this->filePath; } - private function validate($filePath): string + private function validate(string $filePath): void { if (!file_exists($filePath)) { $this->throwBecauseFile($filePath, "doesn't exists"); @@ -40,11 +35,9 @@ private function validate($filePath): string if (!is_readable($filePath)) { $this->throwBecauseFile($filePath, 'is not readable'); } - - return $filePath; } - private function throwBecauseFile($filePath, $reason) + private function throwBecauseFile(string $filePath, string $reason): never { throw new \UnexpectedValueException(sprintf("Bootstrap file has an invalid value: file '%s' %s", $filePath, $reason)); } diff --git a/src/Recruiter/Job.php b/src/Recruiter/Job.php index 17122d13..b784650b 100644 --- a/src/Recruiter/Job.php +++ b/src/Recruiter/Job.php @@ -14,13 +14,7 @@ class Job { - private $status; - private $workable; - private $retryPolicy; - private $repository; - private $lastJobExecution; - - public static function around(Workable $workable, Repository $repository) + public static function around(Workable $workable, Repository $repository): self { return new self( self::initialize(), @@ -32,7 +26,7 @@ public static function around(Workable $workable, Repository $repository) ); } - public static function import($document, Repository $repository) + public static function import($document, Repository $repository): self { return new self( $document, @@ -43,13 +37,13 @@ public static function import($document, Repository $repository) ); } - public function __construct($status, Workable $workable, RetryPolicy $retryPolicy, JobExecution $lastJobExecution, Repository $repository) - { - $this->status = $status; - $this->workable = $workable; - $this->retryPolicy = $retryPolicy; - $this->lastJobExecution = $lastJobExecution; - $this->repository = $repository; + public function __construct( + private array $status, + private readonly Workable $workable, + private RetryPolicy $retryPolicy, + private JobExecution $lastJobExecution, + private readonly Repository $repository, + ) { } public function id() @@ -57,7 +51,7 @@ public function id() return $this->status['_id']; } - public function createdAt() + public function createdAt(): Moment { return T\MongoDate::toMoment($this->status['created_at']); } @@ -74,7 +68,10 @@ public function retryWithPolicy(RetryPolicy $retryPolicy) return $this; } - public function taggedAs(array $tags) + /** + * @return $this + */ + public function taggedAs(array $tags): static { if (!empty($tags)) { $this->status['tags'] = $tags; @@ -83,12 +80,15 @@ public function taggedAs(array $tags) return $this; } - public function inGroup($group) + public function inGroup(array|string $group): static { if (is_array($group)) { - throw new \RuntimeException('Group can be only single string, for other uses use `taggedAs` method. - Received group: `' . var_export($group, true) . '`'); + throw new \RuntimeException( + "Group can be only single string, for other uses use `taggedAs` method. + Received group: `" . var_export($group, true) . "`" + ); } + if (!empty($group)) { $this->status['group'] = $group; } @@ -318,8 +318,7 @@ public static function pickReadyJobsForWorkers(MongoCollection $collection, $wor ['scheduled_at' => ['$lt' => T\MongoDate::now()], 'locked' => false, 'group' => $worksOn, - ] - , + ], [ 'projection' => ['_id' => 1], 'sort' => ['scheduled_at' => 1], diff --git a/src/Recruiter/Job/EventListener.php b/src/Recruiter/Job/EventListener.php index cc2dc6d4..f82db1d1 100644 --- a/src/Recruiter/Job/EventListener.php +++ b/src/Recruiter/Job/EventListener.php @@ -4,5 +4,5 @@ interface EventListener { - public function onEvent($channel, Event $ev); + public function onEvent($channel, Event $ev): void; } diff --git a/src/Recruiter/JobExecution.php b/src/Recruiter/JobExecution.php index 4c321b73..b84373c6 100644 --- a/src/Recruiter/JobExecution.php +++ b/src/Recruiter/JobExecution.php @@ -6,31 +6,31 @@ class JobExecution { - private $isCrashed; - private $scheduledAt; - private $startedAt; - private $endedAt; + private bool $isCrashed = false; + private ?T\Moment $scheduledAt = null; + private ?T\Moment $startedAt = null; + private ?T\Moment $endedAt = null; private $completedWith; - private $failedWith; + private ?\Throwable $failedWith = null; - public function isCrashed() + public function isCrashed(): bool { return $this->isCrashed; } - public function started($scheduledAt = null) + public function started(?T\Moment $scheduledAt = null): void { $this->scheduledAt = $scheduledAt; $this->startedAt = T\now(); } - public function failedWith(\Throwable $exception) + public function failedWith(\Throwable $exception): void { $this->endedAt = T\now(); $this->failedWith = $exception; } - public function completedWith($result) + public function completedWith($result): void { $this->endedAt = T\now(); $this->completedWith = $result; @@ -41,17 +41,17 @@ public function result() return $this->completedWith; } - public function causeOfFailure() + public function causeOfFailure(): ?\Throwable { return $this->failedWith; } - public function isFailed() + public function isFailed(): bool { return !is_null($this->failedWith) || $this->isCrashed(); } - public function duration() + public function duration(): T\Interval { if ($this->startedAt && $this->endedAt && ($this->startedAt <= $this->endedAt)) { return T\seconds( @@ -63,7 +63,7 @@ public function duration() return T\seconds(0); } - public static function import($document) + public static function import(array $document): self { $lastExecution = new self(); if (array_key_exists('last_execution', $document)) { @@ -82,7 +82,7 @@ public static function import($document) return $lastExecution; } - public function export() + public function export(): array { $exported = []; if ($this->scheduledAt) { @@ -95,7 +95,7 @@ public function export() $exported['ended_at'] = T\MongoDate::from($this->endedAt); } if ($this->failedWith) { - $exported['class'] = get_class($this->failedWith); + $exported['class'] = $this->failedWith::class; $exported['message'] = $this->failedWith->getMessage(); $exported['trace'] = $this->traceOf($this->failedWith); } @@ -109,7 +109,7 @@ public function export() } } - private function traceOf($result) + private function traceOf(mixed $result): string { $trace = 'ok'; if ($result instanceof \Throwable) { @@ -117,11 +117,11 @@ private function traceOf($result) } elseif (is_object($result) && method_exists($result, 'trace')) { $trace = $result->trace(); } elseif (is_object($result)) { - $trace = get_class($result); + $trace = $result::class; } elseif (is_string($result) || is_numeric($result)) { $trace = $result; } - return substr($trace, 0, 4096); + return substr((string) $trace, 0, 4096); } } diff --git a/src/Recruiter/JobToSchedule.php b/src/Recruiter/JobToSchedule.php index b6a21cff..cf260a7e 100644 --- a/src/Recruiter/JobToSchedule.php +++ b/src/Recruiter/JobToSchedule.php @@ -9,14 +9,11 @@ class JobToSchedule { - private $job; - /** @var bool */ private $mustBeScheduled; - public function __construct(Job $job) + public function __construct(private readonly Job $job) { - $this->job = $job; $this->mustBeScheduled = false; } @@ -67,7 +64,10 @@ public function scheduleAt(Moment $momentInTime) return $this; } - public function inGroup($group) + /** + * @return $this + */ + public function inGroup(array|string|null $group): static { if (!empty($group)) { $this->job->inGroup($group); @@ -76,7 +76,7 @@ public function inGroup($group) return $this; } - public function taggedAs($tags) + public function taggedAs(array|string $tags): static { if (!empty($tags)) { $this->job->taggedAs(is_array($tags) ? $tags : [$tags]); @@ -85,21 +85,21 @@ public function taggedAs($tags) return $this; } - public function withUrn(string $urn) + public function withUrn(string $urn): static { $this->job->withUrn($urn); return $this; } - public function scheduledBy(string $namespace, string $id, int $nth) + public function scheduledBy(string $namespace, string $id, int $nth): static { $this->job->scheduledBy($namespace, $id, $nth); return $this; } - public function execute() + public function execute(): string { if ($this->mustBeScheduled) { $this->job->save(); @@ -110,7 +110,7 @@ public function execute() return (string) $this->job->id(); } - private function emptyEventDispatcher() + private function emptyEventDispatcher(): EventDispatcher { return new EventDispatcher(); } @@ -122,12 +122,12 @@ public function __call($name, $arguments) return $this->execute(); } - public function export() + public function export(): array { return $this->job->export(); } - public static function import($document, $repository) + public static function import($document, $repository): self { return new self(Job::import($document, $repository)); } diff --git a/src/Recruiter/Recruiter.php b/src/Recruiter/Recruiter.php index d1e58dff..a0138695 100644 --- a/src/Recruiter/Recruiter.php +++ b/src/Recruiter/Recruiter.php @@ -11,65 +11,64 @@ class Recruiter { - private $db; - private $jobs; - private $workers; - private $scheduler; - private $eventDispatcher; + private readonly Job\Repository $jobs; + private readonly Worker\Repository $workers; + private readonly Scheduler\Repository $scheduler; + private readonly EventDispatcher $eventDispatcher; - public function __construct(MongoDB\Database $db) + public function __construct(private readonly MongoDB\Database $db) { - $this->db = $db; - $this->jobs = new Job\Repository($db); - $this->workers = new Worker\Repository($db, $this); - $this->scheduler = new Scheduler\Repository($db); + $this->jobs = new Job\Repository($this->db); + $this->workers = new Worker\Repository($this->db, $this); + $this->scheduler = new Scheduler\Repository($this->db); $this->eventDispatcher = new EventDispatcher(); } - public function hire(MemoryLimit $memoryLimit) + public function hire(MemoryLimit $memoryLimit): Worker { return Worker::workFor($this, $this->workers, $memoryLimit); } - public function jobOf(Workable $workable) + public function jobOf(Workable $workable): JobToSchedule { return new JobToSchedule( Job::around($workable, $this->jobs), ); } - public function repeatableJobOf(Repeatable $repeatable) + public function repeatableJobOf(Repeatable $repeatable): Scheduler { return Scheduler::around($repeatable, $this->scheduler, $this); } - public function queued() + public function queued(): int { return $this->jobs->queued(); } - public function scheduled() + public function scheduled(): int { return $this->jobs->scheduledCount(); } - public function queuedGroupedBy($field, array $query = [], $group = null) + public function queuedGroupedBy($field, array $query = [], $group = null): array { return $this->jobs->queuedGroupedBy($field, $query, $group); } - /** - * @deprecated use the method `analytics` instead - */ - public function statistics($group = null, ?Moment $at = null, array $query = []) + #[\Deprecated(message: 'use the method `analytics` instead')] + public function statistics($group = null, ?Moment $at = null, array $query = []): array { return $this->analytics($group, $at, $query); } - public function analytics($group = null, ?Moment $at = null, array $query = []) + /** + * @return array + */ + public function analytics($group = null, ?Moment $at = null, array $query = []): array { $totalsScheduledJobs = $this->jobs->scheduledCount($group, $query); - $queued = $this->jobs->queued($group, $at, $at ? $at->before(T\hour(24)) : null, $query); + $queued = $this->jobs->queued($group, $at, $at?->before(T\hour(24)), $query); $postponed = $this->jobs->postponed($group, $at, $query); return array_merge( @@ -84,7 +83,7 @@ public function analytics($group = null, ?Moment $at = null, array $query = []) ); } - public function getEventDispatcher() + public function getEventDispatcher(): EventDispatcher { return $this->eventDispatcher; } @@ -94,7 +93,7 @@ public function getEventDispatcher() * * @return int how many */ - public function rollbackLockedJobs() + public function rollbackLockedJobs(): int { $assignedJobs = Worker::assignedJobs($this->db->selectCollection('roster')); @@ -104,16 +103,16 @@ public function rollbackLockedJobs() /** * @step */ - public function bye() + public function bye(): void { } - public function assignJobsToWorkers() + public function assignJobsToWorkers(): array { return $this->assignLockedJobsToWorkers($this->bookJobsForWorkers()); } - public function scheduleRepeatableJobs() + public function scheduleRepeatableJobs(): void { $schedulers = $this->scheduler->all(); foreach ($schedulers as $scheduler) { @@ -124,7 +123,7 @@ public function scheduleRepeatableJobs() /** * @step */ - public function bookJobsForWorkers() + public function bookJobsForWorkers(): array { $roster = $this->db->selectCollection('roster'); $scheduled = $this->db->selectCollection('scheduled'); @@ -150,7 +149,7 @@ public function bookJobsForWorkers() /** * @step */ - public function assignLockedJobsToWorkers($bookedJobs) + public function assignLockedJobsToWorkers(array $bookedJobs): array { $assignments = []; $totalActualAssignments = 0; @@ -169,9 +168,7 @@ public function assignLockedJobsToWorkers($bookedJobs) } return [ - array_map(function ($value) { - return (string) $value; - }, $assignments), + array_map(fn ($value) => (string) $value, $assignments), $totalActualAssignments, ]; } @@ -186,7 +183,7 @@ public function scheduledJob($id) * * @return int how many jobs were unlocked as a result */ - public function retireDeadWorkers(\DateTimeImmutable $now, Interval $consideredDeadAfter) + public function retireDeadWorkers(\DateTimeImmutable $now, Interval $consideredDeadAfter): int { return $this->jobs->releaseAll( $jobsAssignedToDeadWorkers = Worker::retireDeadWorkers($this->workers, $now, $consideredDeadAfter), @@ -204,7 +201,7 @@ public function flushJobsSynchronously(): SynchronousExecutionReport return SynchronousExecutionReport::fromArray($report); } - public function createCollectionsAndIndexes() + public function createCollectionsAndIndexes(): void { $this->db->selectCollection('scheduled')->createIndex( [ @@ -268,7 +265,7 @@ public function createCollectionsAndIndexes() ); } - private function combineJobsWithWorkers($jobs, $workers) + private function combineJobsWithWorkers($jobs, $workers): array { $assignments = min(count($workers), count($jobs)); $workers = array_slice($workers, 0, $assignments); diff --git a/src/Recruiter/RepeatableInJob.php b/src/Recruiter/RepeatableInJob.php index 5546e4f8..e6a23110 100644 --- a/src/Recruiter/RepeatableInJob.php +++ b/src/Recruiter/RepeatableInJob.php @@ -56,7 +56,7 @@ public static function initialize(): array private static function classNameOf($repeatable): string { - $repeatableClassName = get_class($repeatable); + $repeatableClassName = $repeatable::class; if (method_exists($repeatable, 'getClass')) { $repeatableClassName = $repeatable->getClass(); } diff --git a/src/Recruiter/RetryPolicy.php b/src/Recruiter/RetryPolicy.php index 4acb51b5..33f29590 100644 --- a/src/Recruiter/RetryPolicy.php +++ b/src/Recruiter/RetryPolicy.php @@ -13,10 +13,8 @@ interface RetryPolicy * - schedule the job * - archive the job * - do nothing (and the job will be archived anyway) - * - * @return void */ - public function schedule(JobAfterFailure $job); + public function schedule(JobAfterFailure $job): void; /** * Export retry policy parameters. diff --git a/src/Recruiter/RetryPolicy/DoNotDoItAgain.php b/src/Recruiter/RetryPolicy/DoNotDoItAgain.php index 5ae8288a..fafe975b 100644 --- a/src/Recruiter/RetryPolicy/DoNotDoItAgain.php +++ b/src/Recruiter/RetryPolicy/DoNotDoItAgain.php @@ -11,7 +11,7 @@ class DoNotDoItAgain implements RetryPolicy { use RetryPolicyBehaviour; - public function schedule(JobAfterFailure $job) + public function schedule(JobAfterFailure $job): void { // doing nothing means to avoid to reschedule the job } diff --git a/src/Recruiter/RetryPolicy/ExponentialBackoff.php b/src/Recruiter/RetryPolicy/ExponentialBackoff.php index 9db868fc..b1bcedb7 100644 --- a/src/Recruiter/RetryPolicy/ExponentialBackoff.php +++ b/src/Recruiter/RetryPolicy/ExponentialBackoff.php @@ -12,15 +12,15 @@ class ExponentialBackoff implements RetryPolicy { use RetryPolicyBehaviour; - private $retryHowManyTimes; - private $timeToInitiallyWaitBeforeRetry; - public static function forTimes($retryHowManyTimes, $timeToInitiallyWaitBeforeRetry = 60) + private Interval $timeToInitiallyWaitBeforeRetry; + + public static function forTimes($retryHowManyTimes, $timeToInitiallyWaitBeforeRetry = 60): static { return new static($retryHowManyTimes, $timeToInitiallyWaitBeforeRetry); } - public function atFirstWaiting($timeToInitiallyWaitBeforeRetry) + public function atFirstWaiting($timeToInitiallyWaitBeforeRetry): static { return new static($this->retryHowManyTimes, $timeToInitiallyWaitBeforeRetry); } @@ -29,7 +29,7 @@ public function atFirstWaiting($timeToInitiallyWaitBeforeRetry) * @params integer $interval in seconds * @params integer $timeToWaitBeforeRetry in seconds */ - public static function forAnInterval($interval, $timeToInitiallyWaitBeforeRetry) + public static function forAnInterval($interval, $timeToInitiallyWaitBeforeRetry): static { if (!($timeToInitiallyWaitBeforeRetry instanceof Interval)) { $timeToInitiallyWaitBeforeRetry = T\seconds($timeToInitiallyWaitBeforeRetry); @@ -42,19 +42,18 @@ public static function forAnInterval($interval, $timeToInitiallyWaitBeforeRetry) return new static($numberOfRetries, $timeToInitiallyWaitBeforeRetry); } - public function __construct($retryHowManyTimes, $timeToInitiallyWaitBeforeRetry) + public function __construct(private $retryHowManyTimes, int|Interval $timeToInitiallyWaitBeforeRetry) { if (!($timeToInitiallyWaitBeforeRetry instanceof Interval)) { $timeToInitiallyWaitBeforeRetry = T\seconds($timeToInitiallyWaitBeforeRetry); } - $this->retryHowManyTimes = $retryHowManyTimes; $this->timeToInitiallyWaitBeforeRetry = $timeToInitiallyWaitBeforeRetry; } - public function schedule(JobAfterFailure $job) + public function schedule(JobAfterFailure $job): void { if ($job->numberOfAttempts() <= $this->retryHowManyTimes) { - $retryInterval = T\seconds(pow(2, $job->numberOfAttempts() - 1) * $this->timeToInitiallyWaitBeforeRetry->seconds()); + $retryInterval = T\seconds(2 ** ($job->numberOfAttempts() - 1) * $this->timeToInitiallyWaitBeforeRetry->seconds()); $job->scheduleIn($retryInterval); } else { $job->archive('tried-too-many-times'); diff --git a/src/Recruiter/RetryPolicy/RetriableException.php b/src/Recruiter/RetryPolicy/RetriableException.php index 30546a73..896f32cc 100644 --- a/src/Recruiter/RetryPolicy/RetriableException.php +++ b/src/Recruiter/RetryPolicy/RetriableException.php @@ -9,10 +9,7 @@ class RetriableException /** @var string */ private $exceptionClass; - /** @var RetryPolicy */ - private $retryPolicy; - - public function __construct(string $exceptionClass, RetryPolicy $retryPolicy) + public function __construct(string $exceptionClass, private readonly RetryPolicy $retryPolicy) { if (!class_exists($exceptionClass)) { throw new \InvalidArgumentException("Class $exceptionClass doesn't exists"); @@ -21,7 +18,6 @@ public function __construct(string $exceptionClass, RetryPolicy $retryPolicy) throw new \InvalidArgumentException("Class $exceptionClass is not Throwable"); } $this->exceptionClass = $exceptionClass; - $this->retryPolicy = $retryPolicy; } public function exceptionClass(): string diff --git a/src/Recruiter/RetryPolicy/RetriableExceptionFilter.php b/src/Recruiter/RetryPolicy/RetriableExceptionFilter.php index bc4e2203..acfbfa81 100644 --- a/src/Recruiter/RetryPolicy/RetriableExceptionFilter.php +++ b/src/Recruiter/RetryPolicy/RetriableExceptionFilter.php @@ -8,7 +8,6 @@ class RetriableExceptionFilter implements RetryPolicy { - private $filteredRetryPolicy; private $retriableExceptions; /** @@ -21,13 +20,12 @@ public static function onlyFor($exceptionClass, RetryPolicy $retryPolicy) return new self($retryPolicy, [$exceptionClass]); } - public function __construct(RetryPolicy $filteredRetryPolicy, array $retriableExceptions = ['Exception']) + public function __construct(private readonly RetryPolicy $filteredRetryPolicy, array $retriableExceptions = ['Exception']) { - $this->filteredRetryPolicy = $filteredRetryPolicy; $this->retriableExceptions = $this->ensureAreAllExceptions($retriableExceptions); } - public function schedule(JobAfterFailure $job) + public function schedule(JobAfterFailure $job): void { if ($this->isExceptionRetriable($job->causeOfFailure())) { $this->filteredRetryPolicy->schedule($job); @@ -41,7 +39,7 @@ public function export(): array return [ 'retriable_exceptions' => $this->retriableExceptions, 'filtered_retry_policy' => [ - 'class' => get_class($this->filteredRetryPolicy), + 'class' => $this->filteredRetryPolicy::class, 'parameters' => $this->filteredRetryPolicy->export(), ], ]; @@ -76,12 +74,10 @@ private function ensureAreAllExceptions($exceptions) private function isExceptionRetriable($exception) { - if (!is_null($exception) && is_object($exception)) { + if (is_object($exception)) { return array_any( $this->retriableExceptions, - function ($retriableExceptionType) use ($exception) { - return $exception instanceof $retriableExceptionType; - }, + fn ($retriableExceptionType) => $exception instanceof $retriableExceptionType, ); } diff --git a/src/Recruiter/RetryPolicy/RetryForever.php b/src/Recruiter/RetryPolicy/RetryForever.php index 57105fd1..4ec6c57d 100644 --- a/src/Recruiter/RetryPolicy/RetryForever.php +++ b/src/Recruiter/RetryPolicy/RetryForever.php @@ -14,7 +14,7 @@ final class RetryForever implements RetryPolicy use RetryPolicyBehaviour; private $timeToWaitBeforeRetry; - public function __construct($timeToWaitBeforeRetry) + public function __construct(int|Interval $timeToWaitBeforeRetry) { if (!($timeToWaitBeforeRetry instanceof Interval)) { $timeToWaitBeforeRetry = T\seconds($timeToWaitBeforeRetry); @@ -22,12 +22,12 @@ public function __construct($timeToWaitBeforeRetry) $this->timeToWaitBeforeRetry = $timeToWaitBeforeRetry; } - public static function afterSeconds($timeToWaitBeforeRetry = 60) + public static function afterSeconds(int|Interval $timeToWaitBeforeRetry = 60): self { - return new static($timeToWaitBeforeRetry); + return new self($timeToWaitBeforeRetry); } - public function schedule(JobAfterFailure $job) + public function schedule(JobAfterFailure $job): void { $job->scheduleIn($this->timeToWaitBeforeRetry); } diff --git a/src/Recruiter/RetryPolicy/RetryManyTimes.php b/src/Recruiter/RetryPolicy/RetryManyTimes.php index fa599ebe..d5fa7d02 100644 --- a/src/Recruiter/RetryPolicy/RetryManyTimes.php +++ b/src/Recruiter/RetryPolicy/RetryManyTimes.php @@ -12,24 +12,23 @@ class RetryManyTimes implements RetryPolicy { use RetryPolicyBehaviour; - private $retryHowManyTimes; - private $timeToWaitBeforeRetry; - public function __construct($retryHowManyTimes, $timeToWaitBeforeRetry) + private Interval $timeToWaitBeforeRetry; + + public function __construct(private readonly int $retryHowManyTimes, int|Interval $timeToWaitBeforeRetry) { if (!($timeToWaitBeforeRetry instanceof Interval)) { $timeToWaitBeforeRetry = T\seconds($timeToWaitBeforeRetry); } - $this->retryHowManyTimes = $retryHowManyTimes; $this->timeToWaitBeforeRetry = $timeToWaitBeforeRetry; } - public static function forTimes($retryHowManyTimes, $timeToWaitBeforeRetry = 60) + public static function forTimes($retryHowManyTimes, int|Interval $timeToWaitBeforeRetry = 60): static { return new static($retryHowManyTimes, $timeToWaitBeforeRetry); } - public function schedule(JobAfterFailure $job) + public function schedule(JobAfterFailure $job): void { if ($job->numberOfAttempts() <= $this->retryHowManyTimes) { $job->scheduleIn($this->timeToWaitBeforeRetry); diff --git a/src/Recruiter/RetryPolicy/SelectByException.php b/src/Recruiter/RetryPolicy/SelectByException.php index 98d814f4..e00bafe6 100644 --- a/src/Recruiter/RetryPolicy/SelectByException.php +++ b/src/Recruiter/RetryPolicy/SelectByException.php @@ -24,22 +24,23 @@ */ class SelectByException implements RetryPolicy { - /** - * @var array - */ - private $exceptions; - public static function create(): SelectByExceptionBuilder { return new SelectByExceptionBuilder(); } - public function __construct(array $exceptions) - { - $this->exceptions = $exceptions; + public function __construct( + /** + * @var array + */ + private readonly array $exceptions, + ) { } - public function schedule(JobAfterFailure $job) + /** + * @throws \Exception + */ + public function schedule(JobAfterFailure $job): void { $exception = $job->causeOfFailure(); if ($this->isRetriable($exception)) { @@ -58,7 +59,7 @@ function (RetriableException $retriableException) { return [ 'when' => $retriableException->exceptionClass(), 'then' => [ - 'class' => get_class($retryPolicy), + 'class' => $retryPolicy::class, 'parameters' => $retryPolicy->export(), ], ]; @@ -89,9 +90,7 @@ public function isLastRetry(Job $job): bool // I cannot answer to that so... true only if everybody says true return array_all( $this->exceptions, - function (RetriableException $retriableException) use ($job) { - return $retriableException->retryPolicy()->isLastRetry($job); - }, + fn (RetriableException $retriableException) => $retriableException->retryPolicy()->isLastRetry($job), ); } @@ -101,11 +100,14 @@ private function isRetriable($exception): bool $this->retryPolicyFor($exception); return true; - } catch (\Exception $e) { + } catch (\Exception) { return false; } } + /** + * @throws \Exception + */ private function retryPolicyFor(?object $exception): RetryPolicy { if (!is_null($exception) && is_object($exception)) { @@ -117,7 +119,7 @@ private function retryPolicyFor(?object $exception): RetryPolicy } } if ($exception instanceof \Throwable) { - throw new \Exception('Unable to find a RetryPolicy associated to exception: ' . get_class($exception), 0, $exception); + throw new \Exception('Unable to find a RetryPolicy associated to exception: ' . $exception::class, 0, $exception); } } throw new \Exception('Unable to find a RetryPolicy associated to: ' . var_export($exception, true)); diff --git a/src/Recruiter/RetryPolicy/TimeTable.php b/src/Recruiter/RetryPolicy/TimeTable.php index b7d65d19..eec7a965 100644 --- a/src/Recruiter/RetryPolicy/TimeTable.php +++ b/src/Recruiter/RetryPolicy/TimeTable.php @@ -11,11 +11,13 @@ class TimeTable implements RetryPolicy { use RetryPolicyBehaviour; - /** @var array */ - private $timeTable; + private ?array $timeTable; - private $howManyRetries; + private int $howManyRetries; + /** + * @throws \Exception + */ public function __construct(?array $timeTable) { if (is_null($timeTable)) { @@ -29,7 +31,7 @@ public function __construct(?array $timeTable) $this->howManyRetries = self::estimateHowManyRetriesIn($timeTable); } - public function schedule(JobAfterFailure $job) + public function schedule(JobAfterFailure $job): void { foreach ($this->timeTable as $timeSpent => $rescheduleIn) { if ($this->hasBeenCreatedLessThan($job, $timeSpent)) { @@ -60,28 +62,28 @@ public static function import(array $parameters): RetryPolicy private function hasBeenCreatedLessThan($job, $relativeTime) { return $job->createdAt()->isAfter( - T\Moment::fromTimestamp(strtotime($relativeTime, T\now()->seconds())), + T\Moment::fromTimestamp(strtotime((string) $relativeTime, T\now()->seconds())), ); } - private function rescheduleIn($job, $relativeTime) + private function rescheduleIn($job, $relativeTime): void { $job->scheduleAt( - T\Moment::fromTimestamp(strtotime($relativeTime, T\now()->seconds())), + T\Moment::fromTimestamp(strtotime((string) $relativeTime, T\now()->seconds())), ); } - private static function estimateHowManyRetriesIn($timeTable) + private static function estimateHowManyRetriesIn(array $timeTable): int { $now = T\now()->seconds(); $howManyRetries = 0; $timeWindowInSeconds = 0; foreach ($timeTable as $timeWindow => $rescheduleTime) { - $timeWindowInSeconds = ($now - strtotime($timeWindow, $now)) - $timeWindowInSeconds; + $timeWindowInSeconds = ($now - strtotime((string) $timeWindow, $now)) - $timeWindowInSeconds; if ($timeWindowInSeconds <= 0) { throw new \Exception("Time window `$timeWindow` is invalid, must be in the past"); } - $rescheduleTimeInSeconds = (strtotime($rescheduleTime, $now) - $now); + $rescheduleTimeInSeconds = (strtotime((string) $rescheduleTime, $now) - $now); if ($rescheduleTimeInSeconds <= 0) { throw new \Exception("Reschedule time `$rescheduleTime` is invalid, must be in the future"); } diff --git a/src/Recruiter/RetryPolicyBehaviour.php b/src/Recruiter/RetryPolicyBehaviour.php index d934bec6..caea789a 100644 --- a/src/Recruiter/RetryPolicyBehaviour.php +++ b/src/Recruiter/RetryPolicyBehaviour.php @@ -23,7 +23,7 @@ public function retryOnlyWhenExceptionsAre($retriableExceptionTypes) return new RetriableExceptionFilter($this, $retriableExceptionTypes); } - public function schedule(JobAfterFailure $job) + public function schedule(JobAfterFailure $job): void { throw new \Exception('RetryPolicy::schedule(JobAfterFailure) need to be implemented'); } diff --git a/src/Recruiter/RetryPolicyInJob.php b/src/Recruiter/RetryPolicyInJob.php index 625645dc..dd5b5205 100644 --- a/src/Recruiter/RetryPolicyInJob.php +++ b/src/Recruiter/RetryPolicyInJob.php @@ -27,7 +27,7 @@ public static function export($retryPolicy) { return [ 'retry_policy' => [ - 'class' => get_class($retryPolicy), + 'class' => $retryPolicy::class, 'parameters' => $retryPolicy->export(), ], ]; diff --git a/src/Recruiter/SchedulePolicy/Cron.php b/src/Recruiter/SchedulePolicy/Cron.php index 8a3825fe..73e42f86 100644 --- a/src/Recruiter/SchedulePolicy/Cron.php +++ b/src/Recruiter/SchedulePolicy/Cron.php @@ -8,13 +8,8 @@ class Cron implements SchedulePolicy { - private $cronExpression; - private $now; - - public function __construct(string $cronExpression, ?\DateTime $now = null) + public function __construct(private readonly string $cronExpression, private readonly ?\DateTime $now = null) { - $this->cronExpression = $cronExpression; - $this->now = $now; } public function next(): Moment diff --git a/src/Recruiter/SchedulePolicyInJob.php b/src/Recruiter/SchedulePolicyInJob.php index 3f376346..184b9242 100644 --- a/src/Recruiter/SchedulePolicyInJob.php +++ b/src/Recruiter/SchedulePolicyInJob.php @@ -27,7 +27,7 @@ public static function export($schedulePolicy) { return [ 'schedule_policy' => [ - 'class' => get_class($schedulePolicy), + 'class' => $schedulePolicy::class, 'parameters' => $schedulePolicy->export(), ], ]; diff --git a/src/Recruiter/Scheduler.php b/src/Recruiter/Scheduler.php index b6d8dd36..409c17ff 100644 --- a/src/Recruiter/Scheduler.php +++ b/src/Recruiter/Scheduler.php @@ -10,16 +10,6 @@ class Scheduler { private $job; - private $schedulers; - - private $status; - - private $schedulePolicy; - - private $repeatable; - - private $retryPolicy; - public static function around(Repeatable $repeatable, Repository $repository, Recruiter $recruiter) { $retryPolicy = ($repeatable instanceof Retriable) ? @@ -46,18 +36,8 @@ public static function import($document, Repository $repository) ); } - public function __construct( - array $status, - Repeatable $repeatable, - ?SchedulePolicy $schedulePolicy, - ?RetryPolicy $retryPolicy, - Repository $schedulers, - ) { - $this->status = $status; - $this->repeatable = $repeatable; - $this->schedulePolicy = $schedulePolicy; - $this->retryPolicy = $retryPolicy; - $this->schedulers = $schedulers; + public function __construct(private array $status, private readonly Repeatable $repeatable, private ?SchedulePolicy $schedulePolicy, private ?RetryPolicy $retryPolicy, private readonly Repository $schedulers) + { } public function create() @@ -122,7 +102,7 @@ private function aJobIsStillRunning(JobsRepository $jobs) $alreadyScheduledJob = $jobs->scheduled($this->status['last_scheduling']['job_id']); return true; - } catch (\Throwable $e) { + } catch (\Throwable) { return false; } } @@ -147,7 +127,7 @@ public function schedule(JobsRepository $jobs) ++$this->status['attempts']; $this->schedulers->save($this); - $jobToSchedule = (new JobToSchedule(Job::around($this->repeatable, $jobs))) + $jobToSchedule = new JobToSchedule(Job::around($this->repeatable, $jobs)) ->scheduleAt($nextScheduling) ->retryWithPolicy($this->retryPolicy) ->scheduledBy('scheduler', $this->status['urn'], $this->status['attempts']) diff --git a/src/Recruiter/Scheduler/Repository.php b/src/Recruiter/Scheduler/Repository.php index 61f8e3c0..d22ccb96 100644 --- a/src/Recruiter/Scheduler/Repository.php +++ b/src/Recruiter/Scheduler/Repository.php @@ -40,13 +40,11 @@ public function create(Scheduler $scheduler) if (0 === $this->schedulers->count(['urn' => $document['urn']])) { $this->schedulers->insertOne($document); } else { - $document = array_filter($document, function ($key) { - return in_array($key, [ - 'job', - 'schedule_policy', - 'unique', - ]); - }, ARRAY_FILTER_USE_KEY); + $document = array_filter($document, fn ($key) => in_array($key, [ + 'job', + 'schedule_policy', + 'unique', + ]), ARRAY_FILTER_USE_KEY); $this->schedulers->updateOne( ['urn' => $scheduler->urn()], diff --git a/src/Recruiter/SynchronousExecutionReport.php b/src/Recruiter/SynchronousExecutionReport.php index 2775aa5e..f2f80cda 100644 --- a/src/Recruiter/SynchronousExecutionReport.php +++ b/src/Recruiter/SynchronousExecutionReport.php @@ -9,17 +9,11 @@ */ class SynchronousExecutionReport { - /** - * @var array - */ - private $data; - /** * @param array $data = [] */ - public function __construct(array $data = []) + public function __construct(private readonly array $data = []) { - $this->data = $data; } /** @@ -32,9 +26,7 @@ public static function fromArray(array $data): SynchronousExecutionReport public function isThereAFailure(): bool { - return array_any($this->data, function ($jobExecution, $jobId) { - return $jobExecution->isFailed(); - }); + return array_any($this->data, fn ($jobExecution, $jobId) => $jobExecution->isFailed()); } public function toArray() diff --git a/src/Recruiter/Taggable.php b/src/Recruiter/Taggable.php index 0e9354e3..0d2e37db 100644 --- a/src/Recruiter/Taggable.php +++ b/src/Recruiter/Taggable.php @@ -7,7 +7,7 @@ interface Taggable /** * A Job can decide its own tags. Tags are useful to correlate jobs. * - * @return array Strings to be used to tag the job + * @return array Strings to be used to tag the job */ - public function taggedAs(); + public function taggedAs(): array; } diff --git a/src/Recruiter/WaitStrategy.php b/src/Recruiter/WaitStrategy.php index 0b71e42f..8278023c 100644 --- a/src/Recruiter/WaitStrategy.php +++ b/src/Recruiter/WaitStrategy.php @@ -9,14 +9,12 @@ class WaitStrategy private $timeToWaitAtLeast; private $timeToWaitAtMost; private $timeToWait; - private $howToWait; - public function __construct(Interval $timeToWaitAtLeast, Interval $timeToWaitAtMost, $howToWait = 'usleep') + public function __construct(Interval $timeToWaitAtLeast, Interval $timeToWaitAtMost, private $howToWait = 'usleep') { $this->timeToWaitAtLeast = $timeToWaitAtLeast->milliseconds(); $this->timeToWaitAtMost = $timeToWaitAtMost->milliseconds(); $this->timeToWait = $timeToWaitAtLeast->milliseconds(); - $this->howToWait = $howToWait; } public function reset() diff --git a/src/Recruiter/Workable/AlwaysFail.php b/src/Recruiter/Workable/AlwaysFail.php index 3a90f1fa..f0439e11 100644 --- a/src/Recruiter/Workable/AlwaysFail.php +++ b/src/Recruiter/Workable/AlwaysFail.php @@ -9,7 +9,7 @@ class AlwaysFail implements Workable { use WorkableBehaviour; - public function execute() + public function execute(): never { throw new \Exception('Sorry, I\'m good for nothing'); } diff --git a/src/Recruiter/Workable/FactoryMethodCommand.php b/src/Recruiter/Workable/FactoryMethodCommand.php index deac6c23..431871bb 100644 --- a/src/Recruiter/Workable/FactoryMethodCommand.php +++ b/src/Recruiter/Workable/FactoryMethodCommand.php @@ -12,7 +12,7 @@ public static function from(/* $callable[, $argument, $argument...] */) { $arguments = func_get_args(); $callable = array_shift($arguments); - [$class, $method] = explode('::', $callable); + [$class, $method] = explode('::', (string) $callable); return self::singleStep(self::stepFor($class, $method, $arguments)); } @@ -37,11 +37,8 @@ private static function stepFor($class, $method, $arguments) return $step; } - private $steps; - - private function __construct(array $steps = []) + private function __construct(private array $steps = []) { - $this->steps = $steps; } public function asJobOf(Recruiter $recruiter): JobToSchedule @@ -62,7 +59,7 @@ public function execute($retryOptions = null) if (!is_callable($callable)) { $message = 'The following step does not result in a callable: ' . var_export($step, true) . '.'; if (is_object($result)) { - $message .= ' Reached object: ' . get_class($result); + $message .= ' Reached object: ' . $result::class; } else { $message .= ' Reached value: ' . var_export($result, true); } @@ -83,7 +80,7 @@ public function execute($retryOptions = null) private function arguments($step) { - $arguments = isset($step['arguments']) ? $step['arguments'] : []; + $arguments = $step['arguments'] ?? []; return $arguments; } diff --git a/src/Recruiter/Workable/LazyBones.php b/src/Recruiter/Workable/LazyBones.php index d2c99115..29a3c285 100644 --- a/src/Recruiter/Workable/LazyBones.php +++ b/src/Recruiter/Workable/LazyBones.php @@ -25,7 +25,7 @@ public function __construct(private readonly int $usToSleep = 1, private readonl public function execute(): void { - usleep($this->usToSleep + rand(intval(-$this->usOfDelta), $this->usOfDelta)); + usleep($this->usToSleep + random_int(intval(-$this->usOfDelta), $this->usOfDelta)); } public function export(): array diff --git a/src/Recruiter/Workable/RecoverRepeatableFromException.php b/src/Recruiter/Workable/RecoverRepeatableFromException.php index 4521f879..bb33d04e 100644 --- a/src/Recruiter/Workable/RecoverRepeatableFromException.php +++ b/src/Recruiter/Workable/RecoverRepeatableFromException.php @@ -9,19 +9,14 @@ class RecoverRepeatableFromException implements Repeatable { use WorkableBehaviour; - protected $recoverForClass; - protected $recoverForException; - - public function __construct($parameters, $recoverForClass, $recoverForException) + public function __construct($parameters, protected $recoverForClass, protected $recoverForException) { $this->parameters = $parameters; - $this->recoverForClass = $recoverForClass; - $this->recoverForException = $recoverForException; } - public function execute() + public function execute(): never { - throw new \Exception('This job failed while instantiating a workable of class: ' . $this->recoverForClass . PHP_EOL . 'Original exception: ' . get_class($this->recoverForException) . PHP_EOL . $this->recoverForException->getMessage() . PHP_EOL . $this->recoverForException->getTraceAsString() . PHP_EOL); + throw new \Exception('This job failed while instantiating a workable of class: ' . $this->recoverForClass . PHP_EOL . 'Original exception: ' . $this->recoverForException::class . PHP_EOL . $this->recoverForException->getMessage() . PHP_EOL . $this->recoverForException->getTraceAsString() . PHP_EOL); } public function getClass() diff --git a/src/Recruiter/Workable/RecoverWorkableFromException.php b/src/Recruiter/Workable/RecoverWorkableFromException.php index 6993f755..ebeacc86 100644 --- a/src/Recruiter/Workable/RecoverWorkableFromException.php +++ b/src/Recruiter/Workable/RecoverWorkableFromException.php @@ -9,19 +9,14 @@ class RecoverWorkableFromException implements Workable { use WorkableBehaviour; - protected $recoverForClass; - protected $recoverForException; - - public function __construct($parameters, $recoverForClass, $recoverForException) + public function __construct($parameters, protected $recoverForClass, protected $recoverForException) { $this->parameters = $parameters; - $this->recoverForClass = $recoverForClass; - $this->recoverForException = $recoverForException; } - public function execute() + public function execute(): never { - throw new \Exception('This job failed while instantiating a workable of class: ' . $this->recoverForClass . PHP_EOL . 'Original exception: ' . get_class($this->recoverForException) . PHP_EOL . $this->recoverForException->getMessage() . PHP_EOL . $this->recoverForException->getTraceAsString() . PHP_EOL); + throw new \Exception('This job failed while instantiating a workable of class: ' . $this->recoverForClass . PHP_EOL . 'Original exception: ' . $this->recoverForException::class . PHP_EOL . $this->recoverForException->getMessage() . PHP_EOL . $this->recoverForException->getTraceAsString() . PHP_EOL); } public function getClass() diff --git a/src/Recruiter/Workable/SampleRepeatableCommand.php b/src/Recruiter/Workable/SampleRepeatableCommand.php index 62faad5c..07bb97be 100644 --- a/src/Recruiter/Workable/SampleRepeatableCommand.php +++ b/src/Recruiter/Workable/SampleRepeatableCommand.php @@ -14,7 +14,7 @@ class SampleRepeatableCommand implements Workable, Repeatable public function execute() { - var_export((new \DateTime())->format('c')); + var_export(new \DateTime()->format('c')); } public function urn(): string diff --git a/src/Recruiter/Workable/ShellCommand.php b/src/Recruiter/Workable/ShellCommand.php index 2e763704..c92778cc 100644 --- a/src/Recruiter/Workable/ShellCommand.php +++ b/src/Recruiter/Workable/ShellCommand.php @@ -9,16 +9,13 @@ class ShellCommand implements Workable { use WorkableBehaviour; - private $commandLine; - public static function fromCommandLine($commandLine) { return new self($commandLine); } - private function __construct($commandLine) + private function __construct(private $commandLine) { - $this->commandLine = $commandLine; } public function execute() diff --git a/src/Recruiter/WorkableBehaviour.php b/src/Recruiter/WorkableBehaviour.php index 2f6d20e5..dd6f40e3 100644 --- a/src/Recruiter/WorkableBehaviour.php +++ b/src/Recruiter/WorkableBehaviour.php @@ -13,7 +13,7 @@ public function asJobOf(Recruiter $recruiter): JobToSchedule return $recruiter->jobOf($this); } - public function execute() + public function execute(): never { throw new \Exception('Workable::execute() need to be implemented'); } diff --git a/src/Recruiter/WorkableInJob.php b/src/Recruiter/WorkableInJob.php index d53241dc..f09b7f87 100644 --- a/src/Recruiter/WorkableInJob.php +++ b/src/Recruiter/WorkableInJob.php @@ -55,7 +55,7 @@ public static function initialize() private static function classNameOf($workable) { - $workableClassName = get_class($workable); + $workableClassName = $workable::class; if (method_exists($workable, 'getClass')) { $workableClassName = $workable->getClass(); } diff --git a/src/Recruiter/Worker.php b/src/Recruiter/Worker.php index e9885622..072d5e3c 100644 --- a/src/Recruiter/Worker.php +++ b/src/Recruiter/Worker.php @@ -12,11 +12,6 @@ class Worker { - private $status; - private $recruiter; - private $repository; - private $memoryLimit; - public static function workFor( Recruiter $recruiter, Repository $repository, @@ -28,16 +23,8 @@ public static function workFor( return $worker; } - public function __construct( - $status, - Recruiter $recruiter, - Repository $repository, - MemoryLimit $memoryLimit, - ) { - $this->status = $status; - $this->recruiter = $recruiter; - $this->repository = $repository; - $this->memoryLimit = $memoryLimit; + public function __construct(private $status, private readonly Recruiter $recruiter, private readonly Repository $repository, private readonly MemoryLimit $memoryLimit) + { } public function id() @@ -131,7 +118,7 @@ private function afterExecutionOf($job) date('c'), $this->id(), $job->id(), - get_class($e), + $e::class, $e->getMessage(), ); @@ -208,9 +195,7 @@ public static function pickAvailableWorkers(MongoCollection $collection, $worker if (count($workers) > 0) { $unitsOfWorkers = array_group_by( $workers, - function ($worker) { - return $worker['work_on']; - }, + fn ($worker) => $worker['work_on'], ); foreach ($unitsOfWorkers as $workOn => $workersInUnit) { $workersInUnit = array_column($workersInUnit, '_id'); @@ -225,9 +210,7 @@ function ($worker) { public static function tryToAssignJobsToWorkers(MongoCollection $collection, $jobs, $workers) { $assignment = array_combine( - array_map(function ($id) { - return (string) $id; - }, $workers), + array_map(fn ($id) => (string) $id, $workers), $jobs, ); diff --git a/src/Recruiter/Worker/Process.php b/src/Recruiter/Worker/Process.php index 2dd02853..815be80f 100644 --- a/src/Recruiter/Worker/Process.php +++ b/src/Recruiter/Worker/Process.php @@ -6,26 +6,23 @@ class Process { - private $pid; - - public static function withPid($pid) + public static function withPid(int $pid) { return new self($pid); } - public function __construct($pid) + public function __construct(private readonly int $pid) { - $this->pid = $pid; } - public function cleanUp(Repository $repository) + public function cleanUp(Repository $repository): void { if (!$this->isAlive()) { $repository->retireWorkerWithPid($this->pid); } } - public function ifDead() + public function ifDead(): BlackHole|static { if ($this->isAlive()) { return new BlackHole(); @@ -34,7 +31,7 @@ public function ifDead() return $this; } - protected function isAlive() + protected function isAlive(): bool { return posix_kill($this->pid, 0); } diff --git a/src/Recruiter/Worker/Repository.php b/src/Recruiter/Worker/Repository.php index 1e04a7d4..74fd14a9 100644 --- a/src/Recruiter/Worker/Repository.php +++ b/src/Recruiter/Worker/Repository.php @@ -9,12 +9,10 @@ class Repository { private $roster; - private $recruiter; - public function __construct(MongoDB\Database $db, Recruiter $recruiter) + public function __construct(MongoDB\Database $db, private readonly Recruiter $recruiter) { $this->roster = $db->selectCollection('roster'); - $this->recruiter = $recruiter; } public function save($worker) diff --git a/src/Recruiter/functions.php b/src/Recruiter/functions.php index 7e501820..00543400 100644 --- a/src/Recruiter/functions.php +++ b/src/Recruiter/functions.php @@ -4,9 +4,7 @@ function array_group_by($array, ?callable $f = null): array { - $f = $f ?: function ($value) { - return $value; - }; + $f = $f ?: (fn ($value) => $value); return array_reduce( $array, diff --git a/src/Sink/BlackHole.php b/src/Sink/BlackHole.php index e94c724c..e4571ee6 100644 --- a/src/Sink/BlackHole.php +++ b/src/Sink/BlackHole.php @@ -5,7 +5,7 @@ use ArrayAccess; use Iterator; -class BlackHole implements \Iterator, \ArrayAccess +class BlackHole implements \Iterator, \ArrayAccess, \Stringable { public function __construct() { From a43b46f61f8d0a0491522c95a2ed20d4483ca1e2 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 21:26:31 +0200 Subject: [PATCH 38/59] Increase rector level to 3 --- rector.php | 5 ++-- spec/Recruiter/Job/RepositoryTest.php | 7 +++--- .../RetryPolicy/ExponentialBackoffTest.php | 5 ++-- .../RetriableExceptionFilterTest.php | 4 ++-- .../RetryPolicy/SelectByExceptionTest.php | 24 ++++++++++--------- spec/Recruiter/RetryPolicy/TimeTableTest.php | 5 ++-- spec/Recruiter/WorkerProcessTest.php | 8 +++---- 7 files changed, 32 insertions(+), 26 deletions(-) diff --git a/rector.php b/rector.php index 1d25ee45..51312e9f 100644 --- a/rector.php +++ b/rector.php @@ -12,6 +12,7 @@ ]) // uncomment to reach your current PHP version ->withPhpSets() - ->withTypeCoverageLevel(2) + ->withTypeCoverageLevel(3) ->withDeadCodeLevel(0) - ->withCodeQualityLevel(1); + ->withCodeQualityLevel(1) +; diff --git a/spec/Recruiter/Job/RepositoryTest.php b/spec/Recruiter/Job/RepositoryTest.php index 915e2685..4bbca3b1 100644 --- a/spec/Recruiter/Job/RepositoryTest.php +++ b/spec/Recruiter/Job/RepositoryTest.php @@ -12,6 +12,7 @@ use Recruiter\Job; use Recruiter\JobExecution; use Recruiter\JobToSchedule; +use Recruiter\RetryPolicy\DoNotDoItAgain; use Recruiter\Workable; use Symfony\Component\EventDispatcher\EventDispatcherInterface; use Timeless as T; @@ -439,7 +440,7 @@ private function aJobToSchedule($job = null) return new JobToSchedule($job); } - private function workableMock() + private function workableMock(): MockObject&Workable { return $this ->getMockBuilder(Workable::class) @@ -459,7 +460,7 @@ private function workableMockWithCustomParameters($parameters) return $workable; } - private function jobExecutionMock($executionParameters) + private function jobExecutionMock(array $executionParameters): MockObject&JobExecution { $jobExecutionMock = $this ->getMockBuilder(JobExecution::class) @@ -490,7 +491,7 @@ private function jobMockWithAttemptsAndCustomParameters( 'ended_at' => T\MongoDate::from($endedAt), ], 'retry_policy' => [ - 'class' => \Recruiter\RetryPolicy\DoNotDoItAgain::class, + 'class' => DoNotDoItAgain::class, 'parameters' => [], ], ]; diff --git a/spec/Recruiter/RetryPolicy/ExponentialBackoffTest.php b/spec/Recruiter/RetryPolicy/ExponentialBackoffTest.php index d9f3f966..0106c203 100644 --- a/spec/Recruiter/RetryPolicy/ExponentialBackoffTest.php +++ b/spec/Recruiter/RetryPolicy/ExponentialBackoffTest.php @@ -2,6 +2,7 @@ namespace Recruiter\RetryPolicy; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Recruiter\JobAfterFailure; use Timeless as T; @@ -52,12 +53,12 @@ public function testCanBeCreatedByTargetingAMaximumInterval(): void ); } - private function jobExecutedFor($times) + private function jobExecutedFor(int $times): MockObject&JobAfterFailure { $job = $this->getMockBuilder(JobAfterFailure::class)->disableOriginalConstructor()->getMock(); $job->expects($this->any()) ->method('numberOfAttempts') - ->will($this->returnValue($times)) + ->willReturn($times) ; return $job; diff --git a/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php b/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php index 750063dc..732b3301 100644 --- a/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php +++ b/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php @@ -128,7 +128,7 @@ public function testRetriableExceptionsThatAreNotExceptions(): void new RetriableExceptionFilter($retryPolicy, [$notAnExceptionClass]); } - private function jobFailedWithException($exception) + private function jobFailedWithException(\Throwable $exception): MockObject&JobAfterFailure { $jobAfterFailure = $this ->getMockBuilder(JobAfterFailure::class) @@ -139,7 +139,7 @@ private function jobFailedWithException($exception) $jobAfterFailure ->expects($this->any()) ->method('causeOfFailure') - ->will($this->returnValue($exception)) + ->willReturn($exception) ; return $jobAfterFailure; diff --git a/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php b/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php index 515a6e3f..9222c9f9 100644 --- a/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php +++ b/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php @@ -2,6 +2,7 @@ namespace Recruiter\RetryPolicy; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Recruiter\JobAfterFailure; use Recruiter\RetryPolicy; @@ -11,21 +12,19 @@ class SelectByExceptionTest extends TestCase { public function testCanBeBuilt(): void { - $retryPolicy = SelectByException::create() - ->when(\InvalidArgumentException::class)->then(new DoNotDoItAgain()) - ->when(\LogicException::class)->then(new DoNotDoItAgain()) - ->build() + SelectByException::create() + ->when(\InvalidArgumentException::class)->then(new DoNotDoItAgain()) + ->when(\LogicException::class)->then(new DoNotDoItAgain()) + ->build() ; - - $this->assertInstanceOf(RetryPolicy::class, $retryPolicy); } public function testCanBeExportedAndImported(): void { $retryPolicy = SelectByException::create() - ->when(\InvalidArgumentException::class)->then(new DoNotDoItAgain()) - ->when(\LogicException::class)->then(new DoNotDoItAgain()) - ->build() + ->when(\InvalidArgumentException::class)->then(new DoNotDoItAgain()) + ->when(\LogicException::class)->then(new DoNotDoItAgain()) + ->build() ; $retryPolicyExported = $retryPolicy->export(); @@ -51,6 +50,9 @@ public function testSelectByException(): void $retryPolicy->schedule($job); } + /** + * @throws \Exception + */ public function testDefaultDoNotSchedule(): void { $exception = new \Exception('something'); @@ -65,12 +67,12 @@ public function testDefaultDoNotSchedule(): void $retryPolicy->schedule($job); } - private function jobFailedWith(\Exception $exception) + private function jobFailedWith(\Throwable $exception): MockObject&JobAfterFailure { $job = $this->getMockBuilder(JobAfterFailure::class)->disableOriginalConstructor()->getMock(); $job->expects($this->any()) ->method('causeOfFailure') - ->will($this->returnValue($exception)) + ->willReturn($exception) ; return $job; diff --git a/spec/Recruiter/RetryPolicy/TimeTableTest.php b/spec/Recruiter/RetryPolicy/TimeTableTest.php index 4110e6a9..d2a8499d 100644 --- a/spec/Recruiter/RetryPolicy/TimeTableTest.php +++ b/spec/Recruiter/RetryPolicy/TimeTableTest.php @@ -2,6 +2,7 @@ namespace Recruiter\RetryPolicy; +use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Recruiter\Job; use Recruiter\JobAfterFailure; @@ -111,7 +112,7 @@ public function testInvalidTimeTableBecauseRescheduleTimeIsGreaterThanTimeWindow $tt = new TimeTable(['1 minute ago' => '2 minutes']); } - private function givenJobThat(T\Moment $wasCreatedAt) + private function givenJobThat(T\Moment $wasCreatedAt): MockObject&JobAfterFailure { $job = $this->getMockBuilder(JobAfterFailure::class) ->disableOriginalConstructor() @@ -126,7 +127,7 @@ private function givenJobThat(T\Moment $wasCreatedAt) return $job; } - private function jobThatWasCreated(string $relativeTime): JobAfterFailure + private function jobThatWasCreated(string $relativeTime): MockObject&JobAfterFailure { $wasCreatedAt = T\Moment::fromTimestamp(strtotime($relativeTime)); $job = $this->getMockBuilder(JobAfterFailure::class) diff --git a/spec/Recruiter/WorkerProcessTest.php b/spec/Recruiter/WorkerProcessTest.php index 07a036b6..209ea21b 100644 --- a/spec/Recruiter/WorkerProcessTest.php +++ b/spec/Recruiter/WorkerProcessTest.php @@ -59,17 +59,17 @@ public function testDoNotRetireWorkerIfAlive(): void $process->cleanUp($this->repository); } - private function givenWorkerProcessAlive() + private function givenWorkerProcessAlive(): MockObject&Process { return $this->givenWorkerProcess(true); } - private function givenWorkerProcessDead() + private function givenWorkerProcessDead(): MockObject&Process { return $this->givenWorkerProcess(false); } - private function givenWorkerProcess($alive) + private function givenWorkerProcess(bool $alive): MockObject&Process { $process = $this->getMockBuilder(Process::class) ->onlyMethods(['isAlive']) @@ -79,7 +79,7 @@ private function givenWorkerProcess($alive) $process->expects($this->any()) ->method('isAlive') - ->will($this->returnValue($alive)) + ->willReturn($alive) ; return $process; From 6ed91571aad1180147039c53c06e83d570feab38 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 21:29:53 +0200 Subject: [PATCH 39/59] Increase rector level to 4 --- rector.php | 2 +- ...reCalledWhenWorkableImplementsFinalizerInterfaceTest.php | 2 +- spec/Recruiter/Job/RepositoryTest.php | 6 +++--- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rector.php b/rector.php index 51312e9f..bfef9bcc 100644 --- a/rector.php +++ b/rector.php @@ -12,7 +12,7 @@ ]) // uncomment to reach your current PHP version ->withPhpSets() - ->withTypeCoverageLevel(3) + ->withTypeCoverageLevel(4) ->withDeadCodeLevel(0) ->withCodeQualityLevel(1) ; diff --git a/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php b/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php index daceaafb..a5891c64 100644 --- a/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php +++ b/spec/Recruiter/FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest.php @@ -10,7 +10,7 @@ class FinalizerMethodsAreCalledWhenWorkableImplementsFinalizerInterfaceTest extends TestCase { private MockObject&Repository $repository; - private EventDispatcherInterface $dispatcher; + private MockObject&EventDispatcherInterface $dispatcher; private ListenerSpy $listener; /** diff --git a/spec/Recruiter/Job/RepositoryTest.php b/spec/Recruiter/Job/RepositoryTest.php index 4bbca3b1..b5ddb718 100644 --- a/spec/Recruiter/Job/RepositoryTest.php +++ b/spec/Recruiter/Job/RepositoryTest.php @@ -24,7 +24,7 @@ class RepositoryTest extends TestCase private Database $recruiterDb; private Repository $repository; private T\ClockInterface $clock; - private EventDispatcherInterface $eventDispatcher; + private MockObject&EventDispatcherInterface $eventDispatcher; /** * @throws Exception @@ -448,13 +448,13 @@ private function workableMock(): MockObject&Workable ; } - private function workableMockWithCustomParameters($parameters) + private function workableMockWithCustomParameters(array $parameters): MockObject&Workable { $workable = $this->workableMock(); $workable ->expects($this->any()) ->method('export') - ->will($this->returnValue($parameters)) + ->willReturn($parameters) ; return $workable; From f19c94d35ea723e93d6c1773f448a267a0ab9506 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 21:35:53 +0200 Subject: [PATCH 40/59] Fix type hint --- .../RetryPolicy/RetriableExceptionFilterTest.php | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php b/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php index 732b3301..f0767844 100644 --- a/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php +++ b/spec/Recruiter/RetryPolicy/RetriableExceptionFilterTest.php @@ -2,6 +2,7 @@ namespace Recruiter\RetryPolicy; +use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Recruiter\JobAfterFailure; @@ -12,13 +13,16 @@ class RetriableExceptionFilterTest extends TestCase private MockObject&RetryPolicy $filteredRetryPolicy; /** - * @throws \PHPUnit\Framework\MockObject\Exception + * @throws Exception */ protected function setUp(): void { $this->filteredRetryPolicy = $this->createMock(RetryPolicy::class); } + /** + * @throws Exception + */ public function testCallScheduleOnRetriableException(): void { $exception = $this->createMock(\Exception::class); @@ -128,7 +132,7 @@ public function testRetriableExceptionsThatAreNotExceptions(): void new RetriableExceptionFilter($retryPolicy, [$notAnExceptionClass]); } - private function jobFailedWithException(\Throwable $exception): MockObject&JobAfterFailure + private function jobFailedWithException(mixed $exception): MockObject&JobAfterFailure { $jobAfterFailure = $this ->getMockBuilder(JobAfterFailure::class) From 0f2727a0a469417e1652fd14eee252072f114afb Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 22:00:27 +0200 Subject: [PATCH 41/59] Increase PHPStan level to 2 --- phpstan.neon | 4 +- spec/Recruiter/Job/RepositoryTest.php | 2 +- spec/Recruiter/JobToScheduleTest.php | 1 + .../Command/Bko/RemoveSchedulerCommand.php | 6 ++- src/Recruiter/Job.php | 2 +- src/Recruiter/JobToSchedule.php | 38 ++++++++++++++----- src/Timeless/Clock.php | 12 ++++-- src/Timeless/ClockInterface.php | 4 +- src/Timeless/StoppedClock.php | 12 ++++-- 9 files changed, 56 insertions(+), 25 deletions(-) diff --git a/phpstan.neon b/phpstan.neon index 3bb2b042..2db8cff2 100644 --- a/phpstan.neon +++ b/phpstan.neon @@ -1,5 +1,5 @@ parameters: - level: 1 + level: 2 paths: - src - spec @@ -9,5 +9,5 @@ parameters: - '#Function recruiter_became_master not found.#' - '#Function recruiter_stept_back not found.#' - '#Unsafe usage of new static\(\).#' - - '#Call to an undefined static method Sink\\BlackHole::whateverStaticMethod\(\).#' + - '#to an undefined .* Sink\\BlackHole#' inferPrivatePropertyTypeFromConstructor: true diff --git a/spec/Recruiter/Job/RepositoryTest.php b/spec/Recruiter/Job/RepositoryTest.php index b5ddb718..547d5212 100644 --- a/spec/Recruiter/Job/RepositoryTest.php +++ b/spec/Recruiter/Job/RepositoryTest.php @@ -23,7 +23,7 @@ class RepositoryTest extends TestCase { private Database $recruiterDb; private Repository $repository; - private T\ClockInterface $clock; + private T\StoppedClock $clock; private MockObject&EventDispatcherInterface $eventDispatcher; /** diff --git a/spec/Recruiter/JobToScheduleTest.php b/spec/Recruiter/JobToScheduleTest.php index ce35099b..943ccbce 100644 --- a/spec/Recruiter/JobToScheduleTest.php +++ b/spec/Recruiter/JobToScheduleTest.php @@ -17,6 +17,7 @@ protected function setUp(): void $this->job = $this ->getMockBuilder(Job::class) ->disableOriginalConstructor() + ->addMethods(['send']) ->getMock() ; } diff --git a/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php b/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php index 29369c47..df3aa187 100644 --- a/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php +++ b/src/Recruiter/Infrastructure/Command/Bko/RemoveSchedulerCommand.php @@ -9,6 +9,7 @@ use Recruiter\Infrastructure\Persistence\Mongodb\URI as MongoURI; use Recruiter\Scheduler\Repository as SchedulerRepository; use Symfony\Component\Console\Command\Command; +use Symfony\Component\Console\Helper\QuestionHelper; use Symfony\Component\Console\Helper\Table; use Symfony\Component\Console\Helper\TableSeparator; use Symfony\Component\Console\Input\InputInterface; @@ -76,6 +77,7 @@ protected function execute(InputInterface $input, OutputInterface $output): int private function selectUrnToDelete(array $urns, InputInterface $input, OutputInterface $output) { + /** @var QuestionHelper $helper */ $helper = $this->getHelper('question'); $question = new ChoiceQuestion( 'Please select the scheduler which you want delete', @@ -94,7 +96,7 @@ private function selectUrnToDelete(array $urns, InputInterface $input, OutputInt return $selectedUrn; } - private function printTable(array $data, OutputInterface $output) + private function printTable(array $data, OutputInterface $output): void { $rows = []; foreach ($data as $row) { @@ -112,7 +114,7 @@ private function printTable(array $data, OutputInterface $output) echo PHP_EOL; } - protected function buildOutputData() + protected function buildOutputData(): ?array { $outputData = []; $i = 0; diff --git a/src/Recruiter/Job.php b/src/Recruiter/Job.php index b784650b..20083471 100644 --- a/src/Recruiter/Job.php +++ b/src/Recruiter/Job.php @@ -124,7 +124,7 @@ public function scheduledBy(string $namespace, string $id, int $executions) return $this; } - public function methodToCallOnWorkable($method) + public function methodToCallOnWorkable($method): void { if (!method_exists($this->workable, $method)) { throw new \Exception("Unknown method '$method' on workable instance"); diff --git a/src/Recruiter/JobToSchedule.php b/src/Recruiter/JobToSchedule.php index cf260a7e..cbc48d98 100644 --- a/src/Recruiter/JobToSchedule.php +++ b/src/Recruiter/JobToSchedule.php @@ -7,22 +7,27 @@ use Timeless\Interval; use Timeless\Moment; +/** + * @method send() to make PHPStan happy in tests + */ class JobToSchedule { - /** @var bool */ - private $mustBeScheduled; + private bool $mustBeScheduled; public function __construct(private readonly Job $job) { $this->mustBeScheduled = false; } - public function doNotRetry() + public function doNotRetry(): static { return $this->retryWithPolicy(new RetryPolicy\DoNotDoItAgain()); } - public function retryManyTimes($howManyTimes, Interval $timeToWaitBeforeRetry, $retriableExceptionTypes = []) + /** + * @return $this + */ + public function retryManyTimes($howManyTimes, Interval $timeToWaitBeforeRetry, $retriableExceptionTypes = []): static { $this->job->retryWithPolicy( $this->filterForRetriableExceptions( @@ -34,7 +39,10 @@ public function retryManyTimes($howManyTimes, Interval $timeToWaitBeforeRetry, $ return $this; } - public function retryWithPolicy(RetryPolicy $retryPolicy, $retriableExceptionTypes = []) + /** + * @return $this + */ + public function retryWithPolicy(RetryPolicy $retryPolicy, $retriableExceptionTypes = []): static { $this->job->retryWithPolicy( $this->filterForRetriableExceptions( @@ -46,17 +54,26 @@ public function retryWithPolicy(RetryPolicy $retryPolicy, $retriableExceptionTyp return $this; } - public function inBackground() + /** + * @return $this + */ + public function inBackground(): static { return $this->scheduleAt(T\now()); } - public function scheduleIn(Interval $duration) + /** + * @return $this + */ + public function scheduleIn(Interval $duration): static { return $this->scheduleAt($duration->fromNow()); } - public function scheduleAt(Moment $momentInTime) + /** + * @return $this + */ + public function scheduleAt(Moment $momentInTime): static { $this->mustBeScheduled = true; $this->job->scheduleAt($momentInTime); @@ -115,7 +132,10 @@ private function emptyEventDispatcher(): EventDispatcher return new EventDispatcher(); } - public function __call($name, $arguments) + /** + * @throws \Exception + */ + public function __call(string $name, array $arguments) { $this->job->methodToCallOnWorkable($name); diff --git a/src/Timeless/Clock.php b/src/Timeless/Clock.php index d046af0f..0c90fa27 100644 --- a/src/Timeless/Clock.php +++ b/src/Timeless/Clock.php @@ -4,14 +4,18 @@ class Clock implements ClockInterface { - public function start(): ClockInterface + public function start(): self { - return clock($this); + clock($this); + + return $this; } - public function stop(): ClockInterface + public function stop(): StoppedClock { - return clock(new StoppedClock($this->now())); + clock($clock = new StoppedClock($this->now())); + + return $clock; } public function now(): Moment diff --git a/src/Timeless/ClockInterface.php b/src/Timeless/ClockInterface.php index b5a365b8..65faa620 100644 --- a/src/Timeless/ClockInterface.php +++ b/src/Timeless/ClockInterface.php @@ -6,7 +6,7 @@ interface ClockInterface { public function now(): Moment; - public function start(): ClockInterface; + public function start(): Clock; - public function stop(): ClockInterface; + public function stop(): StoppedClock; } diff --git a/src/Timeless/StoppedClock.php b/src/Timeless/StoppedClock.php index 77392ab4..5a82acd0 100644 --- a/src/Timeless/StoppedClock.php +++ b/src/Timeless/StoppedClock.php @@ -18,13 +18,17 @@ public function driftForwardBySeconds(int $seconds): void $this->now = $this->now->after(seconds($seconds)); } - public function start(): ClockInterface + public function start(): Clock { - return clock(new Clock()); + clock($clock = new Clock()); + + return $clock; } - public function stop(): ClockInterface + public function stop(): self { - return clock($this); + clock($this); + + return $this; } } From 5f889bc750eb54afac56208ef4c3a5c816a3e7cc Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 22:11:48 +0200 Subject: [PATCH 42/59] Try to fix more test issues --- spec/Recruiter/JobToScheduleTest.php | 11 +++--- src/Recruiter/Job.php | 19 +++++++--- src/Recruiter/Job/Repository.php | 57 +++++++++++++++------------- 3 files changed, 50 insertions(+), 37 deletions(-) diff --git a/spec/Recruiter/JobToScheduleTest.php b/spec/Recruiter/JobToScheduleTest.php index 943ccbce..be23900f 100644 --- a/spec/Recruiter/JobToScheduleTest.php +++ b/spec/Recruiter/JobToScheduleTest.php @@ -2,6 +2,7 @@ namespace Recruiter; +use PHPUnit\Framework\MockObject\Exception; use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Timeless as T; @@ -11,15 +12,13 @@ class JobToScheduleTest extends TestCase private T\ClockInterface $clock; private MockObject&Job $job; + /** + * @throws Exception + */ protected function setUp(): void { $this->clock = T\clock()->stop(); - $this->job = $this - ->getMockBuilder(Job::class) - ->disableOriginalConstructor() - ->addMethods(['send']) - ->getMock() - ; + $this->job = $this->createMock(Job::class); } protected function tearDown(): void diff --git a/src/Recruiter/Job.php b/src/Recruiter/Job.php index 20083471..ef8af24f 100644 --- a/src/Recruiter/Job.php +++ b/src/Recruiter/Job.php @@ -96,7 +96,10 @@ public function inGroup(array|string $group): static return $this; } - public function scheduleAt(Moment $at) + /** + * @return $this + */ + public function scheduleAt(Moment $at): static { $this->status['locked'] = false; $this->status['scheduled_at'] = T\MongoDate::from($at); @@ -104,14 +107,20 @@ public function scheduleAt(Moment $at) return $this; } - public function withUrn(string $urn) + /** + * @return $this + */ + public function withUrn(string $urn): static { $this->status['urn'] = $urn; return $this; } - public function scheduledBy(string $namespace, string $id, int $executions) + /** + * @return $this + */ + public function scheduledBy(string $namespace, string $id, int $executions): static { $this->status['scheduled'] = [ 'by' => [ @@ -132,7 +141,7 @@ public function methodToCallOnWorkable($method): void $this->status['workable']['method'] = $method; } - public function execute(EventDispatcherInterface $eventDispatcher) + public function execute(EventDispatcherInterface $eventDispatcher): JobExecution { $methodToCall = $this->status['workable']['method']; try { @@ -148,7 +157,7 @@ public function execute(EventDispatcherInterface $eventDispatcher) return $this->lastJobExecution; } - public function retryStatistics() + public function retryStatistics(): array { return [ 'job_id' => (string) $this->id(), diff --git a/src/Recruiter/Job/Repository.php b/src/Recruiter/Job/Repository.php index ef3dc499..d99fbe41 100644 --- a/src/Recruiter/Job/Repository.php +++ b/src/Recruiter/Job/Repository.php @@ -4,13 +4,15 @@ use MongoDB; use MongoDB\BSON\ObjectId; +use MongoDB\Collection; +use MongoDB\Driver\CursorInterface; use Recruiter\Job; use Timeless as T; class Repository { - private $scheduled; - private $archived; + private Collection $scheduled; + private Collection $archived; public function __construct(MongoDB\Database $db) { @@ -18,7 +20,7 @@ public function __construct(MongoDB\Database $db) $this->archived = $db->selectCollection('archived'); } - public function all() + public function all(): array { return $this->map( $this->scheduled->find([], [ @@ -27,14 +29,14 @@ public function all() ); } - public function archiveAll() + public function archiveAll(): void { foreach ($this->all() as $job) { $this->archive($job); } } - public function scheduled($id) + public function scheduled(string|ObjectId $id): Job { if (is_string($id)) { $id = new ObjectId($id); @@ -49,7 +51,7 @@ public function scheduled($id) return $found[0]; } - public function archived($id) + public function archived(string|ObjectId $id): Job { if (is_string($id)) { $id = new ObjectId($id); @@ -64,7 +66,7 @@ public function archived($id) return $found[0]; } - public function save(Job $job) + public function save(Job $job): void { $document = $job->export(); $this->scheduled->replaceOne( @@ -74,14 +76,14 @@ public function save(Job $job) ); } - public function archive(Job $job) + public function archive(Job $job): void { $document = $job->export(); $this->scheduled->deleteOne(['_id' => $document['_id']]); $this->archived->replaceOne(['_id' => $document['_id']], $document, ['upsert' => true]); } - public function releaseAll($jobIds) + public function releaseAll($jobIds): int { $result = $this->scheduled->updateMany( ['_id' => ['$in' => $jobIds]], @@ -93,10 +95,10 @@ public function releaseAll($jobIds) public function countArchived(): int { - return $this->archived->count(); + return $this->archived->countDocuments(); } - public function cleanArchived(T\Moment $upperLimit) + public function cleanArchived(T\Moment $upperLimit): int { $documents = $this->archived->find( [ @@ -116,7 +118,7 @@ public function cleanArchived(T\Moment $upperLimit) return $deleted; } - public function cleanScheduled(T\Moment $upperLimit) + public function cleanScheduled(T\Moment $upperLimit): int { $result = $this->scheduled->deleteMany([ 'created_at' => [ @@ -132,7 +134,7 @@ public function queued( ?T\Moment $at = null, ?T\Moment $from = null, array $query = [], - ) { + ): int { if (null === $at) { $at = T\now(); } @@ -150,7 +152,7 @@ public function queued( return $this->scheduled->count($query); } - public function postponed($group = null, ?T\Moment $at = null, array $query = []) + public function postponed($group = null, ?T\Moment $at = null, array $query = []): int { if (null === $at) { $at = T\now(); @@ -162,19 +164,19 @@ public function postponed($group = null, ?T\Moment $at = null, array $query = [] $query['group'] = $group; } - return $this->scheduled->count($query); + return $this->scheduled->countDocuments($query); } - public function scheduledCount($group = null, array $query = []) + public function scheduledCount($group = null, array $query = []): int { if (null !== $group) { $query['group'] = $group; } - return $this->scheduled->count($query); + return $this->scheduled->countDocuments($query); } - public function queuedGroupedBy($field, array $query = [], $group = null) + public function queuedGroupedBy($field, array $query = [], $group = null): array { $query['scheduled_at']['$lte'] = T\MongoDate::from(T\now()); if (null !== $group) { @@ -197,7 +199,7 @@ public function queuedGroupedBy($field, array $query = [], $group = null) return $distinctAndCount; } - public function recentHistory($group = null, ?T\Moment $at = null, array $query = []) + public function recentHistory($group = null, ?T\Moment $at = null, array $query = []): array { if (null === $at) { $at = T\now(); @@ -305,7 +307,7 @@ public function countDelayedScheduledJobs(T\Moment $lowerLimit): int ]); } - public function delayedScheduledJobs(T\Moment $lowerLimit) + public function delayedScheduledJobs(T\Moment $lowerLimit): array { return $this->map( $this->scheduled->find([ @@ -319,7 +321,7 @@ public function delayedScheduledJobs(T\Moment $lowerLimit) public function recentJobsWithManyAttempts( T\Moment $lowerLimit, T\Moment $upperLimit, - ) { + ): array { $archived = $this->map( $this->recentArchivedOrScheduledJobsWithManyAttempts( $lowerLimit, @@ -342,7 +344,7 @@ public function slowRecentJobs( T\Moment $lowerLimit, T\Moment $upperLimit, $secondsToConsiderJobAsSlow = 5, - ) { + ): array { $archived = []; $archivedArray = $this->slowArchivedRecentJobs( $lowerLimit, @@ -369,7 +371,7 @@ private function slowArchivedRecentJobs( T\Moment $lowerLimit, T\Moment $upperLimit, $secondsToConsiderJobAsSlow, - ) { + ): array { return $this->archived->aggregate([ [ '$match' => [ @@ -413,7 +415,7 @@ private function slowScheduledRecentJobs( T\Moment $lowerLimit, T\Moment $upperLimit, $secondsToConsiderJobAsSlow, - ) { + ): array { return $this->scheduled->aggregate([ [ '$match' => [ @@ -464,7 +466,7 @@ private function countRecentArchivedOrScheduledJobsWithManyAttempts( T\Moment $lowerLimit, T\Moment $upperLimit, $collectionName, - ) { + ): int { return count($this->recentArchivedOrScheduledJobsWithManyAttempts( $lowerLimit, $upperLimit, @@ -488,7 +490,10 @@ private function recentArchivedOrScheduledJobsWithManyAttempts( ]); } - private function map($cursor) + /** + * @return array + */ + private function map(CursorInterface $cursor): array { $jobs = []; foreach ($cursor as $document) { From ab26e18b7677df9d424d50fa4beab47eaa923951 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Sun, 3 Aug 2025 23:55:36 +0200 Subject: [PATCH 43/59] Bump dependencies --- composer.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 1fd7cb23..1b7cb6eb 100644 --- a/composer.json +++ b/composer.json @@ -35,8 +35,8 @@ "mongodb/mongodb": "^2.1", "monolog/monolog": "^3.9", "psr/log": "^3.0", - "recruiterphp/concurrency": "^4.0", - "recruiterphp/geezer": "^6.0", + "recruiterphp/concurrency": "^5.0", + "recruiterphp/geezer": "^7.0", "symfony/console": "^7.3", "symfony/event-dispatcher": "^7.3", "ulrichsg/getopt-php": "^4.0" From 2726f1e35b53ac4d4c5502f975df8b71e4572135 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Mon, 4 Aug 2025 00:15:35 +0200 Subject: [PATCH 44/59] Implement minor improvements --- spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php | 4 ++-- spec/Recruiter/Acceptance/FaultToleranceTest.php | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php b/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php index 11ec14dd..244b10ac 100644 --- a/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php +++ b/spec/Recruiter/Acceptance/BaseAcceptanceTestCase.php @@ -166,7 +166,7 @@ protected function startWorker(array $additionalOptions = []) return end($this->processWorkers); } - protected function stopProcessWithSignal(array $processAndPipes, $signal): void + protected function stopProcessWithSignal(array $processAndPipes, int $signal): void { [$process, $pipes, $name] = $processAndPipes; proc_terminate($process, $signal); @@ -183,7 +183,7 @@ protected function stopProcessWithSignal(array $processAndPipes, $signal): void /** * @param int $duration milliseconds */ - protected function enqueueJob($duration = 10, $tag = 'generic'): void + protected function enqueueJob(int $duration = 10, $tag = 'generic'): void { $workable = ShellCommand::fromCommandLine('sleep ' . ($duration / 1000)); $workable diff --git a/spec/Recruiter/Acceptance/FaultToleranceTest.php b/spec/Recruiter/Acceptance/FaultToleranceTest.php index 6c1140c7..89096f06 100644 --- a/spec/Recruiter/Acceptance/FaultToleranceTest.php +++ b/spec/Recruiter/Acceptance/FaultToleranceTest.php @@ -81,7 +81,7 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDies(): void $worker = $this->startWorker(); $this->waitForNumberOfWorkersToBe(1); [$assignments, $_] = $this->recruiter->assignJobsToWorkers(); - $this->assertEquals(1, count($assignments)); + $this->assertCount(1, $assignments); sleep(2); // The worker is dead and the job is not properly scheduled $this->recruiter->retireDeadWorkers(new \DateTimeImmutable(), T\seconds(0)); @@ -92,7 +92,7 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDies(): void $this->waitForNumberOfWorkersToBe(1); // Here the job is assigned and rescheduled by the retry policy because found crashed [$assignments, $_] = $this->recruiter->assignJobsToWorkers(); - $this->assertEquals(1, count($assignments)); + $this->assertCount(1, $assignments); sleep(2); // The worker is dead and the job is not properly scheduled $this->recruiter->retireDeadWorkers(new \DateTimeImmutable(), T\seconds(0)); @@ -112,10 +112,10 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDies(): void $this->assertEquals(0, $this->recruiter->queued()); } - private function assertJobIsMarkedAsCrashed() + private function assertJobIsMarkedAsCrashed(): void { $jobs = iterator_to_array($this->recruiterDb->selectCollection('scheduled')->find()); - $this->assertEquals(1, count($jobs)); + $this->assertCount(1, $jobs); foreach ($jobs as $job) { $this->assertArrayHasKey('last_execution', $job); $this->assertArrayHasKey('crashed', $job['last_execution']); From 28a1b6d0fe0c9459c5011ea77ee7b644506f0d87 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Mon, 4 Aug 2025 00:16:26 +0200 Subject: [PATCH 45/59] Add var dumper --- composer.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/composer.json b/composer.json index 1b7cb6eb..3953b885 100644 --- a/composer.json +++ b/composer.json @@ -49,7 +49,8 @@ "giorgiosironi/eris": "^1.0", "phpstan/phpstan": "*", "phpunit/phpunit": "^10.0", - "rector/rector": "^2.1" + "rector/rector": "^2.1", + "symfony/var-dumper": "^7.3" }, "suggest": { "symfony/console": "In order to use Recruiter\\Command\\RecruiterJobCommand." From ec0a47033ae2da04c8e59c0700b339fed95ed5d1 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Mon, 4 Aug 2025 00:20:45 +0200 Subject: [PATCH 46/59] Fixed the main Recruiter issue --- examples/bootstrap.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/bootstrap.php b/examples/bootstrap.php index bbc2269c..1911e47f 100644 --- a/examples/bootstrap.php +++ b/examples/bootstrap.php @@ -4,7 +4,7 @@ echo 'BOOTSTRAP!!!' . PHP_EOL; -global $recruiter; +assert(isset($recruiter)); assert($recruiter instanceof Recruiter); $recruiter->getEventDispatcher()->addListener('job.failure.last', function ($event): void { From 529ca764a1a2b614c4d21db60197bd37ad3f7392 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Mon, 4 Aug 2025 00:23:29 +0200 Subject: [PATCH 47/59] Remove risky warning --- spec/Recruiter/RetryPolicy/SelectByExceptionTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php b/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php index 9222c9f9..26ef5e9d 100644 --- a/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php +++ b/spec/Recruiter/RetryPolicy/SelectByExceptionTest.php @@ -5,7 +5,6 @@ use PHPUnit\Framework\MockObject\MockObject; use PHPUnit\Framework\TestCase; use Recruiter\JobAfterFailure; -use Recruiter\RetryPolicy; use Timeless as T; class SelectByExceptionTest extends TestCase @@ -17,6 +16,8 @@ public function testCanBeBuilt(): void ->when(\LogicException::class)->then(new DoNotDoItAgain()) ->build() ; + + $this->expectNotToPerformAssertions(); } public function testCanBeExportedAndImported(): void From 7911175f9ec300887867a3c1f38d9ff3a3e90f63 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Mon, 4 Aug 2025 00:37:13 +0200 Subject: [PATCH 48/59] Fix the issue with the exit But tests are still flaky --- spec/Recruiter/Acceptance/FaultToleranceTest.php | 4 ++-- ...rGuaranteedToExitWithFailureCodeInCaseOfExceptionTest.php | 4 ++-- .../Workable/{ThrowsFatalError.php => ExitsAbruptly.php} | 5 ++--- 3 files changed, 6 insertions(+), 7 deletions(-) rename src/Recruiter/Workable/{ThrowsFatalError.php => ExitsAbruptly.php} (58%) diff --git a/spec/Recruiter/Acceptance/FaultToleranceTest.php b/spec/Recruiter/Acceptance/FaultToleranceTest.php index 89096f06..15d5befe 100644 --- a/spec/Recruiter/Acceptance/FaultToleranceTest.php +++ b/spec/Recruiter/Acceptance/FaultToleranceTest.php @@ -5,7 +5,7 @@ use Recruiter\Infrastructure\Memory\MemoryLimit; use Recruiter\RetryPolicy\RetryManyTimes; use Recruiter\Workable\FailsInConstructor; -use Recruiter\Workable\ThrowsFatalError; +use Recruiter\Workable\ExitsAbruptly; use Timeless as T; class FaultToleranceTest extends BaseAcceptanceTestCase @@ -63,7 +63,7 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDies(): void // executions. The problem is that the retry policy is // evaluated after the execution but fatal errors are not // catchable and so the job will stay scheduled forever - new ThrowsFatalError() + new ExitsAbruptly() ->asJobOf($this->recruiter) ->inBackground() ->retryWithPolicy(RetryManyTimes::forTimes(1, 0)) diff --git a/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest.php b/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest.php index 5070e67b..e683558b 100644 --- a/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest.php +++ b/spec/Recruiter/Acceptance/WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest.php @@ -2,7 +2,7 @@ namespace Recruiter\Acceptance; -use Recruiter\Workable\ThrowsFatalError; +use Recruiter\Workable\ExitsAbruptly; class WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest extends BaseAcceptanceTestCase { @@ -11,7 +11,7 @@ class WorkerGuaranteedToExitWithFailureCodeInCaseOfExceptionTest extends BaseAcc */ public function testInCaseOfExceptionTheExitCodeOfWorkerProcessIsNotZero(): void { - new ThrowsFatalError() + new ExitsAbruptly() ->asJobOf($this->recruiter) ->inBackground() ->execute() diff --git a/src/Recruiter/Workable/ThrowsFatalError.php b/src/Recruiter/Workable/ExitsAbruptly.php similarity index 58% rename from src/Recruiter/Workable/ThrowsFatalError.php rename to src/Recruiter/Workable/ExitsAbruptly.php index e86b0996..dd3e5876 100644 --- a/src/Recruiter/Workable/ThrowsFatalError.php +++ b/src/Recruiter/Workable/ExitsAbruptly.php @@ -5,13 +5,12 @@ use Recruiter\Workable; use Recruiter\WorkableBehaviour; -class ThrowsFatalError implements Workable +class ExitsAbruptly implements Workable { use WorkableBehaviour; public function execute(): void { - /** @phpstan-ignore-next-line */ - new ThisClassDoesnNotExists(); + exit(-1); } } From dc95a28176f38564c0a7ac54054e939c731ba38f Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Mon, 4 Aug 2025 00:37:27 +0200 Subject: [PATCH 49/59] Improve assertions --- spec/Recruiter/Acceptance/FaultToleranceTest.php | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/spec/Recruiter/Acceptance/FaultToleranceTest.php b/spec/Recruiter/Acceptance/FaultToleranceTest.php index 15d5befe..be41fb5a 100644 --- a/spec/Recruiter/Acceptance/FaultToleranceTest.php +++ b/spec/Recruiter/Acceptance/FaultToleranceTest.php @@ -18,7 +18,7 @@ public function testRecruiterCrashAfterLockingJobsBeforeAssignmentAndIsRestarted $this->recruiter->bookJobsForWorkers(); $this->recruiter->rollbackLockedJobs(); [$assignments, $totalNumber] = $this->recruiter->assignJobsToWorkers(); - $this->assertEquals(1, count($assignments)); + $this->assertCount(1, $assignments); $this->assertEquals(1, $totalNumber); } @@ -35,7 +35,7 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDiesInConstructor(): v $this->waitForNumberOfWorkersToBe(1); [$assignments, $_] = $this->recruiter->assignJobsToWorkers(); - $this->assertEquals(1, count($assignments)); + $this->assertCount(1, $assignments); sleep(2); $jobDocument = current($this->scheduled->find()->toArray()); $this->assertEquals(1, $jobDocument['attempts']); @@ -44,7 +44,7 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDiesInConstructor(): v $this->assertStringContainsString('I am supposed to fail in constructor code for testing purpose', $jobDocument['last_execution']['message']); [$assignments, $_] = $this->recruiter->assignJobsToWorkers(); - $this->assertEquals(1, count($assignments)); + $this->assertCount(1, $assignments); sleep(2); $jobDocument = current($this->archived->find()->toArray()); $this->assertEquals(2, $jobDocument['attempts']); @@ -53,7 +53,7 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDiesInConstructor(): v $this->assertStringContainsString('I am supposed to fail in constructor code for testing purpose', $jobDocument['last_execution']['message']); [$assignments, $_] = $this->recruiter->assignJobsToWorkers(); - $this->assertEquals(0, count($assignments)); + $this->assertCount(0, $assignments); } public function testRetryPolicyMustBeAppliedEvenWhenWorkerDies(): void @@ -106,7 +106,7 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDies(): void // because found crashed and because it has been already // executed 2 times [$assignments, $_] = $this->recruiter->assignJobsToWorkers(); - $this->assertEquals(1, count($assignments)); + $this->assertCount(1, $assignments); sleep(1); // The worker is not dead and the job is not scheduled anymore $this->assertEquals(0, $this->recruiter->queued()); From c4431e7f6ad926df6919416c4506a7d6b75f9660 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Mon, 4 Aug 2025 00:38:58 +0200 Subject: [PATCH 50/59] Increase waiting time --- spec/Recruiter/Acceptance/FaultToleranceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Recruiter/Acceptance/FaultToleranceTest.php b/spec/Recruiter/Acceptance/FaultToleranceTest.php index be41fb5a..066d015e 100644 --- a/spec/Recruiter/Acceptance/FaultToleranceTest.php +++ b/spec/Recruiter/Acceptance/FaultToleranceTest.php @@ -96,7 +96,7 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDies(): void sleep(2); // The worker is dead and the job is not properly scheduled $this->recruiter->retireDeadWorkers(new \DateTimeImmutable(), T\seconds(0)); - $this->waitForNumberOfWorkersToBe(0); + $this->waitForNumberOfWorkersToBe(0, 2); $this->assertJobIsMarkedAsCrashed(); // Third execution of the job From 2472cca244e93aa19ae520c3930d3feb9aa7acd2 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Mon, 4 Aug 2025 09:22:38 +0200 Subject: [PATCH 51/59] Increase timeout --- spec/Recruiter/Acceptance/FaultToleranceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Recruiter/Acceptance/FaultToleranceTest.php b/spec/Recruiter/Acceptance/FaultToleranceTest.php index 066d015e..57b66207 100644 --- a/spec/Recruiter/Acceptance/FaultToleranceTest.php +++ b/spec/Recruiter/Acceptance/FaultToleranceTest.php @@ -32,7 +32,7 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDiesInConstructor(): v ; $worker = $this->startWorker(); - $this->waitForNumberOfWorkersToBe(1); + $this->waitForNumberOfWorkersToBe(1, 2); [$assignments, $_] = $this->recruiter->assignJobsToWorkers(); $this->assertCount(1, $assignments); From 183833a8a6b252d91b51e98c13f4fad7b37df9d2 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Mon, 4 Aug 2025 09:29:15 +0200 Subject: [PATCH 52/59] Further increase timeout for pipeline --- spec/Recruiter/Acceptance/FaultToleranceTest.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/spec/Recruiter/Acceptance/FaultToleranceTest.php b/spec/Recruiter/Acceptance/FaultToleranceTest.php index 57b66207..0faeb0b8 100644 --- a/spec/Recruiter/Acceptance/FaultToleranceTest.php +++ b/spec/Recruiter/Acceptance/FaultToleranceTest.php @@ -32,7 +32,7 @@ public function testRetryPolicyMustBeAppliedEvenWhenWorkerDiesInConstructor(): v ; $worker = $this->startWorker(); - $this->waitForNumberOfWorkersToBe(1, 2); + $this->waitForNumberOfWorkersToBe(1, 5); [$assignments, $_] = $this->recruiter->assignJobsToWorkers(); $this->assertCount(1, $assignments); From 0e4fc2ff101aa8f10875d611b2a78afba7dc8727 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Mon, 4 Aug 2025 10:54:01 +0200 Subject: [PATCH 53/59] Enhance CI pipeline --- .github/workflows/ci.yml | 70 +++++++++++++++++++++++++++++++++++ .github/workflows/phpunit.yml | 41 -------------------- 2 files changed, 70 insertions(+), 41 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 .github/workflows/phpunit.yml diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..da778eea --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,70 @@ +name: CI Pipeline + +on: + push: + branches: [ "master" ] + pull_request: + branches: [ "master" ] + +permissions: + contents: read + +jobs: + setup: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Validate composer.json and composer.lock + run: composer validate --strict + + - name: Cache Composer packages + id: composer-cache + uses: actions/cache@v3 + with: + path: vendor + key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} + restore-keys: | + ${{ runner.os }}-php- + + - name: Build with cache + run: | + docker buildx create --use + DOCKER_BUILDKIT=1 docker compose build --build-arg BUILDKIT_INLINE_CACHE=1 + + - name: Install dependencies + run: make install + + test-fast: + needs: setup + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Restore cache and build + run: | + docker buildx create --use + DOCKER_BUILDKIT=1 docker compose build --build-arg BUILDKIT_INLINE_CACHE=1 + make install + - name: Run fast tests + run: make test + + test-long: + needs: setup + runs-on: ubuntu-latest + timeout-minutes: 90 + steps: + - uses: actions/checkout@v4 + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + - name: Restore cache and build + run: | + docker buildx create --use + DOCKER_BUILDKIT=1 docker compose build --build-arg BUILDKIT_INLINE_CACHE=1 + make install + - name: Run long tests + run: make test-long diff --git a/.github/workflows/phpunit.yml b/.github/workflows/phpunit.yml deleted file mode 100644 index 9619b698..00000000 --- a/.github/workflows/phpunit.yml +++ /dev/null @@ -1,41 +0,0 @@ -name: PHPUnit Tests - -on: - push: - branches: [ "master" ] - pull_request: - branches: [ "master" ] - -permissions: - contents: read - -jobs: - build: - runs-on: ubuntu-latest - - steps: - - uses: actions/checkout@v4 - - - name: Validate composer.json and composer.lock - run: composer validate --strict - - - name: Cache Composer packages - id: composer-cache - uses: actions/cache@v3 - with: - path: vendor - key: ${{ runner.os }}-php-${{ hashFiles('**/composer.lock') }} - restore-keys: | - ${{ runner.os }}-php- - - - name: Build the Docker image - run: make build - - - name: Install dependencies - run: make install - - - name: Run test suite (except long ones) - run: make test - - - name: Run test suite (only long ones) - run: make test-long From ba121ebb246482ee334587cf2da03cae79034480 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Mon, 4 Aug 2025 12:15:14 +0200 Subject: [PATCH 54/59] Make long tests optional --- .github/workflows/ci.yml | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index da778eea..5262d402 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,13 @@ on: branches: [ "master" ] pull_request: branches: [ "master" ] + workflow_dispatch: + inputs: + run_long_tests: + description: 'Run long tests' + required: false + default: false + type: boolean permissions: contents: read @@ -57,6 +64,7 @@ jobs: needs: setup runs-on: ubuntu-latest timeout-minutes: 90 + if: github.event.inputs.run_long_tests == 'true' steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx From 914b265363d4d7ca621d49df2c5414e42eed6451 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Mon, 4 Aug 2025 12:26:18 +0200 Subject: [PATCH 55/59] Use multistage build --- Dockerfile | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Dockerfile b/Dockerfile index 1904a3f9..2f486df5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,4 +1,4 @@ -FROM php:8.4-cli +FROM php:8.4-cli AS base # Install system dependencies RUN apt-get update && apt-get install -y \ @@ -24,6 +24,11 @@ COPY --from=composer:latest /usr/bin/composer /usr/bin/composer # Set working directory WORKDIR /app +FROM base AS code + +# Set environment variable for Composer +ENV COMPOSER_ALLOW_SUPERUSER=1 + # Copy composer files COPY composer.json composer.lock* ./ @@ -33,7 +38,4 @@ RUN composer install --optimize-autoloader # Copy application code COPY . . -# Set environment variable for Composer -ENV COMPOSER_ALLOW_SUPERUSER=1 - CMD ["tail", "-f", "/dev/null"] From e5818ec1a738b28263351dffae26f8381dd878d4 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Mon, 4 Aug 2025 12:30:08 +0200 Subject: [PATCH 56/59] Improve the build process --- .github/workflows/ci.yml | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 5262d402..3d43092a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -40,7 +40,12 @@ jobs: - name: Build with cache run: | docker buildx create --use - DOCKER_BUILDKIT=1 docker compose build --build-arg BUILDKIT_INLINE_CACHE=1 + docker buildx build \ + --cache-from type=gha \ + --cache-to type=gha,mode=max \ + --load \ + --tag recruiter-php \ + . - name: Install dependencies run: make install @@ -55,7 +60,11 @@ jobs: - name: Restore cache and build run: | docker buildx create --use - DOCKER_BUILDKIT=1 docker compose build --build-arg BUILDKIT_INLINE_CACHE=1 + docker buildx build \ + --cache-from type=gha \ + --load \ + --tag recruiter-php \ + . make install - name: Run fast tests run: make test @@ -72,7 +81,11 @@ jobs: - name: Restore cache and build run: | docker buildx create --use - DOCKER_BUILDKIT=1 docker compose build --build-arg BUILDKIT_INLINE_CACHE=1 + docker buildx build \ + --cache-from type=gha \ + --load \ + --tag recruiter-php \ + . make install - name: Run long tests run: make test-long From 45633c5c3bcaeeee7bfe10e9d549bc4667a130c1 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Mon, 4 Aug 2025 13:02:19 +0200 Subject: [PATCH 57/59] Dependencies are already in build --- .github/workflows/ci.yml | 5 ----- 1 file changed, 5 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 3d43092a..dc89e62a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -47,9 +47,6 @@ jobs: --tag recruiter-php \ . - - name: Install dependencies - run: make install - test-fast: needs: setup runs-on: ubuntu-latest @@ -65,7 +62,6 @@ jobs: --load \ --tag recruiter-php \ . - make install - name: Run fast tests run: make test @@ -86,6 +82,5 @@ jobs: --load \ --tag recruiter-php \ . - make install - name: Run long tests run: make test-long From 4070bafe7c50ad01a62af70572815ae06959c71a Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Mon, 4 Aug 2025 21:58:00 +0200 Subject: [PATCH 58/59] Move volumes to ovverride --- .env | 1 + compose.dev.yaml | 4 ++++ compose.yaml | 2 -- 3 files changed, 5 insertions(+), 2 deletions(-) create mode 100644 .env create mode 100644 compose.dev.yaml diff --git a/.env b/.env new file mode 100644 index 00000000..949df516 --- /dev/null +++ b/.env @@ -0,0 +1 @@ +COMPOSE_FILE=compose.yaml:compose.dev.yaml diff --git a/compose.dev.yaml b/compose.dev.yaml new file mode 100644 index 00000000..e985c6b9 --- /dev/null +++ b/compose.dev.yaml @@ -0,0 +1,4 @@ +services: + php: + volumes: + - .:/app diff --git a/compose.yaml b/compose.yaml index 61c32cee..e0724f21 100644 --- a/compose.yaml +++ b/compose.yaml @@ -2,8 +2,6 @@ services: php: build: . working_dir: /app - volumes: - - .:/app environment: - COMPOSER_ALLOW_SUPERUSER=1 - MONGODB_URI=mongodb://mongodb:27017/recruiter From 18b1d3e9d3adeb251ec4537846588a89d9e27940 Mon Sep 17 00:00:00 2001 From: Davide Bellettini <325358+dbellettini@users.noreply.github.com> Date: Mon, 4 Aug 2025 22:33:46 +0200 Subject: [PATCH 59/59] Add env to GitHub Actions --- .github/workflows/ci.yml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index dc89e62a..68c86bf2 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -50,6 +50,8 @@ jobs: test-fast: needs: setup runs-on: ubuntu-latest + env: + COMPOSE_FILE: compose.yaml steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx @@ -70,6 +72,8 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 90 if: github.event.inputs.run_long_tests == 'true' + env: + COMPOSE_FILE: compose.yaml steps: - uses: actions/checkout@v4 - name: Set up Docker Buildx