Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .devcontainer.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"dockerComposeFile": "compose.yaml",
"service": "php",
"workspaceFolder": "/workspace"
}
1 change: 1 addition & 0 deletions .env
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
# Put env variables defaults here
# Override locally in gitignored .env.local
PHP_IMAGE_VERSION=8.3
3 changes: 1 addition & 2 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -1,13 +1,12 @@
/.devcontainer/ export-ignore
/.github/ export-ignore
/docker/ export-ignore
/examples/ export-ignore
/stubs/ export-ignore
/tests/ export-ignore
/.env export-ignore
/.gitattributes export-ignore
/.gitignore export-ignore
/.php-cs-fixer.dist.php export-ignore
/compose.yaml export-ignore
/composer.lock export-ignore
/infection.json5.dist export-ignore
/Makefile export-ignore
Expand Down
10 changes: 8 additions & 2 deletions .github/workflows/check.yml → .github/workflows/check.yaml
Original file line number Diff line number Diff line change
@@ -1,9 +1,15 @@
name: Check

on:
workflow_dispatch: ~
push:
branches: [main, '*.*.x']
pull_request: ~

concurrency:
group: check-${{ github.ref }}
cancel-in-progress: true

jobs:
code-style:
runs-on: ubuntu-latest
Expand All @@ -26,14 +32,14 @@ jobs:
deps: [ lowest, highest ]
steps:
- uses: actions/checkout@v6
- run: PHP_VERSION=${{ matrix.php }} make install-${{ matrix.deps }} phpstan
- run: PHP_IMAGE_VERSION=${{ matrix.php }} make install-${{ matrix.deps }} phpstan

test:
runs-on: ubuntu-latest
strategy: *strategy
steps:
- uses: actions/checkout@v6
- run: PHP_VERSION=${{ matrix.php }} make install-${{ matrix.deps }} test
- run: PHP_IMAGE_VERSION=${{ matrix.php }} make install-${{ matrix.deps }} test

