1515use Throwable ;
1616use TheWebSolver \Codegarage \Lib \PipeInterface as Pipe ;
1717
18- /** Pipeline to follow the Chain of Responsibility Design Pattern. */
1918class Pipeline {
20- /** The subject that gets transformed when passed through various pipes. */
2119 protected mixed $ subject ;
2220
23- /**
24- * Pipes being used.
25- *
26- * @var array<string|Closure|Pipe>
27- */
21+ /** @var array<string|Closure|Pipe> */
2822 protected array $ pipes = array ();
2923
30- /**
31- * Global arguments accepted by pipe's func/method parameter.
32- *
33- * @var mixed[]
34- */
24+ /** @var mixed[] */
3525 protected array $ use ;
3626
3727 protected Closure $ catcher ;
3828
3929 /**
30+ * @param string|Pipe|Closure(mixed $subject, Closure $next, mixed ...$use): mixed $pipe
4031 * @throws InvalidPipe When invalid pipe given.
4132 * @throws InvalidPipeline When could not determine thrown exception.
4233 * @phpstan-param class-string<Pipe>|Pipe|Closure(mixed $subject, Closure $next, mixed ...$use): mixed $pipe
@@ -53,35 +44,11 @@ final public static function resolve( string|Closure|Pipe $pipe ): Closure {
5344 $ pipe instanceof Closure => $ pipe ,
5445 };
5546 } catch ( Throwable $ e ) {
56- if ( $ e instanceof InvalidPipe ) {
57- throw $ e ;
58- }
59-
60- throw new InvalidPipeline ( $ e ->getMessage (), $ e ->getCode (), $ e );
47+ self ::throw ( $ e );
6148 }
6249 }
6350
64- /**
65- * Sets the global arguments accepted by pipe's func/method parameter.
66- *
67- * @example usage
68- * ```
69- * $param1 = is_string(...);
70- * $param2 = strtoupper(...);
71- * $subject = ' convert this to all caps ';
72- * $result = (new Pipeline())
73- * ->use($param1, $param2)
74- * ->send($subject)
75- * ->through(pipes: [
76- * // Each pipe func/method accepts [#1] $subject and [#2] $next arguments by default.
77- * // Then, $param1 = [#3] $isString; $param2 = [#4] $capitalize as global args.
78- * // Not the best example but serves the purpose of passing additional args.
79- * static function(string $subject, Closure $next, Closure $isString, Closure $capitalize) {
80- * return $next(!$isString($subject) ? '' : $capitalize($subject));
81- * }
82- * ])->thenReturn();
83- * ```
84- */
51+ /** Sets the global arguments accepted by the pipe handler. */
8552 public function use ( mixed ...$ args ): static {
8653 $ this ->use = $ args ;
8754
@@ -116,29 +83,7 @@ public function through( array $pipes ): static {
11683 return $ this ;
11784 }
11885
119- /**
120- * Captures the pipe's exception that will blatantly abrupt the whole pipeline flow.
121- *
122- * When short-circuited pipeline will prevent remaining pipes operation, it fixes.
123- * Any exception thrown by the failing pipe can then be handled by the user.
124- * Its captured value will be returned instead of the `Pipeline::then()`.
125- *
126- * @param Closure(Throwable $e, mixed ...$use): mixed $fallback
127- * @example usage
128- * ```
129- * $result = (new Pipeline())
130- * ->send(subject: [])
131- * // Pipeline abrupt and empty string returned because first pipe throws an exception.
132- * ->sealWith(fallback: fn(\TypeError $e) => '')
133- * ->through(pipes: [
134- * fn(mixed $subject, Closure $next) => is_string($subject)
135- * ? $next(trim($subject))
136- * : throw new \TypeError('Subject must be a string.'),
137- * // Pipeline never passes subject to this pipe.
138- * fn(mixed $subject, Closure $next) => $next(strtolower($subject))
139- * ])->thenReturn();
140- * ```
141- */
86+ /** Captures the pipe's exception that will blatantly abrupt the whole pipeline flow. */
14287 public function sealWith ( Closure $ fallback ): static {
14388 $ this ->catcher = $ fallback ;
14489
@@ -148,7 +93,7 @@ public function sealWith( Closure $fallback ): static {
14893 /**
14994 * Appends additional pipes to the pipeline stack.
15095 *
151- * @param string|Pipe|Closure(mixed $subject, Closure $next, mixed ...$use): mixed
96+ * @param string|Pipe|Closure(mixed $subject, Closure $next, mixed ...$use): mixed $pipe
15297 * @phpstan-param class-string<Pipe>|Pipe|Closure(mixed $subject, Closure $next, mixed ...$use): mixed $pipe
15398 */
15499 public function pipe ( string |Closure |Pipe $ pipe ): static {
@@ -173,20 +118,14 @@ public function then( Closure $return ): mixed {
173118 try {
174119 return array_reduce ( $ pipes , $ this ->chain ( ... ), $ return )( $ subject , ...$ use );
175120 } catch ( Throwable $ e ) {
176- if ( $ catcher = $ this ->catcher ?? null ) {
177- return $ catcher ( $ e , ...$ use );
178- }
179-
180- return $ e instanceof InvalidPipe
181- ? throw $ e
182- : throw new InvalidPipeline ( $ e ->getMessage (), $ e ->getCode (), $ e );
121+ return ( $ seal = $ this ->catcher ?? null ) ? $ seal ( $ e , ...$ use ) : self ::throw ( $ e );
183122 }
184123 }
185124
186125 /**
187126 * Passes through pipes in the pipeline and returns the transformed result.
188127 *
189- * @throws InvalidPipe When pipe type could not be resolved.
128+ * @throws InvalidPipe When pipe type could not be resolved.
190129 * @throws InvalidPipeline When a pipe abrupt the pipeline by throwing an exception & sealWith not used.
191130 */
192131 public function thenReturn () {
@@ -197,4 +136,10 @@ public function thenReturn() {
197136 protected function chain ( Closure $ next , string |Closure |Pipe $ current ): Closure {
198137 return fn ( $ subject ) => self ::resolve ( $ current )( $ subject , $ next , ...( $ this ->use ?? array () ) );
199138 }
139+
140+ private static function throw ( Throwable $ e ): never {
141+ throw $ e instanceof InvalidPipe
142+ ? $ e
143+ : new InvalidPipeline ( $ e ->getMessage (), $ e ->getCode (), $ e );
144+ }
200145}
0 commit comments