44
55namespace ComplexHeart \Domain \Model \Traits ;
66
7- use ComplexHeart \Domain \Model \Contracts \Aggregatable ;
7+ use ComplexHeart \Domain \Model \Exceptions \Contracts \Aggregatable ;
8+ use ComplexHeart \Domain \Model \Exceptions \Contracts \AggregatesErrors ;
89use ComplexHeart \Domain \Model \Exceptions \InvariantViolation ;
910use Throwable ;
1011
11- use function Lambdish \Phunctional \map ;
12-
1312/**
1413 * Trait HasInvariants
1514 *
@@ -139,8 +138,9 @@ private function computeInvariantHandler(string|callable $handlerFn, string $exc
139138 * Throw invariant violations (single or aggregated).
140139 *
141140 * Responsible for all exception throwing logic:
142- * - Non-aggregatable exceptions: throw the first one immediately
143- * - Aggregatable exceptions: aggregate and throw as InvariantViolation
141+ * - If $exception class doesn't support aggregation: throw first violation immediately
142+ * - If individual violations are non-aggregatable: throw the first one immediately
143+ * - If all conditions pass: aggregate violations using $exception class
144144 *
145145 * @param array<string, Throwable> $violations
146146 * @param string $exception
@@ -149,6 +149,12 @@ private function computeInvariantHandler(string|callable $handlerFn, string $exc
149149 */
150150 private function throwInvariantViolations (array $ violations , string $ exception ): void
151151 {
152+ // Early check: Does the exception class support aggregation?
153+ if (!is_subclass_of ($ exception , AggregatesErrors::class)) {
154+ // @phpstan-ignore-next-line (array_shift always returns Throwable from non-empty violations array)
155+ throw array_shift ($ violations );
156+ }
157+
152158 // Separate aggregatable from non-aggregatable violations
153159 $ aggregatable = [];
154160 $ nonAggregatable = [];
@@ -163,13 +169,14 @@ private function throwInvariantViolations(array $violations, string $exception):
163169
164170 // If there are non-aggregatable exceptions, throw the first one immediately
165171 if (!empty ($ nonAggregatable )) {
172+ // @phpstan-ignore-next-line (array_shift always returns Throwable from non-empty array)
166173 throw array_shift ($ nonAggregatable );
167174 }
168175
169- // All violations are aggregatable - aggregate them
176+ // All violations are aggregatable - aggregate them using the provided exception class
170177 if (!empty ($ aggregatable )) {
171- $ messages = map ( fn ( Throwable $ e ): string => $ e -> getMessage (), $ aggregatable );
172- throw InvariantViolation:: fromViolations (array_values ($ messages ));
178+ // @phpstan-ignore-next-line (fromErrors returns Throwable instance implementing AggregatesErrors)
179+ throw $ exception :: fromErrors (array_values ($ aggregatable ));
173180 }
174181 }
175182}
0 commit comments