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
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
26 changes: 25 additions & 1 deletion docs/guide/upgrade.md
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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.

Expand Down
5 changes: 5 additions & 0 deletions src/Contracts/Toolkit/Identifiers/Identifier.php
Original file line number Diff line number Diff line change
Expand Up @@ -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.
*/
Expand Down
16 changes: 15 additions & 1 deletion src/Contracts/Toolkit/Result/ListOfErrors.php
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,7 @@ public function contains(Closure|UnitEnum $matcher): bool;
/**
* Get all the unique error codes in the list.
*
* @return array<UnitEnum>
* @return list<UnitEnum>
*/
public function codes(): array;

Expand All @@ -47,6 +47,20 @@ public function codes(): array;
*/
public function code(): ?UnitEnum;

/**
* Get all the unique error messages in the list.
*
* @return list<non-empty-string>
*/
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.
*
Expand Down
2 changes: 2 additions & 0 deletions src/Toolkit/Identifiers/Guid.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@

final readonly class Guid implements Identifier
{
use IsIdentifier;

public static function from(Identifier $value): self
{
if ($value instanceof self) {
Expand Down
2 changes: 2 additions & 0 deletions src/Toolkit/Identifiers/IntegerId.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

final readonly class IntegerId implements Identifier, JsonSerializable
{
use IsIdentifier;

public static function from(Identifier|int $value): self
{
return match(true) {
Expand Down
26 changes: 26 additions & 0 deletions src/Toolkit/Identifiers/IsIdentifier.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
<?php

/*
* Copyright 2025 Cloud Creativity Limited
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/

declare(strict_types=1);

namespace CloudCreativity\Modules\Toolkit\Identifiers;

use CloudCreativity\Modules\Contracts\Toolkit\Identifiers\Identifier;

trait IsIdentifier
{
public function any(?Identifier ...$others): bool
{
return array_any(
$others,
fn (?Identifier $other): bool => $this->is($other),
);
}
}
2 changes: 2 additions & 0 deletions src/Toolkit/Identifiers/StringId.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,8 @@

final readonly class StringId implements Identifier, JsonSerializable
{
use IsIdentifier;

public static function from(Identifier|string $value): self
{
return match(true) {
Expand Down
2 changes: 2 additions & 0 deletions src/Toolkit/Identifiers/Uuid.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

final class Uuid implements Identifier, JsonSerializable
{
use IsIdentifier;

private static ?IUuidFactory $factory = null;

public static function setFactory(?IUuidFactory $factory): void
Expand Down
28 changes: 28 additions & 0 deletions src/Toolkit/Result/ListOfErrors.php
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
8 changes: 1 addition & 7 deletions src/Toolkit/Result/Result.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
15 changes: 12 additions & 3 deletions tests/Unit/Toolkit/Identifiers/GuidTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand All @@ -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'));
Expand Down
4 changes: 4 additions & 0 deletions tests/Unit/Toolkit/Identifiers/IntegerIdTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,16 @@ 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
{
$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());
}

/**
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions tests/Unit/Toolkit/Identifiers/StringIdTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -56,13 +56,16 @@ 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
{
$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());
}

/**
Expand All @@ -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
Expand Down
4 changes: 4 additions & 0 deletions tests/Unit/Toolkit/Identifiers/UuidTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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));
}

/**
Expand All @@ -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
Expand Down
32 changes: 32 additions & 0 deletions tests/Unit/Toolkit/Result/ListOfErrorsTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -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());
}
}