8
8
use Saloon \Enums \PipeOrder ;
9
9
use Saloon \Http \PendingRequest ;
10
10
use Saloon \Contracts \FakeResponse ;
11
+ use Saloon \Exceptions \Request \FatalRequestException ;
11
12
12
13
class MiddlewarePipeline
13
14
{
@@ -21,13 +22,19 @@ class MiddlewarePipeline
21
22
*/
22
23
protected Pipeline $ responsePipeline ;
23
24
25
+ /**
26
+ * Fatal Pipeline
27
+ */
28
+ protected Pipeline $ fatalPipeline ;
29
+
24
30
/**
25
31
* Constructor
26
32
*/
27
33
public function __construct ()
28
34
{
29
35
$ this ->requestPipeline = new Pipeline ;
30
36
$ this ->responsePipeline = new Pipeline ;
37
+ $ this ->fatalPipeline = new Pipeline ;
31
38
}
32
39
33
40
/**
@@ -92,6 +99,33 @@ public function onResponse(callable $callable, ?string $name = null, ?PipeOrder
92
99
return $ this ;
93
100
}
94
101
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
+
95
129
/**
96
130
* Process the request pipeline.
97
131
*/
@@ -108,6 +142,15 @@ public function executeResponsePipeline(Response $response): Response
108
142
return $ this ->responsePipeline ->process ($ response );
109
143
}
110
144
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
+
111
154
/**
112
155
* Merge in another middleware pipeline.
113
156
*
@@ -125,8 +168,14 @@ public function merge(MiddlewarePipeline $middlewarePipeline): static
125
168
$ middlewarePipeline ->getResponsePipeline ()->getPipes ()
126
169
);
127
170
171
+ $ fatalPipes = array_merge (
172
+ $ this ->getFatalPipeline ()->getPipes (),
173
+ $ middlewarePipeline ->getFatalPipeline ()->getPipes ()
174
+ );
175
+
128
176
$ this ->requestPipeline ->setPipes ($ requestPipes );
129
177
$ this ->responsePipeline ->setPipes ($ responsePipes );
178
+ $ this ->fatalPipeline ->setPipes ($ fatalPipes );
130
179
131
180
return $ this ;
132
181
}
@@ -146,4 +195,12 @@ public function getResponsePipeline(): Pipeline
146
195
{
147
196
return $ this ->responsePipeline ;
148
197
}
198
+
199
+ /**
200
+ * Get the fatal pipeline
201
+ */
202
+ public function getFatalPipeline (): Pipeline
203
+ {
204
+ return $ this ->fatalPipeline ;
205
+ }
149
206
}
0 commit comments