diff --git a/.docker/php/cli/Dockerfile b/.docker/php/cli/Dockerfile index 54eaba1..5410cb6 100644 --- a/.docker/php/cli/Dockerfile +++ b/.docker/php/cli/Dockerfile @@ -26,14 +26,14 @@ ENV COMPOSER_CACHE_DIR=/var/.composer FROM app_setup AS app_worker -RUN install-php-extensions opcache igbinary +RUN install-php-extensions igbinary USER $UID WORKDIR $WORKDIR FROM app_setup AS app_devel -RUN install-php-extensions opcache igbinary xdebug +RUN install-php-extensions igbinary xdebug USER $UID WORKDIR $WORKDIR diff --git a/.dockerignore b/.dockerignore index 918f3ab..fbc5149 100644 --- a/.dockerignore +++ b/.dockerignore @@ -1,3 +1,5 @@ .git .github .docker +.idea +.zed diff --git a/.gitattributes b/.gitattributes index 33245e5..c5581e2 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,11 +1,13 @@ -/.* export-ignore -/benchmark/ export-ignore -/tests/ export-ignore -/runtime/ export-ignore -/*.neon export-ignore -/*.xml export-ignore -/*.xml.dist export-ignore -/*.lock export-ignore -docker-compose.yml export-ignore -Makefile export-ignore -rector.php export-ignore +text=auto eol=lf +*.phar binary +*.php diff=php + +* export-ignore +/src -export-ignore +/src/** -export-ignore +/example -export-ignore +/example/** -export-ignore +/composer.json -export-ignore +/CHANGELOG.md -export-ignore +/LICENSE -export-ignore +/README.md -export-ignore diff --git a/.github/workflows/code-check.yml b/.github/workflows/code-check.yml index 86d8f7d..b077250 100644 --- a/.github/workflows/code-check.yml +++ b/.github/workflows/code-check.yml @@ -21,9 +21,6 @@ jobs: with: php-version: ${{ matrix.php-version }} coverage: none - env: - fail-fast: true - COMPOSER_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Install composer dependencies uses: ramsey/composer-install@v3 @@ -35,7 +32,7 @@ jobs: run: vendor/bin/phpcs - name: Psalm - run: vendor/bin/psalm --stats --shepherd --output-format=github + run: vendor/bin/psalm --php-version=${{ matrix.php-version }} --stats --show-info --no-diff --no-cache - name: PHPStan run: vendor/bin/phpstan analyse diff --git a/Makefile b/Makefile index 6e52711..5d53410 100644 --- a/Makefile +++ b/Makefile @@ -29,6 +29,10 @@ check: composer:latest \ composer check +fix: + - make phpcbf + - make rector + psalm: ## psalm docker run --init -it --rm -u ${USER} -v "$$(pwd):/app" -w /app \ ghcr.io/kuaukutsu/php:${PHP_VERSION}-cli \ @@ -84,7 +88,7 @@ publisher: consumer: USER=$(USER) docker compose -f ./docker-compose.yml run --rm -u $(USER) -w /tests/simulation cli \ - php worker-with-catch.php --schema=high + php worker.php --schema=high bench: ## bench USER=$(USER) docker compose -f ./docker-compose.yml run --rm -u $(USER) -w / \ diff --git a/composer.json b/composer.json index e2c994e..8af96be 100644 --- a/composer.json +++ b/composer.json @@ -18,8 +18,7 @@ "php": "^8.3", "ext-redis": "*", "amphp/redis": "^2.0", - "kuaukutsu/queue-core": "^0.5.3", - "symfony/console": "^7.3" + "kuaukutsu/queue-core": "^0.5.3" }, "require-dev": { "ext-pcntl": "*", @@ -31,6 +30,7 @@ "roave/security-advisories": "dev-latest", "slevomat/coding-standard": "^8.7", "squizlabs/php_codesniffer": "^3.7", + "symfony/console": "^7.3", "vimeo/psalm": "^6.13" }, "autoload": { @@ -47,9 +47,6 @@ ] }, "config": { - "platform": { - "php": "8.3.21" - }, "allow-plugins": { "dealerdirect/phpcodesniffer-composer-installer": true }, diff --git a/rector.php b/rector.php index a8c874d..a04510e 100644 --- a/rector.php +++ b/rector.php @@ -1,33 +1,32 @@ paths( +return Rector\Config\RectorConfig::configure() + ->withPaths( [ __DIR__ . '/src', __DIR__ . '/tests', ] - ); - - // register a single rule - $rectorConfig->rule(InlineConstructorDefaultToPropertyRector::class); - $rectorConfig->ruleWithConfiguration( + ) + ->withParallel() + ->withCache('/tmp/var/rector') + ->withPhpSets() + ->withRules( + [ + InlineConstructorDefaultToPropertyRector::class, + ] + ) + ->withConfiguredRule( AddOverrideAttributeToOverriddenMethodsRector::class, [ 'allow_override_empty_method' => true, ] ); - - // define sets of rules - $rectorConfig->sets([ - SetList::PHP_81, - SetList::PHP_82, - SetList::PHP_83, - ]); -}; diff --git a/src/Consumer.php b/src/Consumer.php index c3abf39..e536684 100644 --- a/src/Consumer.php +++ b/src/Consumer.php @@ -9,7 +9,6 @@ use Throwable; use Amp\Redis\RedisClient; use Revolt\EventLoop; -use kuaukutsu\queue\core\exception\QueueConsumeException; use kuaukutsu\queue\core\handler\HandlerInterface; use kuaukutsu\queue\core\ConsumerInterface; use kuaukutsu\queue\core\SchemaInterface; @@ -44,9 +43,6 @@ public function __construct( $this->handler = new TaskHandler($handler, $catch); } - /** - * @throws QueueConsumeException - */ #[Override] public function consume(SchemaInterface $schema): void { diff --git a/src/Publisher.php b/src/Publisher.php index 0c19ef5..1e5763d 100644 --- a/src/Publisher.php +++ b/src/Publisher.php @@ -29,10 +29,6 @@ public function __construct(private RedisClient $redis) { } - /** - * @return non-empty-string - * @throws QueuePublishException - */ #[Override] public function push(SchemaInterface $schema, QueueTask $task, ?QueueContext $context = null): string { diff --git a/src/internal/Context.php b/src/internal/Context.php index 3e73f9c..24ff350 100644 --- a/src/internal/Context.php +++ b/src/internal/Context.php @@ -28,22 +28,18 @@ final class Context /** * @var non-empty-string[] */ - private array $identityList = []; + private array $ackList = []; /** * @var non-empty-string[] */ private array $payloadList = []; - /** - * @param non-negative-int $maxExceededAttempts - */ public function __construct( public readonly SchemaInterface $schema, private readonly RedisConsume $streamGroup, private readonly RedisString $string, private readonly EventDispatcher $eventDispatcher, - public readonly int $maxExceededAttempts = 3, ) { } @@ -91,16 +87,16 @@ static function () use ($fn, $name, $event): void { */ public function setAck(string $identity, string $payloadUuid): void { - $this->identityList[] = $identity; + $this->ackList[] = $identity; $this->payloadList[] = $payloadUuid; } public function sendAck(): void { - if ($this->identityList !== []) { - $this->streamGroup->ack($this->identityList[0], ...array_slice($this->identityList, 1)); - $this->trigger(Event::MessageAck, new MessageAckEvent($this->identityList)); - $this->identityList = []; + if ($this->ackList !== []) { + $this->streamGroup->ack($this->ackList[0], ...array_slice($this->ackList, 1)); + $this->trigger(Event::MessageAck, new MessageAckEvent($this->ackList)); + $this->ackList = []; } if ($this->payloadList !== []) { diff --git a/src/internal/stream/RedisConsume.php b/src/internal/stream/RedisConsume.php index c0b6958..8f94925 100644 --- a/src/internal/stream/RedisConsume.php +++ b/src/internal/stream/RedisConsume.php @@ -170,13 +170,13 @@ public function pending(string $identity): array 1, ); - if (is_array($result)) { + if (is_array($result) && is_array($result[0])) { return [ - /** - * @phpstan-ignore offsetAccess.nonOffsetAccessible,cast.string - */ + /** @phpstan-ignore cast.string */ 'consumer' => (string)($result[0][1] ?? $template['consumer']), + /** @phpstan-ignore cast.int */ 'elapsedMilliseconds' => (int)($result[0][2] ?? $template['elapsedMilliseconds']), + /** @phpstan-ignore cast.int */ 'deliveryCount' => (int)($result[0][3] ?? $template['deliveryCount']), ]; } diff --git a/src/internal/workflow/WorkflowCatch.php b/src/internal/workflow/WorkflowCatch.php index 98c4a26..ef686f9 100644 --- a/src/internal/workflow/WorkflowCatch.php +++ b/src/internal/workflow/WorkflowCatch.php @@ -22,8 +22,13 @@ */ final readonly class WorkflowCatch { - public function __construct(private RedisConsume $stream) - { + /** + * @param non-negative-int $maxExceededAttempts + */ + public function __construct( + private RedisConsume $stream, + private int $maxExceededAttempts = 3, + ) { } /** @@ -51,7 +56,7 @@ public function __invoke(Context $ctx, string $identity, Payload $payload, Throw return; } - if ($ctx->maxExceededAttempts === 0) { + if ($this->maxExceededAttempts === 0) { $ctx->trigger( Event::MessageHandleError, new MessageErrorEvent($payload, $exception), @@ -67,7 +72,7 @@ public function __invoke(Context $ctx, string $identity, Payload $payload, Throw new MessageErrorEvent($payload, $exception, $attempts), ); - if ($attempts >= $ctx->maxExceededAttempts) { + if ($attempts >= $this->maxExceededAttempts) { $this->dlq( $ctx, $identity, @@ -153,10 +158,9 @@ private function incrAttempt(Context $ctx, Payload $payload, int $currentAttempt private function attempts(RedisConsume $command, Context $ctx, string $identity): int { /** - * @param non-empty-string $identity - * @return positive-int + * @psalm-var Closure(RedisConsume, Context, non-empty-string): positive-int $fn */ - $fn = static function (RedisConsume $command, Context $ctx, string $identity): int { + static $fn = static function (RedisConsume $command, Context $ctx, string $identity): int { /** @var non-empty-string $identity */ try { $pending = $command->pending($identity); diff --git a/src/internal/workflow/WorkflowClaim.php b/src/internal/workflow/WorkflowClaim.php index 5ccfbd7..12a2632 100644 --- a/src/internal/workflow/WorkflowClaim.php +++ b/src/internal/workflow/WorkflowClaim.php @@ -4,6 +4,7 @@ namespace kuaukutsu\poc\queue\stream\internal\workflow; +use Closure; use Amp\CancelledException; use Amp\TimeoutCancellation; use kuaukutsu\poc\queue\stream\event\Event; @@ -29,7 +30,17 @@ public function __construct( public function __invoke(Context $ctx, WorkflowCatch $catch): void { $action = $this->action->run(...); - $workflow = static function (string $identity, Payload $payload) use ($action, $catch, $ctx): void { + + /** + * @psalm-var Closure(Context, string, Payload, callable, callable): void $workflow + */ + static $workflow = static function ( + Context $ctx, + string $identity, + Payload $payload, + callable $action, + callable $catch, + ): void { /** @var non-empty-string $identity */ if ($action($catch(...), $ctx, $identity, $payload)) { $ctx->setAck($identity, $payload->uuid); @@ -40,7 +51,7 @@ public function __invoke(Context $ctx, WorkflowCatch $catch): void while (true) { $list = []; foreach ($this->autoclaim($this->stream, $lastIdentity) as $identity => $payload) { - $list[] = async($workflow(...), $identity, $payload); + $list[] = async($workflow(...), $ctx, $identity, $payload, $action, $catch); $lastIdentity = $identity; } @@ -67,9 +78,10 @@ public function __invoke(Context $ctx, WorkflowCatch $catch): void private function autoclaim(RedisConsume $command, string $lastIdentity): iterable { /** - * @return iterable + * @psalm-var Closure(RedisConsume, string): iterable $fn + * @phpstan-ignore varTag.nativeType */ - $fn = static function (RedisConsume $command, string $lastIdentity): iterable { + static $fn = static function (RedisConsume $command, string $lastIdentity): iterable { $batch = $command->autoclaim($lastIdentity); if ($batch === []) { return; diff --git a/src/internal/workflow/WorkflowMain.php b/src/internal/workflow/WorkflowMain.php index 3b46c07..1f64dad 100644 --- a/src/internal/workflow/WorkflowMain.php +++ b/src/internal/workflow/WorkflowMain.php @@ -4,6 +4,7 @@ namespace kuaukutsu\poc\queue\stream\internal\workflow; +use Closure; use Amp\CancelledException; use Amp\TimeoutCancellation; use kuaukutsu\poc\queue\stream\event\Event; @@ -30,7 +31,17 @@ public function __invoke(Context $ctx, WorkflowClaim $claim, WorkflowCatch $catc { $lastAction = time(); $action = $this->action->run(...); - $workflow = static function (string $identity, Payload $payload) use ($action, $catch, $ctx): void { + + /** + * @psalm-var Closure(Context, string, Payload, callable, callable): void $workflow + */ + static $workflow = static function ( + Context $ctx, + string $identity, + Payload $payload, + callable $action, + callable $catch, + ): void { /** @var non-empty-string $identity */ if ($action($catch(...), $ctx, $identity, $payload)) { $ctx->setAck($identity, $payload->uuid); @@ -41,7 +52,7 @@ public function __invoke(Context $ctx, WorkflowClaim $claim, WorkflowCatch $catc while (true) { $list = []; foreach ($this->read($this->stream) as $identity => $payload) { - $list[] = async($workflow(...), $identity, $payload); + $list[] = async($workflow(...), $ctx, $identity, $payload, $action, $catch); } if ($list === []) { @@ -68,9 +79,10 @@ public function __invoke(Context $ctx, WorkflowClaim $claim, WorkflowCatch $catc private function read(RedisConsume $command): iterable { /** - * @return iterable + * @psalm-var Closure(RedisConsume):iterable $fn + * @phpstan-ignore varTag.nativeType */ - $fn = static function (RedisConsume $command): iterable { + static $fn = static function (RedisConsume $command): iterable { $batch = $command->read(); if ($batch === []) { return; diff --git a/tests/simulation/worker.php b/tests/simulation/worker.php index 0cf0150..76b7073 100644 --- a/tests/simulation/worker.php +++ b/tests/simulation/worker.php @@ -8,10 +8,10 @@ declare(strict_types=1); use Symfony\Component\Console\Output\ConsoleOutput; -use kuaukutsu\poc\queue\stream\tools\TraceConsoleOutput; use kuaukutsu\queue\core\interceptor\ArgumentsVerifyInterceptor; use kuaukutsu\poc\queue\stream\interceptor\ExactlyOnceInterceptor; use kuaukutsu\poc\queue\stream\tests\stub\QueueSchemaStub; +use kuaukutsu\poc\queue\stream\tests\stub\TraceConsoleOutput; use kuaukutsu\poc\queue\stream\Builder; use function Amp\Redis\createRedisClient; diff --git a/src/tools/NullConsoleOutput.php b/tests/stub/NullConsoleOutput.php similarity index 97% rename from src/tools/NullConsoleOutput.php rename to tests/stub/NullConsoleOutput.php index a72902d..ae26df8 100644 --- a/src/tools/NullConsoleOutput.php +++ b/tests/stub/NullConsoleOutput.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace kuaukutsu\poc\queue\stream\tools; +namespace kuaukutsu\poc\queue\stream\tests\stub; use LogicException; use Override; diff --git a/src/tools/TraceConsoleOutput.php b/tests/stub/TraceConsoleOutput.php similarity index 96% rename from src/tools/TraceConsoleOutput.php rename to tests/stub/TraceConsoleOutput.php index daa551f..aee5366 100644 --- a/src/tools/TraceConsoleOutput.php +++ b/tests/stub/TraceConsoleOutput.php @@ -2,7 +2,7 @@ declare(strict_types=1); -namespace kuaukutsu\poc\queue\stream\tools; +namespace kuaukutsu\poc\queue\stream\tests\stub; use Override; use Symfony\Component\Console\Output\ConsoleOutputInterface;