Skip to content

Commit 8d1322c

Browse files
committed
Feature/Invariant system now throws user defined exceptions.
1 parent 578dba6 commit 8d1322c

File tree

2 files changed

+55
-37
lines changed

2 files changed

+55
-37
lines changed

src/Traits/HasInvariants.php

Lines changed: 44 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
namespace ComplexHeart\Domain\Model\Traits;
66

77
use ComplexHeart\Domain\Model\Exceptions\InvariantViolation;
8-
use Exception;
8+
use Throwable;
99

1010
/**
1111
* Trait HasInvariants
@@ -15,26 +15,38 @@
1515
*/
1616
trait HasInvariants
1717
{
18+
/**
19+
* Static property to keep cached invariants list to optimize performance.
20+
*
21+
* @var array<string, <string, string>>
22+
*/
23+
private static $_invariantsCache = [];
24+
1825
/**
1926
* Retrieve the object invariants.
2027
*
2128
* @return string[]
2229
*/
2330
final public static function invariants(): array
2431
{
25-
$invariants = [];
26-
foreach (get_class_methods(static::class) as $invariant) {
27-
if (str_starts_with($invariant, 'invariant') && !in_array($invariant, ['invariants', 'invariantHandler'])) {
28-
$invariantRuleName = preg_replace('/[A-Z]([A-Z](?![a-z]))*/', ' $0', $invariant);
29-
if (is_null($invariantRuleName)) {
30-
continue;
31-
}
32+
if (array_key_exists(static::class, static::$_invariantsCache) === false) {
33+
$invariants = [];
34+
foreach (get_class_methods(static::class) as $invariant) {
35+
if (str_starts_with($invariant, 'invariant') && !in_array($invariant,
36+
['invariants', 'invariantHandler'])) {
37+
$invariantRuleName = preg_replace('/[A-Z]([A-Z](?![a-z]))*/', ' $0', $invariant);
38+
if (is_null($invariantRuleName)) {
39+
continue;
40+
}
3241

33-
$invariants[$invariant] = str_replace('invariant ', '', strtolower($invariantRuleName));
42+
$invariants[$invariant] = str_replace('invariant ', '', strtolower($invariantRuleName));
43+
}
3444
}
45+
46+
static::$_invariantsCache[static::class] = $invariants;
3547
}
3648

37-
return $invariants;
49+
return static::$_invariantsCache[static::class];
3850
}
3951

4052
/**
@@ -52,53 +64,60 @@ final public static function invariants(): array
5264
* If exception is thrown the error message will be the exception message.
5365
*
5466
* $onFail function must have the following signature:
55-
* fn(array<string, string>) => void
67+
* fn(array<string, Throwable>) => void
5668
*
5769
* @param string|callable $onFail
70+
* @param string $exception
5871
*
5972
* @return void
6073
*/
61-
private function check(string|callable $onFail = 'invariantHandler'): void
62-
{
63-
$violations = $this->computeInvariantViolations();
74+
private function check(
75+
string|callable $onFail = 'invariantHandler',
76+
string $exception = InvariantViolation::class
77+
): void {
78+
$violations = $this->computeInvariantViolations($exception);
6479
if (!empty($violations)) {
65-
call_user_func_array($this->computeInvariantHandler($onFail), [$violations]);
80+
call_user_func_array($this->computeInvariantHandler($onFail, $exception), [$violations]);
6681
}
6782
}
6883

6984
/**
7085
* Computes the list of invariant violations.
7186
*
72-
* @return array<string, string>
87+
* @return array<string, Throwable>
7388
*/
74-
private function computeInvariantViolations(): array
89+
private function computeInvariantViolations(string $exception): array
7590
{
7691
$violations = [];
7792
foreach (static::invariants() as $invariant => $rule) {
7893
try {
7994
if (!$this->{$invariant}()) {
80-
$violations[$invariant] = $rule;
95+
$violations[$invariant] = new $exception($rule);
8196
}
82-
} catch (Exception $e) {
83-
$violations[$invariant] = $e->getMessage();
97+
} catch (Throwable $e) {
98+
$violations[$invariant] = $e;
8499
}
85100
}
86101

87102
return $violations;
88103
}
89104

90-
private function computeInvariantHandler(string|callable $handlerFn): callable
105+
private function computeInvariantHandler(string|callable $handlerFn, string $exception): callable
91106
{
92107
if (!is_string($handlerFn)) {
93108
return $handlerFn;
94109
}
95110

96111
return method_exists($this, $handlerFn)
97-
? function (array $violations) use ($handlerFn): void {
98-
$this->{$handlerFn}($violations);
112+
? function (array $violations) use ($handlerFn, $exception): void {
113+
$this->{$handlerFn}($violations, $exception);
99114
}
100-
: function (array $violations): void {
101-
throw new InvariantViolation(
115+
: function (array $violations) use ($exception): void {
116+
if (count($violations) === 1) {
117+
throw array_shift($violations);
118+
}
119+
120+
throw new $exception(
102121
sprintf(
103122
"Unable to create %s due %s",
104123
basename(str_replace('\\', '/', static::class)),

tests/ValueObjectsTest.php

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@
5555
protected string $_pattern = '[a-z]';
5656
};
5757
})
58-
->throws(InvariantViolation::class)
58+
->throws(InvalidArgumentException::class)
5959
->group('Unit');
6060

6161
test('BooleanValue should create a valid BooleanValue Object.', function () {
@@ -157,10 +157,10 @@
157157
protected string $valueType = 'string';
158158
};
159159

160-
expect($vo)->toHaveCount(2);
161-
expect($vo)->toBeIterable();
162-
expect($vo->getIterator())->toBeInstanceOf(ArrayIterator::class);
163-
expect($vo[0])->toEqual('one');
160+
expect($vo)->toHaveCount(2)
161+
->and($vo)->toBeIterable()
162+
->and($vo->getIterator())->toBeInstanceOf(ArrayIterator::class)
163+
->and($vo[0])->toEqual('one');
164164
})
165165
->group('Unit');
166166

@@ -223,8 +223,8 @@
223223
test('UUIDValue should create a valid UUIDValue Object.', function () {
224224
$vo = UUIDValue::random();
225225

226-
expect($vo->is($vo))->toBeTrue();
227-
expect((string) $vo)->toEqual($vo->__toString());
226+
expect($vo->is($vo))->toBeTrue()
227+
->and((string) $vo)->toEqual($vo->__toString());
228228
})
229229
->group('Unit');
230230

@@ -241,10 +241,9 @@
241241
const TWO = 'two';
242242
};
243243

244-
expect($vo->value())->toBe('one');
245-
expect($vo->value())->toBe((string) $vo);
246-
247-
expect($vo::getLabels()[0])->toBe('ONE');
248-
expect($vo::getLabels()[1])->toBe('TWO');
244+
expect($vo->value())->toBe('one')
245+
->and($vo->value())->toBe((string) $vo)
246+
->and($vo::getLabels()[0])->toBe('ONE')
247+
->and($vo::getLabels()[1])->toBe('TWO');
249248
})
250249
->group('Unit');

0 commit comments

Comments
 (0)