15
15
use Throwable ;
16
16
use TheWebSolver \Codegarage \Lib \PipeInterface as Pipe ;
17
17
18
- /** Pipeline to follow the Chain of Responsibility Design Pattern. */
19
18
class Pipeline {
20
- /** The subject that gets transformed when passed through various pipes. */
21
19
protected mixed $ subject ;
22
20
23
- /**
24
- * Pipes being used.
25
- *
26
- * @var array<string|Closure|Pipe>
27
- */
21
+ /** @var array<string|Closure|Pipe> */
28
22
protected array $ pipes = array ();
29
23
30
- /**
31
- * Global arguments accepted by pipe's func/method parameter.
32
- *
33
- * @var mixed[]
34
- */
24
+ /** @var mixed[] */
35
25
protected array $ use ;
36
26
37
27
protected Closure $ catcher ;
38
28
39
29
/**
30
+ * @param string|Pipe|Closure(mixed $subject, Closure $next, mixed ...$use): mixed $pipe
40
31
* @throws InvalidPipe When invalid pipe given.
41
32
* @throws InvalidPipeline When could not determine thrown exception.
42
33
* @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 {
53
44
$ pipe instanceof Closure => $ pipe ,
54
45
};
55
46
} 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 );
61
48
}
62
49
}
63
50
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. */
85
52
public function use ( mixed ...$ args ): static {
86
53
$ this ->use = $ args ;
87
54
@@ -116,29 +83,7 @@ public function through( array $pipes ): static {
116
83
return $ this ;
117
84
}
118
85
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. */
142
87
public function sealWith ( Closure $ fallback ): static {
143
88
$ this ->catcher = $ fallback ;
144
89
@@ -148,7 +93,7 @@ public function sealWith( Closure $fallback ): static {
148
93
/**
149
94
* Appends additional pipes to the pipeline stack.
150
95
*
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
152
97
* @phpstan-param class-string<Pipe>|Pipe|Closure(mixed $subject, Closure $next, mixed ...$use): mixed $pipe
153
98
*/
154
99
public function pipe ( string |Closure |Pipe $ pipe ): static {
@@ -173,20 +118,14 @@ public function then( Closure $return ): mixed {
173
118
try {
174
119
return array_reduce ( $ pipes , $ this ->chain ( ... ), $ return )( $ subject , ...$ use );
175
120
} 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 );
183
122
}
184
123
}
185
124
186
125
/**
187
126
* Passes through pipes in the pipeline and returns the transformed result.
188
127
*
189
- * @throws InvalidPipe When pipe type could not be resolved.
128
+ * @throws InvalidPipe When pipe type could not be resolved.
190
129
* @throws InvalidPipeline When a pipe abrupt the pipeline by throwing an exception & sealWith not used.
191
130
*/
192
131
public function thenReturn () {
@@ -197,4 +136,10 @@ public function thenReturn() {
197
136
protected function chain ( Closure $ next , string |Closure |Pipe $ current ): Closure {
198
137
return fn ( $ subject ) => self ::resolve ( $ current )( $ subject , $ next , ...( $ this ->use ?? array () ) );
199
138
}
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
+ }
200
145
}
0 commit comments