# infect:
# runs-on: ubuntu-latest
Expand Down
2 changes: 1 addition & 1 deletion .php-cs-fixer.dist.php
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
$config = (new Config())
->setFinder(
Finder::create()
->in(__DIR__ . '/examples')
->in(__DIR__ . '/src')
->in(__DIR__ . '/tests')
->in(__DIR__ . '/examples')
->append([
__FILE__,
]),
Expand Down
48 changes: 33 additions & 15 deletions Makefile
Original file line number Diff line number Diff line change
@@ -1,13 +1,10 @@
include .env
-include .env.local

SHELL ?= /bin/bash

DOCKER ?= docker
DOCKER_COMPOSE ?= $(DOCKER) compose
DOCKER_COMPOSE ?= $(DOCKER) compose $(shell test -f .env.local && echo '--env-file .env --env-file .env.local')
export CONTAINER_USER ?= $(shell id -u):$(shell id -g)
INSIDE_CONTAINER ?= $(shell test -f /.dockerenv && echo 1)

RUN ?= $(if $(INSIDE_CONTAINER),,$(DOCKER_COMPOSE) run --rm php)
RUN ?= $(if $(INSIDE_DEVCONTAINER),,$(DOCKER_COMPOSE) run --rm php)
COMPOSER ?= $(RUN) composer

##
Expand All @@ -17,31 +14,39 @@ COMPOSER ?= $(RUN) composer
var:
mkdir var

vendor: composer.json
if [ -f vendor/.lowest ]; then $(MAKE) install-lowest; else $(MAKE) install-highest; fi
vendor: composer.json $(wildcard composer.lock)
@if [ -f vendor/.lowest ]; then $(MAKE) install-lowest; else $(MAKE) install-highest; fi

i: install-highest
install-highest: ## Install highest Composer dependencies
$(COMPOSER) update $(ARGS)
$(COMPOSER) install
@rm -f vendor/.lowest
@touch vendor
.PHONY: i install-highest

install-lowest: ## Install lowest Composer dependencies
$(COMPOSER) update --prefer-lowest --prefer-stable $(ARGS)
$(COMPOSER) update --prefer-lowest --prefer-stable
@touch vendor/.lowest
@touch vendor
.PHONY: install-lowest

up: ## Docker compose up
$(DOCKER_COMPOSE) up --build --detach $(ARGS)
$(DOCKER_COMPOSE) up --remove-orphans --build --detach $(ARGS)
.PHONY: up

down: ## Docker compose down
$(DOCKER_COMPOSE) down --remove-orphans $(ARGS)
.PHONY: down

compose: ## Run docker compose command: `make compose CMD=start`
dc: docker-compose
docker-compose: ## Run docker compose command: `make dc CMD=start`
$(DOCKER_COMPOSE) $(CMD)
.PHONY: compose
.PHONY: dc docker-compose

c: composer
composer: ## Run Composer command: `make c CMD=start`
$(COMPOSER) $(CMD)
.PHONY: c composer

run: ## Run a command using the php container: `make run CMD='php --version'`
$(RUN) $(CMD)
Expand All @@ -53,6 +58,19 @@ terminal: var ## Start a terminal inside the php container
$(DOCKER_COMPOSE) run --rm $(ARGS) php bash
.PHONY: t terminal

rescaffold:
$(DOCKER) run \
--volume .:/project \
--user $(CONTAINER_USER) \
--interactive --tty --rm \
--pull always \
ghcr.io/phpyh/scaffolder:latest \
--user-name-default "$(shell git config user.name 2>/dev/null || whoami 2>/dev/null)" \
--user-email-default "$(shell git config user.email 2>/dev/null)" \
--package-project-default "$(shell basename $$(pwd))"
git add --all 2>/dev/null || true
.PHONY: rescaffold

##
## Tools
## -----
Expand Down Expand Up @@ -94,7 +112,7 @@ composer-validate: ## Validate composer.json
.PHONY: composer-validate

composer-normalize: ## Normalize composer.json
$(COMPOSER) normalize --diff $(ARGS)
$(COMPOSER) normalize --no-check-lock --no-update-lock --diff $(ARGS)
.PHONY: composer-normalize

composer-normalize-check: ## Check that composer.json is normalized
Expand All @@ -104,7 +122,7 @@ composer-normalize-check: ## Check that composer.json is normalized
fix: fixer rector composer-normalize ## Run all fixing recipes
.PHONY: fix

check: fixer-check rector-check composer-validate composer-normalize-check phpstan test deps-analyze ## Run all project checks
check: fixer-check rector-check composer-validate composer-normalize-check deps-analyze phpstan test ## Run all project checks
.PHONY: check

# -----------------------
Expand Down
11 changes: 7 additions & 4 deletions compose.yaml
Original file line number Diff line number Diff line change
@@ -1,13 +1,16 @@
services:
php:
image: ghcr.io/thesis-php/php:${PHP_VERSION:-8.3}
# noinspection ComposeUnknownValues
image: ghcr.io/phpyh/php:${PHP_IMAGE_VERSION:?Make sure `PHP_IMAGE_VERSION` is declared in `.env`}
user: ${CONTAINER_USER:-}
environment:
HISTFILE: /app/var/.docker_history
COMPOSER_CACHE_DIR: /app/var/.composer_cache
INSIDE_DEVCONTAINER: true
HISTFILE: /workspace/var/.docker_history
COMPOSER_CACHE_DIR: /workspace/var/.composer_cache
tty: true
volumes:
- .:/app:cached
- .:/workspace:cached
working_dir: /workspace

rabbitmq:
image: rabbitmq:4.0-management-alpine
Expand Down
4 changes: 4 additions & 0 deletions composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,10 @@
"phpunit/phpunit": "^12.4.4",
"symfony/var-dumper": "^7.3.5"
},
"conflict": {
"amphp/dns": "<2.1.2",
"amphp/socket": "<2.3.1"
},
"autoload": {
"psr-4": {
"Thesis\\Amqp\\": "src/"
Expand Down
2 changes: 1 addition & 1 deletion infection.json5.dist
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
"logs": {
"text": "var/infection.log",
"stryker": {
"report": "/^\\d+\\.\\d+\\.x$/"
"report": "/^(?>main|\\d+\\.\\d+\\.x)$/D"
},
},
"tmpDir": "var",
Expand Down
23 changes: 19 additions & 4 deletions phpstan.dist.neon
Original file line number Diff line number Diff line change
@@ -1,12 +1,27 @@
includes:
- /composer/vendor/phpstan/phpstan-strict-rules/rules.neon
- /composer/vendor/phpstan/phpstan-phpunit/extension.neon
- /composer/vendor/phpstan/phpstan-phpunit/rules.neon

parameters:
level: max
paths:
- examples
- src
- tests
- examples
checkBenevolentUnionTypes: true
tmpDir: var/phpstan
editorUrl: ''
editorUrlTitle: '%%file%%:%%line%%'
tmpDir: ./var/phpstan
editorUrlTitle: '%%relFile%%:%%line%%'
stubFiles:
- stubs/amphp.phpstub

# strict rules
checkBenevolentUnionTypes: true
checkMissingCallableSignature: true
# checkMissingOverrideMethodAttribute: true
checkUninitializedProperties: true
# rememberPossiblyImpureFunctionValues: false
reportAlwaysTrueInLastCondition: true
reportAnyTypeWideningInVarTag: true
reportPossiblyNonexistentConstantArrayOffset: true
# reportPossiblyNonexistentGeneralArrayOffset: true
9 changes: 2 additions & 7 deletions rector.php
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,7 @@

declare(strict_types=1);

use Rector\Config\RectorConfig;
use Rector\Php80\Rector\Class_\StringableForToStringRector;
use Rector\Php83\Rector\ClassMethod\AddOverrideAttributeToOverriddenMethodsRector;

return RectorConfig::configure()
return Rector\Config\RectorConfig::configure()
->withPaths([
__DIR__ . '/examples',
__DIR__ . '/src',
Expand All @@ -16,6 +12,5 @@
->withCache(__DIR__ . '/var/rector')
->withPhpSets()
->withSkip([
StringableForToStringRector::class,
AddOverrideAttributeToOverriddenMethodsRector::class,
Rector\Php80\Rector\Class_\StringableForToStringRector::class,
]);
16 changes: 9 additions & 7 deletions src/Config.php
Original file line number Diff line number Diff line change
Expand Up @@ -130,14 +130,12 @@ public static function fromURI(#[\SensitiveParameter] string $uri): self
}

$channelMax = self::MAX_CHANNEL;
if (isset($query['channel_max']) && is_numeric($query['channel_max']) && (int) $query['channel_max'] > 0) {
/** @var int<0, 65535> $channelMax */
if (isset($query['channel_max']) && is_numeric($query['channel_max']) && (int) $query['channel_max'] >= 0) {
$channelMax = min($channelMax, (int) $query['channel_max']);
}

$frameMax = self::MAX_FRAME;
if (isset($query['frame_max']) && is_numeric($query['frame_max']) && (int) $query['frame_max'] > 0) {
/** @var int<0, 65535> $frameMax */
if (isset($query['frame_max']) && is_numeric($query['frame_max']) && (int) $query['frame_max'] >= 0) {
$frameMax = min($frameMax, (int) $query['frame_max']);
}

Expand All @@ -164,12 +162,16 @@ public static function fromURI(#[\SensitiveParameter] string $uri): self
$urls = [];
foreach (explode(',', $components['host'] ?? '') as $host) {
$hostport = explode(':', $host);
$urls[] = \sprintf('%s:%d', $hostport[0] ?: self::DEFAULT_HOST, (int) ($hostport[1] ?? $port));
$urls[] = \sprintf('%s:%d', $hostport[0], (int) ($hostport[1] ?? $port));
}

$vhost = self::DEFAULT_VHOST;
if (isset($components['path'])) {
$vhost = ltrim($components['path'], '/') ?: self::DEFAULT_VHOST;
$vhost = ltrim($components['path'], '/');

if ($vhost === '') {
$vhost = self::DEFAULT_VHOST;
}
}

$user = self::DEFAULT_USERNAME;
Expand All @@ -183,7 +185,7 @@ public static function fromURI(#[\SensitiveParameter] string $uri): self
}

return new self(
scheme: Scheme::tryFrom($components['scheme'] ?? Scheme::amqp->value) ?: throw UriIsInvalid::invalidScheme($components['scheme'] ?? ''),
scheme: Scheme::tryFrom($components['scheme'] ?? Scheme::amqp->value) ?? throw UriIsInvalid::invalidScheme($components['scheme'] ?? ''),
urls: $urls,
user: $user,
password: $password,
Expand Down
10 changes: 4 additions & 6 deletions src/Internal/Delivery/ConsumerTagGenerator.php
Original file line number Diff line number Diff line change
Expand Up @@ -13,17 +13,15 @@ final class ConsumerTagGenerator
private const string PACKAGE_NAME = 'thesis/amqp';

/** @var non-empty-string */
private readonly string $commandName;
private readonly string $infix;

/** @var non-negative-int */
private int $consumerId = 0;

public function __construct()
{
/** @var array{0?: string} $cli */
$cli = $_SERVER['argv'] ?? [0 => self::PACKAGE_NAME];

$this->commandName = ($cli[0] ?? self::PACKAGE_NAME) ?: self::PACKAGE_NAME;
$command = $_SERVER['argv'][0] ?? null; // @phpstan-ignore offsetAccess.nonOffsetAccessible
$this->infix = \is_string($command) && $command !== '' ? $command : self::PACKAGE_NAME;
}

/**
Expand All @@ -32,7 +30,7 @@ public function __construct()
public function next(): string
{
$prefix = 'ctag-';
$infix = $this->commandName;
$infix = $this->infix;
$suffix = \sprintf('-%d', ++$this->consumerId);

if (\strlen($prefix) + \strlen($infix) + \strlen($suffix) > self::TAG_LENGTH_MAX) {
Expand Down
2 changes: 1 addition & 1 deletion src/Internal/Delivery/DeliverySupervisor.php
Original file line number Diff line number Diff line change
Expand Up @@ -190,7 +190,7 @@ private function runListeners(): void
ack: $this->return !== null ? $noAction : $channel->ack(...),
nack: $this->return !== null ? $noAction : $channel->nack(...),
reject: $this->return !== null ? $noAction : $channel->reject(...),
reply: $this->replier($this->header->properties, $channel) ?: $noAction,
reply: $this->replier($this->header->properties, $channel) ?? $noAction,
message: new Message(
body: $this->message,
headers: $this->header->properties->headers,
Expand Down
2 changes: 1 addition & 1 deletion src/Internal/Io/Buffer.php
Original file line number Diff line number Diff line change
Expand Up @@ -228,7 +228,7 @@ public function readDecimal(): int
$scale = $this->readUint8();
$value = $this->readUint32();

return (int) ($value * (10 ** $scale));
return (int) ($value * (10 ** $scale)); // @phpstan-ignore cast.useless
}

public function readText(): string
Expand Down
2 changes: 1 addition & 1 deletion src/Internal/MessageProperties.php
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ public static function read(Io\ReadBytes $reader, int $mask): self
$contentType = self::hasSet($mask, self::FLAG_CONTENT_TYPE) ? $reader->readString() : null;
$contentEncoding = self::hasSet($mask, self::FLAG_CONTENT_ENCODING) ? $reader->readString() : null;
$headers = self::hasSet($mask, self::FLAG_HEADERS) ? $reader->readTable() : [];
$deliveryMode = self::hasSet($mask, self::FLAG_DELIVERY_MODE) ? (DeliveryMode::tryFrom($reader->readUint8()) ?: DeliveryMode::Whatever) : DeliveryMode::Whatever;
$deliveryMode = self::hasSet($mask, self::FLAG_DELIVERY_MODE) ? (DeliveryMode::tryFrom($reader->readUint8()) ?? DeliveryMode::Whatever) : DeliveryMode::Whatever;
/** @var ?int<0, 9> $priority */
$priority = self::hasSet($mask, self::FLAG_PRIORITY) ? $reader->readUint8() : null;
/** @var non-empty-string $correlationId */
Expand Down
2 changes: 1 addition & 1 deletion src/Internal/Returns/FutureBoundedReturnListener.php
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ public function __construct(
private readonly DeliverySupervisor $supervisor,
?\Closure $mandatoryIdGenerator = null,
) {
$this->mandatoryIdGenerator = $mandatoryIdGenerator ?: static fn(): string => bin2hex(random_bytes(10));
$this->mandatoryIdGenerator = $mandatoryIdGenerator ?? static fn(): string => bin2hex(random_bytes(10));
}

public function listen(): void
Expand Down
Loading