diff --git a/CHANGELOG.md b/CHANGELOG.md index 8a54395..12be414 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/) and [this changelog format](http://keepachangelog.com/). +## Next Major Release + +### Added + +- **BREAKING**: The `Identifier` interface now has an `any()` method that returns `true` if any of the given identifiers + match the identifier. If you have implemented any custom identifiers, you can add the `IsIdentifier` trait to + implement this new method. +- **BREAKING**: Add `message()` and `messages()` helper methods to the `ErrorList` interface. Although technically + breaking, this will not affect most implementations as the concrete error list class provided this package has been + updated. + ## Unreleased ## [4.1.0] - 2025-10-03 diff --git a/docs/guide/upgrade.md b/docs/guide/upgrade.md index a1f041e..ab13826 100644 --- a/docs/guide/upgrade.md +++ b/docs/guide/upgrade.md @@ -1,5 +1,27 @@ # Upgrade Guide +## 4.x to 5.x + +Upgrade using Composer: + +```bash +composer config minimum-stability dev +composer require cloudcreativity/ddd-modules:^5.0 +``` + +### Identifiers + +A new `any()` method has been added to the `Identifier` interface. This will only affect your implementation if you have +implemented any custom identifier classes. To solve, add the `IsIdentifier` trait to your class. + +### Other Changes + +Although this release contains other breaking changes, most implementations can upgrade without making any changes. This +is because the majority of changes affect classes that are implemented by this package - and it is unlikely you are +implementing these yourself. + +Refer to the changelog for a full list of changes. + ## 3.x to 4.x Upgrade using Composer: @@ -8,7 +30,9 @@ Upgrade using Composer: composer require cloudcreativity/ddd-modules:^4.0 ``` -Although this release contains breaking changes, most implementations can upgrade without making any changes. This is because the majority of changes affect classes that are implemented by this package - and it is unlikely you are implementing these yourself. +Although this release contains breaking changes, most implementations can upgrade without making any changes. This is +because the majority of changes affect classes that are implemented by this package - and it is unlikely you are +implementing these yourself. Refer to the changelog for a full list of changes. diff --git a/src/Contracts/Toolkit/Identifiers/Identifier.php b/src/Contracts/Toolkit/Identifiers/Identifier.php index dd7acd5..3bac5c6 100644 --- a/src/Contracts/Toolkit/Identifiers/Identifier.php +++ b/src/Contracts/Toolkit/Identifiers/Identifier.php @@ -22,6 +22,11 @@ interface Identifier extends Stringable, Contextual */ public function is(?self $other): bool; + /** + * Is the identifier any of the provided identifiers? + */ + public function any(?self ...$others): bool; + /** * Fluent to-string method. */ diff --git a/src/Contracts/Toolkit/Result/ListOfErrors.php b/src/Contracts/Toolkit/Result/ListOfErrors.php index f69642c..240b9a7 100644 --- a/src/Contracts/Toolkit/Result/ListOfErrors.php +++ b/src/Contracts/Toolkit/Result/ListOfErrors.php @@ -38,7 +38,7 @@ public function contains(Closure|UnitEnum $matcher): bool; /** * Get all the unique error codes in the list. * - * @return array + * @return list */ public function codes(): array; @@ -47,6 +47,20 @@ public function codes(): array; */ public function code(): ?UnitEnum; + /** + * Get all the unique error messages in the list. + * + * @return list + */ + public function messages(): array; + + /** + * Get the first error message in the list. + * + * @return non-empty-string|null + */ + public function message(): ?string; + /** * Return a new instance with the provided error pushed on to the end of the list. * diff --git a/src/Toolkit/Identifiers/Guid.php b/src/Toolkit/Identifiers/Guid.php index c325872..89c479e 100644 --- a/src/Toolkit/Identifiers/Guid.php +++ b/src/Toolkit/Identifiers/Guid.php @@ -23,6 +23,8 @@ final readonly class Guid implements Identifier { + use IsIdentifier; + public static function from(Identifier $value): self { if ($value instanceof self) { diff --git a/src/Toolkit/Identifiers/IntegerId.php b/src/Toolkit/Identifiers/IntegerId.php index a2bf94a..9dad711 100644 --- a/src/Toolkit/Identifiers/IntegerId.php +++ b/src/Toolkit/Identifiers/IntegerId.php @@ -19,6 +19,8 @@ final readonly class IntegerId implements Identifier, JsonSerializable { + use IsIdentifier; + public static function from(Identifier|int $value): self { return match(true) { diff --git a/src/Toolkit/Identifiers/IsIdentifier.php b/src/Toolkit/Identifiers/IsIdentifier.php new file mode 100644 index 0000000..8f04367 --- /dev/null +++ b/src/Toolkit/Identifiers/IsIdentifier.php @@ -0,0 +1,26 @@ + $this->is($other), + ); + } +} diff --git a/src/Toolkit/Identifiers/StringId.php b/src/Toolkit/Identifiers/StringId.php index 8d128e8..0001ee0 100644 --- a/src/Toolkit/Identifiers/StringId.php +++ b/src/Toolkit/Identifiers/StringId.php @@ -19,6 +19,8 @@ final readonly class StringId implements Identifier, JsonSerializable { + use IsIdentifier; + public static function from(Identifier|string $value): self { return match(true) { diff --git a/src/Toolkit/Identifiers/Uuid.php b/src/Toolkit/Identifiers/Uuid.php index 5e40d39..403c17a 100644 --- a/src/Toolkit/Identifiers/Uuid.php +++ b/src/Toolkit/Identifiers/Uuid.php @@ -20,6 +20,8 @@ final class Uuid implements Identifier, JsonSerializable { + use IsIdentifier; + private static ?IUuidFactory $factory = null; public static function setFactory(?IUuidFactory $factory): void diff --git a/src/Toolkit/Result/ListOfErrors.php b/src/Toolkit/Result/ListOfErrors.php index b75c70b..210ab79 100644 --- a/src/Toolkit/Result/ListOfErrors.php +++ b/src/Toolkit/Result/ListOfErrors.php @@ -102,6 +102,34 @@ public function code(): ?UnitEnum return null; } + public function messages(): array + { + $messages = []; + + foreach ($this->stack as $error) { + $message = $error->message(); + + if (strlen($message) > 0) { + $messages[$message] = $message; + } + } + + return array_values($messages); + } + + public function message(): ?string + { + foreach ($this->stack as $error) { + $message = $error->message(); + + if (strlen($message) > 0) { + return $message; + } + } + + return null; + } + public function push(IError $error): self { $copy = clone $this; diff --git a/src/Toolkit/Result/Result.php b/src/Toolkit/Result/Result.php index 2095261..e8549c7 100644 --- a/src/Toolkit/Result/Result.php +++ b/src/Toolkit/Result/Result.php @@ -115,13 +115,7 @@ public function errors(): IListOfErrors public function error(): ?string { - foreach ($this->errors as $error) { - if ($message = $error->message()) { - return $message; - } - } - - return null; + return $this->errors->message(); } public function meta(): Meta diff --git a/tests/Unit/Toolkit/Identifiers/GuidTest.php b/tests/Unit/Toolkit/Identifiers/GuidTest.php index 5cf3580..b4fff3a 100644 --- a/tests/Unit/Toolkit/Identifiers/GuidTest.php +++ b/tests/Unit/Toolkit/Identifiers/GuidTest.php @@ -59,6 +59,12 @@ public function testStringId(string|UnitEnum $type, string $value, string|UnitEn { $guid = Guid::fromString($type, '123'); + $others = [ + Guid::fromInteger($type, 123), + Guid::fromString($type, '234'), + Guid::fromString($other, '123'), + ]; + $this->assertInstanceOf(\Stringable::class, $guid); $this->assertSame($type, $guid->type); $this->assertObjectEquals(new StringId('123'), $guid->id); @@ -67,10 +73,13 @@ public function testStringId(string|UnitEnum $type, string $value, string|UnitEn $this->assertTrue($guid->isType($type)); $this->assertTrue($guid->is($guid)); $this->assertTrue($guid->is(clone $guid)); - $this->assertFalse($guid->is(Guid::fromInteger($type, 123))); - $this->assertFalse($guid->is(Guid::fromString($type, '234'))); - $this->assertFalse($guid->is(Guid::fromString($other, '123'))); + $this->assertTrue($guid->any($others[0], $others[1], clone $guid)); + $this->assertFalse($guid->is($others[0])); + $this->assertFalse($guid->is($others[1])); + $this->assertFalse($guid->is($others[2])); $this->assertFalse($guid->is(null)); + $this->assertFalse($guid->any(...$others)); + $this->assertFalse($guid->any(null, ...$others)); $this->assertSame(['type' => $value, 'id' => '123'], $guid->context()); $this->assertEquals($guid, Guid::fromString($type, '123')); $this->assertObjectEquals($guid, Guid::fromString($type, '123')); diff --git a/tests/Unit/Toolkit/Identifiers/IntegerIdTest.php b/tests/Unit/Toolkit/Identifiers/IntegerIdTest.php index a42a7c8..abb299c 100644 --- a/tests/Unit/Toolkit/Identifiers/IntegerIdTest.php +++ b/tests/Unit/Toolkit/Identifiers/IntegerIdTest.php @@ -50,6 +50,7 @@ public function testItIsEquals(): void $this->assertObjectEquals($id = new IntegerId(99), $other = IntegerId::from(99)); $this->assertSame($id, IntegerId::from($id)); $this->assertTrue($id->is($other)); + $this->assertTrue($id->any(new IntegerId(1), new IntegerId(2), $other)); } public function testItIsNotEqual(): void @@ -57,6 +58,8 @@ public function testItIsNotEqual(): void $id = new IntegerId(99); $this->assertFalse($id->equals($other = new IntegerId(100))); $this->assertFalse($id->is($other)); + $this->assertFalse($id->any($other, new IntegerId(200), new IntegerId(300))); + $this->assertFalse($id->any()); } /** @@ -77,6 +80,7 @@ public function testIsWithOtherIdentifiers(Identifier $other): void $id = new IntegerId(1); $this->assertFalse($id->is($other)); + $this->assertFalse($id->any(new IntegerId(2), null, $other)); } public function testIsWithNull(): void diff --git a/tests/Unit/Toolkit/Identifiers/StringIdTest.php b/tests/Unit/Toolkit/Identifiers/StringIdTest.php index 9564905..77ac42d 100644 --- a/tests/Unit/Toolkit/Identifiers/StringIdTest.php +++ b/tests/Unit/Toolkit/Identifiers/StringIdTest.php @@ -56,6 +56,7 @@ public function testItIsEquals(): void $this->assertObjectEquals($id = new StringId('99'), $other = StringId::from('99')); $this->assertSame($id, StringId::from($id)); $this->assertTrue($id->is($other)); + $this->assertTrue($id->any(new StringId('foo'), new StringId('bar'), null, $other)); } public function testItIsNotEqual(): void @@ -63,6 +64,8 @@ public function testItIsNotEqual(): void $id = new StringId('99'); $this->assertFalse($id->equals($other = new StringId('100'))); $this->assertFalse($id->is($other)); + $this->assertFalse($id->any(new StringId('foo'), new StringId('bar'), null, $other)); + $this->assertFalse($id->any()); } /** @@ -83,6 +86,7 @@ public function testIsWithOtherIdentifiers(Identifier $other): void $id = new StringId('1'); $this->assertFalse($id->is($other)); + $this->assertFalse($id->any($other, new StringId('foo'), null)); } public function testIsWithNull(): void diff --git a/tests/Unit/Toolkit/Identifiers/UuidTest.php b/tests/Unit/Toolkit/Identifiers/UuidTest.php index 47d1365..7f7febc 100644 --- a/tests/Unit/Toolkit/Identifiers/UuidTest.php +++ b/tests/Unit/Toolkit/Identifiers/UuidTest.php @@ -55,6 +55,7 @@ public function testItIsEquals(): void $this->assertObjectEquals($id = new Uuid($base), $other = Uuid::from($base)); $this->assertSame($id, Uuid::from($id)); $this->assertTrue($id->is($other)); + $this->assertTrue($id->any(null, Uuid::random(), $other)); } public function testItIsNotEqual(): void @@ -64,6 +65,7 @@ public function testItIsNotEqual(): void RamseyUuid::fromString('38c7be26-6887-4742-8b6b-7d07b30ca596'), ))); $this->assertFalse($id->is($other)); + $this->assertFalse($id->any(null, Uuid::random(), $other)); } /** @@ -84,6 +86,8 @@ public function testIsWithOtherIdentifiers(Identifier $other): void $id = new Uuid(RamseyUuid::fromString('6dcbad65-ed92-4e60-973b-9ba58a022816')); $this->assertFalse($id->is($other)); + $this->assertFalse($id->any(null, Uuid::random(), $other)); + $this->assertFalse($id->any()); } public function testIsWithNull(): void diff --git a/tests/Unit/Toolkit/Result/ListOfErrorsTest.php b/tests/Unit/Toolkit/Result/ListOfErrorsTest.php index b52e837..efb83bf 100644 --- a/tests/Unit/Toolkit/Result/ListOfErrorsTest.php +++ b/tests/Unit/Toolkit/Result/ListOfErrorsTest.php @@ -152,4 +152,36 @@ public function testCode(): void $this->assertSame(TestBackedEnum::Foo, $errors1->code()); $this->assertNull($errors2->code()); } + + public function testMessages(): void + { + $errors = new ListOfErrors( + new Error(message: 'Message A'), + new Error(message: 'Message B'), + new Error(code: TestUnitEnum::Baz), + new Error(message: 'Message A'), + new Error(message: 'Message C'), + ); + + $this->assertSame(['Message A', 'Message B', 'Message C'], $errors->messages()); + } + + public function testMessage(): void + { + $errors1 = new ListOfErrors( + new Error(message: 'Message A'), + new Error(message: 'Message B'), + new Error(code: TestUnitEnum::Baz), + new Error(message: 'Message A'), + new Error(message: 'Message C'), + ); + + $errors2 = new ListOfErrors( + new Error(code: TestUnitEnum::Baz), + new Error(code: TestBackedEnum::Foo), + ); + + $this->assertSame('Message A', $errors1->message()); + $this->assertNull($errors2->message()); + } }