55namespace ComplexHeart \Domain \Model \Traits ;
66
77use ComplexHeart \Domain \Model \Exceptions \InvariantViolation ;
8- use Exception ;
8+ use Throwable ;
9+
10+ use function Lambdish \Phunctional \map ;
911
1012/**
1113 * Trait HasInvariants
1517 */
1618trait HasInvariants
1719{
20+ /**
21+ * Static property to keep cached invariants list to optimize performance.
22+ *
23+ * @var array<string, string[]>
24+ */
25+ protected static $ _invariantsCache = [];
26+
1827 /**
1928 * Retrieve the object invariants.
2029 *
2130 * @return string[]
2231 */
2332 final public static function invariants (): array
2433 {
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+ }
3243
33- $ invariants [$ invariant ] = str_replace ('invariant ' , '' , strtolower ($ invariantRuleName ));
44+ $ invariants [$ invariant ] = str_replace ('invariant ' , '' , strtolower ($ invariantRuleName ));
45+ }
3446 }
47+
48+ static ::$ _invariantsCache [static ::class] = $ invariants ;
3549 }
3650
37- return $ invariants ;
51+ return static :: $ _invariantsCache [ static ::class] ;
3852 }
3953
4054 /**
@@ -52,57 +66,68 @@ final public static function invariants(): array
5266 * If exception is thrown the error message will be the exception message.
5367 *
5468 * $onFail function must have the following signature:
55- * fn(array<string, string >) => void
69+ * fn(array<string, Throwable >) => void
5670 *
5771 * @param string|callable $onFail
72+ * @param string $exception
5873 *
5974 * @return void
6075 */
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 );
6481 if (!empty ($ violations )) {
65- call_user_func_array ($ this ->computeInvariantHandler ($ onFail ), [$ violations ]);
82+ call_user_func_array ($ this ->computeInvariantHandler ($ onFail, $ exception ), [$ violations ]);
6683 }
6784 }
6885
6986 /**
7087 * Computes the list of invariant violations.
7188 *
72- * @return array<string, string>
89+ * @param string $exception
90+ *
91+ * @return array<string, Throwable>
7392 */
74- private function computeInvariantViolations (): array
93+ private function computeInvariantViolations (string $ exception ): array
7594 {
7695 $ violations = [];
7796 foreach (static ::invariants () as $ invariant => $ rule ) {
7897 try {
7998 if (!$ this ->{$ invariant }()) {
80- $ violations [$ invariant ] = $ rule ;
99+ /** @var array<string, Throwable> $violations */
100+ $ violations [$ invariant ] = new $ exception ($ rule );
81101 }
82- } catch (Exception $ e ) {
83- $ violations [$ invariant ] = $ e ->getMessage ();
102+ } catch (Throwable $ e ) {
103+ /** @var array<string, Throwable> $violations */
104+ $ violations [$ invariant ] = $ e ;
84105 }
85106 }
86107
87108 return $ violations ;
88109 }
89110
90- private function computeInvariantHandler (string |callable $ handlerFn ): callable
111+ private function computeInvariantHandler (string |callable $ handlerFn, string $ exception ): callable
91112 {
92113 if (!is_string ($ handlerFn )) {
93114 return $ handlerFn ;
94115 }
95116
96117 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 );
99120 }
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
102127 sprintf (
103- "Unable to create %s due %s " ,
128+ "Unable to create %s due: %s " ,
104129 basename (str_replace ('\\' , '/ ' , static ::class)),
105- implode (", " , $ violations ),
130+ implode (", " , map ( fn ( Throwable $ e ): string => $ e -> getMessage (), $ violations) ),
106131
107132 )
108133 );
0 commit comments