diff --git a/.gitignore b/.gitignore index b2d3bb8..5c45ac0 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,6 @@ # PHP specific files and folders +/cache /vendor -.phpunit.result.cache # OS generated files and folders .DS_Store diff --git a/composer.json b/composer.json index a9b307e..4ff549d 100644 --- a/composer.json +++ b/composer.json @@ -25,21 +25,29 @@ "minimum-stability": "stable", "require": { "php": ">=8.2", - "guzzlehttp/guzzle": "7.9.3" + "guzzlehttp/guzzle": "^7.5" }, "require-dev": { "phpstan/phpstan": "2.1.17", "phpunit/phpunit": "11.5.21", + "rector/rector": "2.1.1", "symplify/easy-coding-standard": "12.5.20", "testcontainers/testcontainers": "1.0.3" }, "scripts": { - "analyze": "vendor/bin/ecs check && composer stan", - "format": "vendor/bin/ecs check --fix", - "stan": " vendor/bin/phpstan analyze -l 5 src tests", + "analyze": [ + "vendor/bin/rector process --dry-run --ansi", + "vendor/bin/ecs check", + "@stan" + ], + "format": [ + "vendor/bin/rector process", + "vendor/bin/ecs check --fix" + ], + "stan": "vendor/bin/phpstan analyze", "qa": [ - "composer analyze", - "composer test" + "@analyze", + "@test" ], "test": "vendor/bin/phpunit" }, diff --git a/composer.lock b/composer.lock index cf69d57..28a7aa3 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "ccefa4638de4e738729b2c6066af7b0d", + "content-hash": "0c704edf4d29864601b165b081bfbb14", "packages": [ { "name": "guzzlehttp/guzzle", @@ -1118,16 +1118,16 @@ }, { "name": "myclabs/deep-copy", - "version": "1.13.1", + "version": "1.13.3", "source": { "type": "git", "url": "https://github.com/myclabs/DeepCopy.git", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c" + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/1720ddd719e16cf0db4eb1c6eca108031636d46c", - "reference": "1720ddd719e16cf0db4eb1c6eca108031636d46c", + "url": "https://api.github.com/repos/myclabs/DeepCopy/zipball/faed855a7b5f4d4637717c2b3863e277116beb36", + "reference": "faed855a7b5f4d4637717c2b3863e277116beb36", "shasum": "" }, "require": { @@ -1166,7 +1166,7 @@ ], "support": { "issues": "https://github.com/myclabs/DeepCopy/issues", - "source": "https://github.com/myclabs/DeepCopy/tree/1.13.1" + "source": "https://github.com/myclabs/DeepCopy/tree/1.13.3" }, "funding": [ { @@ -1174,20 +1174,20 @@ "type": "tidelift" } ], - "time": "2025-04-29T12:36:36+00:00" + "time": "2025-07-05T12:25:42+00:00" }, { "name": "nikic/php-parser", - "version": "v5.4.0", + "version": "v5.5.0", "source": { "type": "git", "url": "https://github.com/nikic/PHP-Parser.git", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494" + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/447a020a1f875a434d62f2a401f53b82a396e494", - "reference": "447a020a1f875a434d62f2a401f53b82a396e494", + "url": "https://api.github.com/repos/nikic/PHP-Parser/zipball/ae59794362fe85e051a58ad36b289443f57be7a9", + "reference": "ae59794362fe85e051a58ad36b289443f57be7a9", "shasum": "" }, "require": { @@ -1230,9 +1230,9 @@ ], "support": { "issues": "https://github.com/nikic/PHP-Parser/issues", - "source": "https://github.com/nikic/PHP-Parser/tree/v5.4.0" + "source": "https://github.com/nikic/PHP-Parser/tree/v5.5.0" }, - "time": "2024-12-30T11:07:19+00:00" + "time": "2025-05-31T08:24:38+00:00" }, { "name": "nyholm/psr7", @@ -1985,16 +1985,16 @@ }, { "name": "phpunit/php-code-coverage", - "version": "11.0.9", + "version": "11.0.10", "source": { "type": "git", "url": "https://github.com/sebastianbergmann/php-code-coverage.git", - "reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7" + "reference": "1a800a7446add2d79cc6b3c01c45381810367d76" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/14d63fbcca18457e49c6f8bebaa91a87e8e188d7", - "reference": "14d63fbcca18457e49c6f8bebaa91a87e8e188d7", + "url": "https://api.github.com/repos/sebastianbergmann/php-code-coverage/zipball/1a800a7446add2d79cc6b3c01c45381810367d76", + "reference": "1a800a7446add2d79cc6b3c01c45381810367d76", "shasum": "" }, "require": { @@ -2051,15 +2051,27 @@ "support": { "issues": "https://github.com/sebastianbergmann/php-code-coverage/issues", "security": "https://github.com/sebastianbergmann/php-code-coverage/security/policy", - "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/11.0.9" + "source": "https://github.com/sebastianbergmann/php-code-coverage/tree/show" }, "funding": [ { "url": "https://github.com/sebastianbergmann", "type": "github" + }, + { + "url": "https://liberapay.com/sebastianbergmann", + "type": "liberapay" + }, + { + "url": "https://thanks.dev/u/gh/sebastianbergmann", + "type": "thanks_dev" + }, + { + "url": "https://tidelift.com/funding/github/packagist/phpunit/php-code-coverage", + "type": "tidelift" } ], - "time": "2025-02-25T13:26:39+00:00" + "time": "2025-06-18T08:56:18+00:00" }, { "name": "phpunit/php-file-iterator", @@ -2415,6 +2427,66 @@ ], "time": "2025-05-21T12:35:00+00:00" }, + { + "name": "rector/rector", + "version": "2.1.1", + "source": { + "type": "git", + "url": "https://github.com/rectorphp/rector.git", + "reference": "d0917c069bb0d9bb06ed111cf052510f609015a4" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/rectorphp/rector/zipball/d0917c069bb0d9bb06ed111cf052510f609015a4", + "reference": "d0917c069bb0d9bb06ed111cf052510f609015a4", + "shasum": "" + }, + "require": { + "php": "^7.4|^8.0", + "phpstan/phpstan": "^2.1.17" + }, + "conflict": { + "rector/rector-doctrine": "*", + "rector/rector-downgrade-php": "*", + "rector/rector-phpunit": "*", + "rector/rector-symfony": "*" + }, + "suggest": { + "ext-dom": "To manipulate phpunit.xml via the custom-rule command" + }, + "bin": [ + "bin/rector" + ], + "type": "library", + "autoload": { + "files": [ + "bootstrap.php" + ] + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "description": "Instant Upgrade and Automated Refactoring of any PHP code", + "homepage": "https://getrector.com/", + "keywords": [ + "automation", + "dev", + "migration", + "refactoring" + ], + "support": { + "issues": "https://github.com/rectorphp/rector/issues", + "source": "https://github.com/rectorphp/rector/tree/2.1.1" + }, + "funding": [ + { + "url": "https://github.com/tomasvotruba", + "type": "github" + } + ], + "time": "2025-07-10T11:31:31+00:00" + }, { "name": "sebastian/cli-parser", "version": "3.0.2", @@ -3917,16 +3989,16 @@ }, { "name": "symfony/serializer", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/serializer.git", - "reference": "2d86f81b1c506d7e1578789f93280dab4b8411bb" + "reference": "feaf837cedbbc8287986602223175d3fd639922d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/serializer/zipball/2d86f81b1c506d7e1578789f93280dab4b8411bb", - "reference": "2d86f81b1c506d7e1578789f93280dab4b8411bb", + "url": "https://api.github.com/repos/symfony/serializer/zipball/feaf837cedbbc8287986602223175d3fd639922d", + "reference": "feaf837cedbbc8287986602223175d3fd639922d", "shasum": "" }, "require": { @@ -3995,7 +4067,7 @@ "description": "Handles serializing and deserializing data structures, including object graphs, into array structures or other formats like XML and JSON.", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/serializer/tree/v7.3.0" + "source": "https://github.com/symfony/serializer/tree/v7.3.1" }, "funding": [ { @@ -4011,7 +4083,7 @@ "type": "tidelift" } ], - "time": "2025-05-12T14:48:23+00:00" + "time": "2025-06-27T19:55:54+00:00" }, { "name": "symfony/translation-contracts", @@ -4093,16 +4165,16 @@ }, { "name": "symfony/validator", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/validator.git", - "reference": "dabb03cddf50761c0aff4fbf5a3b3fffb3e5e38b" + "reference": "e2f2497c869fc57446f735fbf00cff4de32ae8c3" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/validator/zipball/dabb03cddf50761c0aff4fbf5a3b3fffb3e5e38b", - "reference": "dabb03cddf50761c0aff4fbf5a3b3fffb3e5e38b", + "url": "https://api.github.com/repos/symfony/validator/zipball/e2f2497c869fc57446f735fbf00cff4de32ae8c3", + "reference": "e2f2497c869fc57446f735fbf00cff4de32ae8c3", "shasum": "" }, "require": { @@ -4171,7 +4243,7 @@ "description": "Provides tools to validate values", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/validator/tree/v7.3.0" + "source": "https://github.com/symfony/validator/tree/v7.3.1" }, "funding": [ { @@ -4187,20 +4259,20 @@ "type": "tidelift" } ], - "time": "2025-05-29T07:19:49+00:00" + "time": "2025-06-26T13:22:23+00:00" }, { "name": "symfony/yaml", - "version": "v7.3.0", + "version": "v7.3.1", "source": { "type": "git", "url": "https://github.com/symfony/yaml.git", - "reference": "cea40a48279d58dc3efee8112634cb90141156c2" + "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/yaml/zipball/cea40a48279d58dc3efee8112634cb90141156c2", - "reference": "cea40a48279d58dc3efee8112634cb90141156c2", + "url": "https://api.github.com/repos/symfony/yaml/zipball/0c3555045a46ab3cd4cc5a69d161225195230edb", + "reference": "0c3555045a46ab3cd4cc5a69d161225195230edb", "shasum": "" }, "require": { @@ -4243,7 +4315,7 @@ "description": "Loads and dumps YAML files", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/yaml/tree/v7.3.0" + "source": "https://github.com/symfony/yaml/tree/v7.3.1" }, "funding": [ { @@ -4259,7 +4331,7 @@ "type": "tidelift" } ], - "time": "2025-04-04T10:10:33+00:00" + "time": "2025-06-03T06:57:57+00:00" }, { "name": "symplify/easy-coding-standard", @@ -4434,12 +4506,12 @@ ], "aliases": [], "minimum-stability": "stable", - "stability-flags": [], + "stability-flags": {}, "prefer-stable": false, "prefer-lowest": false, "platform": { "php": ">=8.2" }, - "platform-dev": [], - "plugin-api-version": "2.3.0" + "platform-dev": {}, + "plugin-api-version": "2.6.0" } diff --git a/ecs.php b/ecs.php index e37fb39..c17f1a8 100644 --- a/ecs.php +++ b/ecs.php @@ -2,10 +2,31 @@ declare(strict_types=1); +use PhpCsFixer\Fixer\ClassNotation\ClassAttributesSeparationFixer; +use PhpCsFixer\Fixer\Operator\NotOperatorWithSuccessorSpaceFixer; use Symplify\EasyCodingStandard\Config\ECSConfig; -use Symplify\EasyCodingStandard\ValueObject\Set\SetList; -return static function (ECSConfig $ecsConfig): void { - $ecsConfig->paths([__DIR__ . '/src', __DIR__ . '/tests']); - $ecsConfig->sets([SetList::PSR_12]); -}; +return ECSConfig::configure() + ->withParallel() + ->withPreparedSets( + psr12: true, + common: false, + symplify: false, + arrays: true, + comments: true, + docblocks: true, + spaces: true, + namespaces: true, + controlStructures: true, + phpunit: true, + strict: true, + cleanCode: true, + ) + ->withPaths([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + ->withSkip([ + ClassAttributesSeparationFixer::class, + NotOperatorWithSuccessorSpaceFixer::class, + ]); diff --git a/phpstan.neon b/phpstan.neon new file mode 100755 index 0000000..a8cdb4b --- /dev/null +++ b/phpstan.neon @@ -0,0 +1,5 @@ +parameters: + level: 5 + checkMissingCallableSignature: true + paths: + - src diff --git a/phpunit.xml b/phpunit.xml index b0acac7..86c0c69 100644 --- a/phpunit.xml +++ b/phpunit.xml @@ -2,9 +2,15 @@ withPhpVersion(PhpVersion::PHP_82) + ->withPhpstanConfigs([ + __DIR__ . '/phpstan.neon', + ]) + ->withRootFiles() + ->withComposerBased(phpunit: true) + ->withPreparedSets( + deadCode: true, + codeQuality: true, + codingStyle: true, + typeDeclarations: true, + privatization: true, + naming: true, + instanceOf: true, + earlyReturn: true, + rectorPreset: true, + phpunitCodeQuality: true + ) + ->withPaths([ + __DIR__ . '/src', + __DIR__ . '/tests', + ]) + ->withSkip([ + EncapsedStringsToSprintfRector::class, + NewlineAfterStatementRector::class, + RenamePropertyToMatchTypeRector::class, + SimplifyUselessVariableRector::class, + ]); diff --git a/src/Client.php b/src/Client.php index 9d29fa9..6e32bc0 100644 --- a/src/Client.php +++ b/src/Client.php @@ -6,7 +6,6 @@ use DateTimeImmutable; use GuzzleHttp\Client as HttpClient; -use GuzzleHttp\Exception\GuzzleException; use RuntimeException; final readonly class Client @@ -19,7 +18,7 @@ public function __construct(string $url, string $apiToken) $this->apiToken = $apiToken; $this->httpClient = new HttpClient([ 'base_uri' => rtrim($url, '/'), - 'http_errors' => false + 'http_errors' => false, ]); } @@ -75,7 +74,7 @@ public function writeEvents(array $events, array $preconditions = []): array $requestBody = [ 'events' => $events, ]; - if (!empty($preconditions)) { + if ($preconditions !== []) { $requestBody['preconditions'] = $preconditions; } @@ -101,7 +100,7 @@ public function writeEvents(array $events, array $preconditions = []): array $body = (string) $response->getBody(); $data = json_decode($body, true); - $writtenEvents = array_map(fn ($item) => new CloudEvent( + $writtenEvents = array_map(fn ($item): CloudEvent => new CloudEvent( $item['specversion'], $item['id'], new DateTimeImmutable($item['time']), diff --git a/src/Container.php b/src/Container.php index c7d8815..21077d7 100644 --- a/src/Container.php +++ b/src/Container.php @@ -6,27 +6,23 @@ use GuzzleHttp\Client as HttpClient; use GuzzleHttp\Exception\GuzzleException; +use RuntimeException; use Testcontainers\Container\GenericContainer; use Testcontainers\Container\StartedGenericContainer; final class Container { - private string $imageName; - private string $imageTag; - private int $internalPort; - private string $apiToken; - private ?StartedGenericContainer $container; + private string $imageName = 'thenativeweb/eventsourcingdb'; + private string $imageTag = 'latest'; + private int $internalPort = 3000; + private string $apiToken = 'secret'; + private ?StartedGenericContainer $container = null; private HttpClient $httpClient; public function __construct() { - $this->imageName = 'thenativeweb/eventsourcingdb'; - $this->imageTag = 'latest'; - $this->internalPort = 3000; - $this->apiToken = 'secret'; - $this->container = null; $this->httpClient = new HttpClient([ - 'http_errors' => false + 'http_errors' => false, ]); } @@ -58,7 +54,7 @@ public function start(): void '--api-token', $this->apiToken, '--data-directory-temporary', '--http-enabled', - '--https-enabled=false' + '--https-enabled=false', ]); $this->container = $container->start(); @@ -73,6 +69,7 @@ public function start(): void usleep(100_000); continue; } + $status = $response->getStatusCode(); if ($status === 200) { @@ -107,27 +104,27 @@ public function getApiToken(): string public function isRunning(): bool { - return $this->container !== null; + return $this->container instanceof StartedGenericContainer; } public function stop(): void { - if ($this->container !== null) { + if ($this->container instanceof StartedGenericContainer) { $this->container->stop(); $this->container = null; } } - public function getClient(): \Thenativeweb\Eventsourcingdb\Client + public function getClient(): Client { $baseUrl = $this->getBaseUrl(); - return new \Thenativeweb\Eventsourcingdb\Client($baseUrl, $this->apiToken); + return new Client($baseUrl, $this->apiToken); } private function ensureRunning(): void { - if ($this->container === null) { - throw new \RuntimeException('Container must be running'); + if (!$this->container instanceof StartedGenericContainer) { + throw new RuntimeException('Container must be running'); } } } diff --git a/src/EventCandidate.php b/src/EventCandidate.php index 3748d2f..19a612f 100644 --- a/src/EventCandidate.php +++ b/src/EventCandidate.php @@ -5,7 +5,6 @@ namespace Thenativeweb\Eventsourcingdb; use JsonSerializable; -use RuntimeException; final readonly class EventCandidate implements JsonSerializable { @@ -31,6 +30,7 @@ public function jsonSerialize(): array if ($this->traceParent !== null) { $result['traceParent'] = $this->traceParent; } + if ($this->traceState !== null) { $result['traceState'] = $this->traceState; } diff --git a/tests/ClientTest.php b/tests/ClientTest.php index d471927..bd40930 100644 --- a/tests/ClientTest.php +++ b/tests/ClientTest.php @@ -11,11 +11,12 @@ use Thenativeweb\Eventsourcingdb\IsSubjectOnEventId; use Thenativeweb\Eventsourcingdb\IsSubjectPristine; -class ClientTest extends TestCase +final class ClientTest extends TestCase { use ClientTestTrait; private Container $container; + private Client $client; protected function setUp(): void @@ -24,13 +25,13 @@ protected function setUp(): void $this->container = $this->startContainer(); $this->client = $this->container->getClient(); } + protected function tearDown(): void { $this->container->stop(); parent::tearDown(); } - public function testPingSucceedsWhenServerIsReachable(): void { $this->client->ping(); @@ -46,8 +47,6 @@ public function testPingFailsWhenServerIsUnreachable(): void $client->ping(); } - - public function testVerifyApiTokenDoesNotThrowAnErrorIfTheTokenIsValid(): void { $client = $this->container->getClient(); @@ -66,7 +65,7 @@ public function testVerifyApiTokenThrowsAnErrorIfTheTokenIsInvalid(): void public function testWriteEventsWritesASingleEvent(): void { - $event = new EventCandidate( + $eventCandidate = new EventCandidate( 'https://www.eventsourcingdb.io', '/test', 'io.eventsourcingdb.test', @@ -76,7 +75,7 @@ public function testWriteEventsWritesASingleEvent(): void ); $writtenEvents = $this->client->writeEvents([ - $event, + $eventCandidate, ]); $this->assertCount(1, $writtenEvents); @@ -144,7 +143,7 @@ public function testWriteEventsSupportsTheIsSubjectPristinePrecondition(): void $this->client->writeEvents([ $secondEvent, ], [ - new IsSubjectPristine('/test') + new IsSubjectPristine('/test'), ]); } @@ -176,7 +175,7 @@ public function testWriteEventsSupportsTheIsSubjectOnEventIdPrecondition(): void $this->client->writeEvents([ $secondEvent, ], [ - new IsSubjectOnEventId('/test', '1') + new IsSubjectOnEventId('/test', '1'), ]); } } diff --git a/tests/ClientTestTrait.php b/tests/ClientTestTrait.php index 5c1a6f5..d19eb26 100644 --- a/tests/ClientTestTrait.php +++ b/tests/ClientTestTrait.php @@ -5,7 +5,6 @@ namespace Thenativeweb\Eventsourcingdb\Tests; use Thenativeweb\Eventsourcingdb\Container; -use function Thenativeweb\Eventsourcingdb\Tests\getImageVersionFromDockerfile; trait ClientTestTrait {