Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature | Fatal Exception Middleware #440

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@
"friendsofphp/php-cs-fixer": "^3.5",
"illuminate/collections": "^9.39 || ^10.0",
"league/flysystem": "^3.0",
"pestphp/pest": "^2.6",
"pestphp/pest": "^2.10",
"phpstan/phpstan": "^1.11.4",
"saloonphp/xml-wrangler": "^1.1",
"spatie/ray": "^1.33",
Expand Down
57 changes: 57 additions & 0 deletions src/Helpers/MiddlewarePipeline.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
use Saloon\Enums\PipeOrder;
use Saloon\Http\PendingRequest;
use Saloon\Contracts\FakeResponse;
use Saloon\Exceptions\Request\FatalRequestException;

class MiddlewarePipeline
{
Expand All @@ -21,13 +22,19 @@ class MiddlewarePipeline
*/
protected Pipeline $responsePipeline;

/**
* Fatal Pipeline
*/
protected Pipeline $fatalPipeline;

/**
* Constructor
*/
public function __construct()
{
$this->requestPipeline = new Pipeline;
$this->responsePipeline = new Pipeline;
$this->fatalPipeline = new Pipeline;
}

/**
Expand Down Expand Up @@ -92,6 +99,33 @@ public function onResponse(callable $callable, ?string $name = null, ?PipeOrder
return $this;
}

/**
* Add a middleware to run on fatal errors
*
* @param callable(FatalRequestException): (void) $callable
* @return $this
*/
public function onFatalException(callable $callable, ?string $name = null, ?PipeOrder $order = null): static
{
/**
* For some reason, PHP is not destructing non-static Closures, or 'things' using non-static Closures, correctly, keeping unused objects intact.
* Using a *static* Closure, or re-binding it to an empty, anonymous class/object is a workaround for the issue.
* If we don't, things using the MiddlewarePipeline, in turn, won't destruct.
* Concretely speaking, for Saloon, this means that the Connector will *not* get destructed, and thereby also not the underlying client.
* Which in turn leaves open file handles until the process terminates.
*
* Do note that this is entirely about our *wrapping* Closure below.
* The provided callable doesn't affect the MiddlewarePipeline.
*/
$this->fatalPipeline->pipe(static function (FatalRequestException $throwable) use ($callable): FatalRequestException {
$callable($throwable);

return $throwable;
}, $name, $order);

return $this;
}

/**
* Process the request pipeline.
*/
Expand All @@ -108,6 +142,15 @@ public function executeResponsePipeline(Response $response): Response
return $this->responsePipeline->process($response);
}

/**
* Process the fatal pipeline.
* @throws \Saloon\Exceptions\Request\FatalRequestException
*/
public function executeFatalPipeline(FatalRequestException $throwable): void
{
$this->fatalPipeline->process($throwable);
}

/**
* Merge in another middleware pipeline.
*
Expand All @@ -125,8 +168,14 @@ public function merge(MiddlewarePipeline $middlewarePipeline): static
$middlewarePipeline->getResponsePipeline()->getPipes()
);

$fatalPipes = array_merge(
$this->getFatalPipeline()->getPipes(),
$middlewarePipeline->getFatalPipeline()->getPipes()
);

$this->requestPipeline->setPipes($requestPipes);
$this->responsePipeline->setPipes($responsePipes);
$this->fatalPipeline->setPipes($fatalPipes);

return $this;
}
Expand All @@ -146,4 +195,12 @@ public function getResponsePipeline(): Pipeline
{
return $this->responsePipeline;
}

/**
* Get the fatal pipeline
*/
public function getFatalPipeline(): Pipeline
{
return $this->fatalPipeline;
}
}
9 changes: 9 additions & 0 deletions src/Http/PendingRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
use Saloon\Http\Middleware\ValidateProperties;
use Saloon\Http\Middleware\DetermineMockResponse;
use Saloon\Exceptions\InvalidResponseClassException;
use Saloon\Exceptions\Request\FatalRequestException;
use Saloon\Traits\PendingRequest\ManagesPsrRequests;
use Saloon\Http\PendingRequest\MergeRequestProperties;
use Saloon\Http\PendingRequest\BootConnectorAndRequest;
Expand Down Expand Up @@ -152,6 +153,14 @@ public function executeResponsePipeline(Response $response): Response
return $this->middleware()->executeResponsePipeline($response);
}

/**
* Execute the fatal pipeline.
*/
public function executeFatalPipeline(FatalRequestException $throwable): void
{
$this->middleware()->executeFatalPipeline($throwable);
}

/**
* Get the request.
*/
Expand Down
5 changes: 5 additions & 0 deletions src/Traits/Connector/SendsRequests.php
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,11 @@ public function send(Request $request, MockClient $mockClient = null, callable $

$exceptionResponse = $exception instanceof RequestException ? $exception->getResponse() : null;

// If the exception is a FatalRequestException, we'll execute the fatal pipeline
if($exception instanceof FatalRequestException) {
$exception->getPendingRequest()->executeFatalPipeline($exception);
}

// If we've reached our max attempts - we won't try again, but we'll either
// return the last response made or just throw an exception.

Expand Down
Loading
Loading