From d5c0f66e1a789ee3e0ba40b02981315396ca9be0 Mon Sep 17 00:00:00 2001 From: Golo Roden Date: Mon, 20 Oct 2025 23:51:27 +0200 Subject: [PATCH 1/5] feat: Add isSubjectPopulated precondition --- README.md | 14 +++++++++ src/IsSubjectPopulated.php | 25 ++++++++++++++++ tests/WriteEventsTest.php | 58 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 src/IsSubjectPopulated.php diff --git a/README.md b/README.md index 9947e5e..8bfdcca 100644 --- a/README.md +++ b/README.md @@ -79,6 +79,20 @@ $writtenEvents = $client->writeEvents([ ]); ``` +#### Using the `isSubjectPopulated` precondition + +If you only want to write events in case a subject (such as `/books/42`) already has at least one event, import the `IsSubjectPopulated` class, use it to create a precondition, and pass it in an array as the second argument: + +```php +use Thenativeweb\Eventsourcingdb\IsSubjectPopulated; + +$writtenEvents = $client->writeEvents([ + // events +], [ + new IsSubjectPopulated('/books/42'), +]); +``` + #### Using the `isSubjectOnEventId` precondition If you only want to write events in case the last event of a subject (such as `/books/42`) has a specific ID (e.g., `0`), import the `IsSubjectOnEventId` class, use it to create a precondition, and pass it in an array as the second argument: diff --git a/src/IsSubjectPopulated.php b/src/IsSubjectPopulated.php new file mode 100644 index 0000000..a013ac4 --- /dev/null +++ b/src/IsSubjectPopulated.php @@ -0,0 +1,25 @@ + 'isSubjectPopulated', + 'payload' => [ + 'subject' => $this->subject, + ], + ]; + } +} diff --git a/tests/WriteEventsTest.php b/tests/WriteEventsTest.php index 3ebe392..8f7ae01 100644 --- a/tests/WriteEventsTest.php +++ b/tests/WriteEventsTest.php @@ -3,10 +3,12 @@ declare(strict_types=1); use PHPUnit\Framework\TestCase; +use RuntimeException; use Thenativeweb\Eventsourcingdb\CloudEvent; use Thenativeweb\Eventsourcingdb\EventCandidate; use Thenativeweb\Eventsourcingdb\IsEventQlQueryTrue; use Thenativeweb\Eventsourcingdb\IsSubjectOnEventId; +use Thenativeweb\Eventsourcingdb\IsSubjectPopulated; use Thenativeweb\Eventsourcingdb\IsSubjectPristine; use Thenativeweb\Eventsourcingdb\Tests\Trait\ClientTestTrait; @@ -104,6 +106,62 @@ public function testSupportsTheIsSubjectPristinePrecondition(): void ); } + public function testSupportsTheIsSubjectPopulatedPrecondition(): void + { + $firstEvent = new EventCandidate( + source: 'https://www.eventsourcingdb.io', + subject: '/test', + type: 'io.eventsourcingdb.test', + data: [ + 'value' => 23, + ], + ); + + $secondEvent = new EventCandidate( + source: 'https://www.eventsourcingdb.io', + subject: '/test', + type: 'io.eventsourcingdb.test', + data: [ + 'value' => 42, + ], + ); + + try { + $this->client->writeEvents( + [ + $secondEvent, + ], + [ + new IsSubjectPopulated('/test'), + ], + ); + $this->fail('Expected the isSubjectPopulated precondition to reject writing to an empty subject.'); + } catch (RuntimeException $runtimeException) { + $this->assertSame( + "Failed to write events, got HTTP status code '409', expected '200'", + $runtimeException->getMessage(), + ); + } + + $this->client->writeEvents([ + $firstEvent, + ]); + + $writtenEvents = $this->client->writeEvents( + [ + $secondEvent, + ], + [ + new IsSubjectPopulated('/test'), + ], + ); + + $this->assertCount(1, $writtenEvents); + $this->assertInstanceOf(CloudEvent::class, $writtenEvents[0]); + $this->assertSame('1', $writtenEvents[0]->id); + $this->assertSame(42, $writtenEvents[0]->data['value']); + } + public function testSupportsTheIsSubjectOnEventIdPrecondition(): void { $firstEvent = new EventCandidate( From 0bd10762531e70c387f84c7ead2eebb1aa387e91 Mon Sep 17 00:00:00 2001 From: Golo Roden Date: Tue, 21 Oct 2025 09:53:58 +0200 Subject: [PATCH 2/5] Streamline tests. --- tests/WriteEventsTest.php | 41 ++++++++++++++++++++++----------------- 1 file changed, 23 insertions(+), 18 deletions(-) diff --git a/tests/WriteEventsTest.php b/tests/WriteEventsTest.php index 8f7ae01..d7a0eef 100644 --- a/tests/WriteEventsTest.php +++ b/tests/WriteEventsTest.php @@ -3,7 +3,6 @@ declare(strict_types=1); use PHPUnit\Framework\TestCase; -use RuntimeException; use Thenativeweb\Eventsourcingdb\CloudEvent; use Thenativeweb\Eventsourcingdb\EventCandidate; use Thenativeweb\Eventsourcingdb\IsEventQlQueryTrue; @@ -106,6 +105,29 @@ public function testSupportsTheIsSubjectPristinePrecondition(): void ); } + public function testRejectsWritingToEmptySubjectWhenUsingTheIsSubjectPopulatedPrecondition(): void + { + $secondEvent = new EventCandidate( + source: 'https://www.eventsourcingdb.io', + subject: '/test', + type: 'io.eventsourcingdb.test', + data: [ + 'value' => 42, + ], + ); + + $this->expectExceptionMessage("Failed to write events, got HTTP status code '409', expected '200'"); + + $this->client->writeEvents( + [ + $secondEvent, + ], + [ + new IsSubjectPopulated('/test'), + ], + ); + } + public function testSupportsTheIsSubjectPopulatedPrecondition(): void { $firstEvent = new EventCandidate( @@ -126,23 +148,6 @@ public function testSupportsTheIsSubjectPopulatedPrecondition(): void ], ); - try { - $this->client->writeEvents( - [ - $secondEvent, - ], - [ - new IsSubjectPopulated('/test'), - ], - ); - $this->fail('Expected the isSubjectPopulated precondition to reject writing to an empty subject.'); - } catch (RuntimeException $runtimeException) { - $this->assertSame( - "Failed to write events, got HTTP status code '409', expected '200'", - $runtimeException->getMessage(), - ); - } - $this->client->writeEvents([ $firstEvent, ]); From 9e618c1dcdfaafd931078cda9179ee3d9d7644f7 Mon Sep 17 00:00:00 2001 From: Golo Roden Date: Tue, 21 Oct 2025 09:58:43 +0200 Subject: [PATCH 3/5] Fix linter issues. --- tests/WriteEventsTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/WriteEventsTest.php b/tests/WriteEventsTest.php index d7a0eef..cbe51df 100644 --- a/tests/WriteEventsTest.php +++ b/tests/WriteEventsTest.php @@ -46,7 +46,7 @@ public function testWritesMultipleEvents(): void ], ); - $secondEvent = new EventCandidate( + $eventCandidate = new EventCandidate( source: 'https://www.eventsourcingdb.io', subject: '/test', type: 'io.eventsourcingdb.test', @@ -120,7 +120,7 @@ public function testRejectsWritingToEmptySubjectWhenUsingTheIsSubjectPopulatedPr $this->client->writeEvents( [ - $secondEvent, + $eventCandidate, ], [ new IsSubjectPopulated('/test'), From 9838598fda98048f01cb0484956a8886758a1511 Mon Sep 17 00:00:00 2001 From: Golo Roden Date: Tue, 21 Oct 2025 10:02:02 +0200 Subject: [PATCH 4/5] Fix linter issues. --- tests/WriteEventsTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/WriteEventsTest.php b/tests/WriteEventsTest.php index cbe51df..d7a0eef 100644 --- a/tests/WriteEventsTest.php +++ b/tests/WriteEventsTest.php @@ -46,7 +46,7 @@ public function testWritesMultipleEvents(): void ], ); - $eventCandidate = new EventCandidate( + $secondEvent = new EventCandidate( source: 'https://www.eventsourcingdb.io', subject: '/test', type: 'io.eventsourcingdb.test', @@ -120,7 +120,7 @@ public function testRejectsWritingToEmptySubjectWhenUsingTheIsSubjectPopulatedPr $this->client->writeEvents( [ - $eventCandidate, + $secondEvent, ], [ new IsSubjectPopulated('/test'), From 382d5e97c7f5a2645968eacac450532b6082ff7d Mon Sep 17 00:00:00 2001 From: Golo Roden Date: Tue, 21 Oct 2025 10:07:32 +0200 Subject: [PATCH 5/5] Fixed linter issues. --- tests/WriteEventsTest.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/WriteEventsTest.php b/tests/WriteEventsTest.php index d7a0eef..5a24fe4 100644 --- a/tests/WriteEventsTest.php +++ b/tests/WriteEventsTest.php @@ -107,7 +107,7 @@ public function testSupportsTheIsSubjectPristinePrecondition(): void public function testRejectsWritingToEmptySubjectWhenUsingTheIsSubjectPopulatedPrecondition(): void { - $secondEvent = new EventCandidate( + $eventCandidate = new EventCandidate( source: 'https://www.eventsourcingdb.io', subject: '/test', type: 'io.eventsourcingdb.test', @@ -120,7 +120,7 @@ public function testRejectsWritingToEmptySubjectWhenUsingTheIsSubjectPopulatedPr $this->client->writeEvents( [ - $secondEvent, + $eventCandidate, ], [ new IsSubjectPopulated('/test'),