88use Saloon \Enums \PipeOrder ;
99use Saloon \Http \PendingRequest ;
1010use Saloon \Contracts \FakeResponse ;
11+ use Saloon \Exceptions \Request \FatalRequestException ;
1112
1213class MiddlewarePipeline
1314{
@@ -21,13 +22,19 @@ class MiddlewarePipeline
2122 */
2223 protected Pipeline $ responsePipeline ;
2324
25+ /**
26+ * Fatal Pipeline
27+ */
28+ protected Pipeline $ fatalPipeline ;
29+
2430 /**
2531 * Constructor
2632 */
2733 public function __construct ()
2834 {
2935 $ this ->requestPipeline = new Pipeline ;
3036 $ this ->responsePipeline = new Pipeline ;
37+ $ this ->fatalPipeline = new Pipeline ;
3138 }
3239
3340 /**
@@ -92,6 +99,33 @@ public function onResponse(callable $callable, ?string $name = null, ?PipeOrder
9299 return $ this ;
93100 }
94101
102+ /**
103+ * Add a middleware to run on fatal errors
104+ *
105+ * @param callable(FatalRequestException): (void) $callable
106+ * @return $this
107+ */
108+ public function onFatalException (callable $ callable , ?string $ name = null , ?PipeOrder $ order = null ): static
109+ {
110+ /**
111+ * For some reason, PHP is not destructing non-static Closures, or 'things' using non-static Closures, correctly, keeping unused objects intact.
112+ * Using a *static* Closure, or re-binding it to an empty, anonymous class/object is a workaround for the issue.
113+ * If we don't, things using the MiddlewarePipeline, in turn, won't destruct.
114+ * Concretely speaking, for Saloon, this means that the Connector will *not* get destructed, and thereby also not the underlying client.
115+ * Which in turn leaves open file handles until the process terminates.
116+ *
117+ * Do note that this is entirely about our *wrapping* Closure below.
118+ * The provided callable doesn't affect the MiddlewarePipeline.
119+ */
120+ $ this ->fatalPipeline ->pipe (static function (FatalRequestException $ throwable ) use ($ callable ): FatalRequestException {
121+ $ callable ($ throwable );
122+
123+ return $ throwable ;
124+ }, $ name , $ order );
125+
126+ return $ this ;
127+ }
128+
95129 /**
96130 * Process the request pipeline.
97131 */
@@ -108,6 +142,15 @@ public function executeResponsePipeline(Response $response): Response
108142 return $ this ->responsePipeline ->process ($ response );
109143 }
110144
145+ /**
146+ * Process the fatal pipeline.
147+ * @throws \Saloon\Exceptions\Request\FatalRequestException
148+ */
149+ public function executeFatalPipeline (FatalRequestException $ throwable ): void
150+ {
151+ $ this ->fatalPipeline ->process ($ throwable );
152+ }
153+
111154 /**
112155 * Merge in another middleware pipeline.
113156 *
@@ -125,8 +168,14 @@ public function merge(MiddlewarePipeline $middlewarePipeline): static
125168 $ middlewarePipeline ->getResponsePipeline ()->getPipes ()
126169 );
127170
171+ $ fatalPipes = array_merge (
172+ $ this ->getFatalPipeline ()->getPipes (),
173+ $ middlewarePipeline ->getFatalPipeline ()->getPipes ()
174+ );
175+
128176 $ this ->requestPipeline ->setPipes ($ requestPipes );
129177 $ this ->responsePipeline ->setPipes ($ responsePipes );
178+ $ this ->fatalPipeline ->setPipes ($ fatalPipes );
130179
131180 return $ this ;
132181 }
@@ -146,4 +195,12 @@ public function getResponsePipeline(): Pipeline
146195 {
147196 return $ this ->responsePipeline ;
148197 }
198+
199+ /**
200+ * Get the fatal pipeline
201+ */
202+ public function getFatalPipeline (): Pipeline
203+ {
204+ return $ this ->fatalPipeline ;
205+ }
149206}
0 commit comments