|
5 | 5 | use Exception; |
6 | 6 | use GuzzleHttp\Psr7\Response; |
7 | 7 | use GuzzleHttp\RequestOptions; |
| 8 | +use GuzzleHttp\Client as GuzzleClient; |
| 9 | +use Psr\Http\Message\ResponseInterface; |
| 10 | +use GuzzleHttp\Promise\PromiseInterface; |
8 | 11 | use Sammyjo20\Saloon\Clients\MockClient; |
9 | 12 | use Sammyjo20\Saloon\Http\SaloonRequest; |
| 13 | +use GuzzleHttp\Exception\GuzzleException; |
10 | 14 | use Sammyjo20\Saloon\Http\SaloonResponse; |
11 | 15 | use GuzzleHttp\Exception\RequestException; |
12 | 16 | use Sammyjo20\Saloon\Http\SaloonConnector; |
|
15 | 19 | use Sammyjo20\Saloon\Traits\ManagesPlugins; |
16 | 20 | use Sammyjo20\Saloon\Clients\BaseMockClient; |
17 | 21 | use Sammyjo20\Saloon\Traits\CollectsHeaders; |
| 22 | +use GuzzleHttp\Psr7\Request as GuzzleRequest; |
18 | 23 | use Sammyjo20\Saloon\Traits\CollectsHandlers; |
19 | 24 | use GuzzleHttp\Exception\BadResponseException; |
20 | 25 | use Sammyjo20\Saloon\Traits\CollectsQueryParams; |
@@ -69,20 +74,29 @@ class RequestManager |
69 | 74 | */ |
70 | 75 | protected ?BaseMockClient $mockClient = null; |
71 | 76 |
|
| 77 | + /** |
| 78 | + * Determines if the request should be sent asynchronously. |
| 79 | + * |
| 80 | + * @var bool |
| 81 | + */ |
| 82 | + protected bool $asynchronous = false; |
| 83 | + |
72 | 84 | /** |
73 | 85 | * Construct the request manager |
74 | 86 | * |
75 | 87 | * @param SaloonRequest $request |
76 | 88 | * @param MockClient|null $mockClient |
| 89 | + * @param bool $asynchronous |
77 | 90 | * @throws SaloonMultipleMockMethodsException |
78 | 91 | * @throws SaloonNoMockResponsesProvidedException |
79 | 92 | * @throws \Sammyjo20\Saloon\Exceptions\SaloonInvalidConnectorException |
80 | 93 | */ |
81 | | - public function __construct(SaloonRequest $request, MockClient $mockClient = null) |
| 94 | + public function __construct(SaloonRequest $request, MockClient $mockClient = null, bool $asynchronous = false) |
82 | 95 | { |
83 | 96 | $this->request = $request; |
84 | 97 | $this->connector = $request->getConnector(); |
85 | 98 | $this->inLaravelEnvironment = $this->detectLaravel(); |
| 99 | + $this->asynchronous = $asynchronous; |
86 | 100 |
|
87 | 101 | $this->bootLaravelManager(); |
88 | 102 | $this->bootMockClient($mockClient); |
@@ -151,42 +165,97 @@ public function hydrate(): void |
151 | 165 | } |
152 | 166 |
|
153 | 167 | /** |
154 | | - * Send off the message... 🚀 |
| 168 | + * Send off the request 🚀 |
155 | 169 | * |
156 | | - * @return SaloonResponse |
| 170 | + * @return SaloonResponse|PromiseInterface |
| 171 | + * @throws SaloonInvalidResponseClassException |
157 | 172 | * @throws \GuzzleHttp\Exception\GuzzleException |
158 | 173 | * @throws \ReflectionException |
159 | 174 | * @throws \Sammyjo20\Saloon\Exceptions\SaloonDuplicateHandlerException |
160 | 175 | * @throws \Sammyjo20\Saloon\Exceptions\SaloonInvalidConnectorException |
161 | 176 | * @throws \Sammyjo20\Saloon\Exceptions\SaloonInvalidHandlerException |
162 | | - * @throws \Sammyjo20\Saloon\Exceptions\SaloonInvalidResponseClassException |
163 | 177 | * @throws \Sammyjo20\Saloon\Exceptions\SaloonMissingMockException |
164 | 178 | */ |
165 | 179 | public function send() |
166 | 180 | { |
167 | | - // Hydrate the manager with juicy headers, config, interceptors, handlers... |
| 181 | + // Let's firstly hydrate the request manager, which will retrieve all the attributes |
| 182 | + // from the request and connector and build them up inside the request. |
168 | 183 |
|
169 | 184 | $this->hydrate(); |
170 | 185 |
|
171 | | - // Build up the config! |
| 186 | + // Next, we will retrieve our Guzzle client, request and build up the request options |
| 187 | + // in a way Guzzle will understand. |
172 | 188 |
|
| 189 | + $client = $this->createGuzzleClient(); |
| 190 | + $request = $this->createGuzzleRequest(); |
173 | 191 | $requestOptions = $this->buildRequestOptions(); |
174 | 192 |
|
175 | | - // Boot up our Guzzle client... This will also boot up handlers... |
176 | | - |
177 | | - $client = $this->createGuzzleClient(); |
| 193 | + // Finally, we will send the request! If the asynchronous mode has been requested, |
| 194 | + // we will return a promise with the Saloon response, however if not then we will |
| 195 | + // just return a response. |
178 | 196 |
|
179 | | - // Send the request! 🚀 |
| 197 | + return $this->asynchronous === true |
| 198 | + ? $this->sendAsyncRequest($client, $request, $requestOptions) |
| 199 | + : $this->sendSyncRequest($client, $request, $requestOptions); |
| 200 | + } |
180 | 201 |
|
| 202 | + /** |
| 203 | + * Send a traditional, synchronous request. |
| 204 | + * |
| 205 | + * @param GuzzleClient $client |
| 206 | + * @param GuzzleRequest $request |
| 207 | + * @param array $requestOptions |
| 208 | + * @return SaloonResponse |
| 209 | + * @throws SaloonInvalidResponseClassException |
| 210 | + * @throws \GuzzleHttp\Exception\GuzzleException |
| 211 | + * @throws \ReflectionException |
| 212 | + * @throws \Sammyjo20\Saloon\Exceptions\SaloonInvalidConnectorException |
| 213 | + */ |
| 214 | + private function sendSyncRequest(GuzzleClient $client, GuzzleRequest $request, array $requestOptions): SaloonResponse |
| 215 | + { |
181 | 216 | try { |
182 | | - $guzzleResponse = $client->send($this->createGuzzleRequest(), $requestOptions); |
| 217 | + $guzzleResponse = $client->send($request, $requestOptions); |
183 | 218 | } catch (BadResponseException $exception) { |
184 | 219 | return $this->createResponse($requestOptions, $exception->getResponse(), $exception); |
185 | 220 | } |
186 | 221 |
|
187 | 222 | return $this->createResponse($requestOptions, $guzzleResponse); |
188 | 223 | } |
189 | 224 |
|
| 225 | + /** |
| 226 | + * Prepare an asynchronous request, and return a promise. |
| 227 | + * |
| 228 | + * @param GuzzleClient $client |
| 229 | + * @param GuzzleRequest $request |
| 230 | + * @param array $requestOptions |
| 231 | + * @return PromiseInterface |
| 232 | + */ |
| 233 | + private function sendAsyncRequest(GuzzleClient $client, GuzzleRequest $request, array $requestOptions): PromiseInterface |
| 234 | + { |
| 235 | + return $client->sendAsync($request, $requestOptions) |
| 236 | + ->then( |
| 237 | + function (ResponseInterface $guzzleResponse) use ($requestOptions) { |
| 238 | + // Instead of the promise returning a Guzzle response, we want to return |
| 239 | + // a Saloon response. |
| 240 | + |
| 241 | + return $this->createResponse($requestOptions, $guzzleResponse); |
| 242 | + }, |
| 243 | + function (GuzzleException $guzzleException) use ($requestOptions) { |
| 244 | + // If the exception was a connect exception, we should return that in the |
| 245 | + // promise instead rather than trying to convert it into a |
| 246 | + // SaloonResponse, since there was no response. |
| 247 | + |
| 248 | + if (! $guzzleException instanceof RequestException) { |
| 249 | + throw $guzzleException; |
| 250 | + } |
| 251 | + |
| 252 | + $response = $this->createResponse($requestOptions, $guzzleException->getResponse(), $guzzleException); |
| 253 | + |
| 254 | + throw $response->toException(); |
| 255 | + } |
| 256 | + ); |
| 257 | + } |
| 258 | + |
190 | 259 | /** |
191 | 260 | * Create a response. |
192 | 261 | * |
|
0 commit comments