5
5
namespace ComplexHeart \Domain \Model \Traits ;
6
6
7
7
use ComplexHeart \Domain \Model \Exceptions \InvariantViolation ;
8
- use Exception ;
8
+ use Throwable ;
9
+
10
+ use function Lambdish \Phunctional \map ;
9
11
10
12
/**
11
13
* Trait HasInvariants
15
17
*/
16
18
trait HasInvariants
17
19
{
20
+ /**
21
+ * Static property to keep cached invariants list to optimize performance.
22
+ *
23
+ * @var array<string, string[]>
24
+ */
25
+ protected static $ _invariantsCache = [];
26
+
18
27
/**
19
28
* Retrieve the object invariants.
20
29
*
21
30
* @return string[]
22
31
*/
23
32
final public static function invariants (): array
24
33
{
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
- }
34
+ if (array_key_exists (static ::class, static ::$ _invariantsCache ) === false ) {
35
+ $ invariants = [];
36
+ foreach (get_class_methods (static ::class) as $ invariant ) {
37
+ if (str_starts_with ($ invariant , 'invariant ' ) && !in_array ($ invariant ,
38
+ ['invariants ' , 'invariantHandler ' ])) {
39
+ $ invariantRuleName = preg_replace ('/[A-Z]([A-Z](?![a-z]))*/ ' , ' $0 ' , $ invariant );
40
+ if (is_null ($ invariantRuleName )) {
41
+ continue ;
42
+ }
32
43
33
- $ invariants [$ invariant ] = str_replace ('invariant ' , '' , strtolower ($ invariantRuleName ));
44
+ $ invariants [$ invariant ] = str_replace ('invariant ' , '' , strtolower ($ invariantRuleName ));
45
+ }
34
46
}
47
+
48
+ static ::$ _invariantsCache [static ::class] = $ invariants ;
35
49
}
36
50
37
- return $ invariants ;
51
+ return static :: $ _invariantsCache [ static ::class] ;
38
52
}
39
53
40
54
/**
@@ -52,57 +66,68 @@ final public static function invariants(): array
52
66
* If exception is thrown the error message will be the exception message.
53
67
*
54
68
* $onFail function must have the following signature:
55
- * fn(array<string, string >) => void
69
+ * fn(array<string, Throwable >) => void
56
70
*
57
71
* @param string|callable $onFail
72
+ * @param string $exception
58
73
*
59
74
* @return void
60
75
*/
61
- private function check (string |callable $ onFail = 'invariantHandler ' ): void
62
- {
63
- $ violations = $ this ->computeInvariantViolations ();
76
+ private function check (
77
+ string |callable $ onFail = 'invariantHandler ' ,
78
+ string $ exception = InvariantViolation::class
79
+ ): void {
80
+ $ violations = $ this ->computeInvariantViolations ($ exception );
64
81
if (!empty ($ violations )) {
65
- call_user_func_array ($ this ->computeInvariantHandler ($ onFail ), [$ violations ]);
82
+ call_user_func_array ($ this ->computeInvariantHandler ($ onFail, $ exception ), [$ violations ]);
66
83
}
67
84
}
68
85
69
86
/**
70
87
* Computes the list of invariant violations.
71
88
*
72
- * @return array<string, string>
89
+ * @param string $exception
90
+ *
91
+ * @return array<string, Throwable>
73
92
*/
74
- private function computeInvariantViolations (): array
93
+ private function computeInvariantViolations (string $ exception ): array
75
94
{
76
95
$ violations = [];
77
96
foreach (static ::invariants () as $ invariant => $ rule ) {
78
97
try {
79
98
if (!$ this ->{$ invariant }()) {
80
- $ violations [$ invariant ] = $ rule ;
99
+ /** @var array<string, Throwable> $violations */
100
+ $ violations [$ invariant ] = new $ exception ($ rule );
81
101
}
82
- } catch (Exception $ e ) {
83
- $ violations [$ invariant ] = $ e ->getMessage ();
102
+ } catch (Throwable $ e ) {
103
+ /** @var array<string, Throwable> $violations */
104
+ $ violations [$ invariant ] = $ e ;
84
105
}
85
106
}
86
107
87
108
return $ violations ;
88
109
}
89
110
90
- private function computeInvariantHandler (string |callable $ handlerFn ): callable
111
+ private function computeInvariantHandler (string |callable $ handlerFn, string $ exception ): callable
91
112
{
92
113
if (!is_string ($ handlerFn )) {
93
114
return $ handlerFn ;
94
115
}
95
116
96
117
return method_exists ($ this , $ handlerFn )
97
- ? function (array $ violations ) use ($ handlerFn ): void {
98
- $ this ->{$ handlerFn }($ violations );
118
+ ? function (array $ violations ) use ($ handlerFn, $ exception ): void {
119
+ $ this ->{$ handlerFn }($ violations, $ exception );
99
120
}
100
- : function (array $ violations ): void {
101
- throw new InvariantViolation (
121
+ : function (array $ violations ) use ($ exception ): void {
122
+ if (count ($ violations ) === 1 ) {
123
+ throw array_shift ($ violations );
124
+ }
125
+
126
+ throw new $ exception ( // @phpstan-ignore-line
102
127
sprintf (
103
- "Unable to create %s due %s " ,
128
+ "Unable to create %s due: %s " ,
104
129
basename (str_replace ('\\' , '/ ' , static ::class)),
105
- implode (", " , $ violations ),
130
+ implode (", " , map ( fn ( Throwable $ e ): string => $ e -> getMessage (), $ violations) ),
106
131
107
132
)
108
133
);
0 commit comments