Skip to content

Commit

Permalink
Merge pull request #296 from saloonphp/fix/v3-improve-middleware-order
Browse files Browse the repository at this point in the history
Fix | V3 - Improve middleware order and add support for PHP 8.3
  • Loading branch information
Sammyjo20 authored Sep 21, 2023
2 parents e434e20 + 1fc3b42 commit bfd711d
Show file tree
Hide file tree
Showing 11 changed files with 149 additions and 140 deletions.
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
"homepage": "https://github.com/saloonphp/saloon",
"require": {
"php": "^8.1",
"guzzlehttp/guzzle": "^7.7",
"guzzlehttp/guzzle": "^7.6",
"guzzlehttp/promises": "^1.5 || ^2.0",
"guzzlehttp/psr7": "^2.0",
"psr/http-factory": "^1.0",
Expand Down
2 changes: 1 addition & 1 deletion src/Http/Middleware/ValidateProperties.php
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ public function __invoke(PendingRequest $pendingRequest): void

foreach ($pendingRequest->headers()->all() as $key => $unused) {
if (! is_string($key)) {
throw new InvalidHeaderException('One or more of the headers are invalid. Make sure to use the header name as the key. For example: \'Content-Type\' => \'application/json\'.');
throw new InvalidHeaderException('One or more of the headers are invalid. Make sure to use the header name as the key. For example: [\'Content-Type\' => \'application/json\'].');
}
}
}
Expand Down
167 changes: 56 additions & 111 deletions src/Http/PendingRequest.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,20 @@
use Saloon\Contracts\FakeResponse;
use Saloon\Http\Faking\MockClient;
use Saloon\Contracts\Authenticator;
use Saloon\Http\Middleware\MergeBody;
use Saloon\Http\Middleware\MergeDelay;
use Saloon\Contracts\Body\BodyRepository;
use Saloon\Http\PendingRequest\MergeBody;
use Saloon\Http\PendingRequest\MergeDelay;
use Saloon\Http\Middleware\DelayMiddleware;
use Saloon\Http\PendingRequest\BootPlugins;
use Saloon\Traits\Auth\AuthenticatesRequests;
use Saloon\Http\Middleware\ValidateProperties;
use Saloon\Http\Middleware\AuthenticateRequest;
use Saloon\Http\Middleware\DetermineMockResponse;
use Saloon\Http\Middleware\MergeRequestProperties;
use Saloon\Exceptions\InvalidResponseClassException;
use Saloon\Traits\PendingRequest\ManagesPsrRequests;
use Saloon\Http\PendingRequest\DetermineMockResponse;
use Saloon\Http\PendingRequest\MergeRequestProperties;
use Saloon\Http\PendingRequest\BootConnectorAndRequest;
use Saloon\Traits\RequestProperties\HasRequestProperties;
use Saloon\Http\PendingRequest\AuthenticatePendingRequest;

class PendingRequest
{
Expand Down Expand Up @@ -74,7 +76,7 @@ class PendingRequest
/**
* Build up the request payload.
*
* @throws \ReflectionException
* @throws \Saloon\Exceptions\DuplicatePipeNameException
*/
public function __construct(Connector $connector, Request $request, MockClient $mockClient = null)
{
Expand All @@ -92,108 +94,39 @@ public function __construct(Connector $connector, Request $request, MockClient $
$this->authenticator = $request->getAuthenticator() ?? $connector->getAuthenticator();
$this->mockClient = $mockClient ?? $request->getMockClient() ?? $connector->getMockClient();

// Next, we'll boot our plugin traits.

$this->bootPlugins();

// Finally, we'll register and execute the middleware pipeline.

$this->registerAndExecuteMiddleware();
}

/**
* Boot every plugin on the connector and request.
*
* @return $this
* @throws \ReflectionException
*/
protected function bootPlugins(): static
{
$connector = $this->connector;
$request = $this->request;

$connectorTraits = Helpers::classUsesRecursive($connector);
$requestTraits = Helpers::classUsesRecursive($request);

foreach ($connectorTraits as $connectorTrait) {
Helpers::bootPlugin($this, $connector, $connectorTrait);
}

foreach ($requestTraits as $requestTrait) {
Helpers::bootPlugin($this, $request, $requestTrait);
}

return $this;
}

/**
* Run the boot method on the connector and request.
*
* @return $this
*/
protected function bootConnectorAndRequest(): static
{
// This method is not going to be part of a middleware because the
// users may wish to register middleware inside the boot methods.

$this->connector->boot($this);
$this->request->boot($this);

return $this;
}

/**
* Register and execute middleware
*/
protected function registerAndExecuteMiddleware(): void
{
$middleware = $this->middleware();

// We'll start with our core middleware like merging request properties, merging the
// body, delay and also running authenticators on the request.

$middleware
->onRequest(new MergeRequestProperties, 'mergeRequestProperties')
->onRequest(new MergeBody, 'mergeBody')
->onRequest(new MergeDelay, 'mergeDelay')
->onRequest(new AuthenticateRequest, 'authenticateRequest')
->onRequest(new DetermineMockResponse, 'determineMockResponse');
// Now, we'll register our global middleware and our mock response middleware.
// Registering these middleware first means that the mock client can set
// the fake response for every subsequent middleware.

// Next, we'll merge in our "Global" middleware which can be middleware set by the
// user or set by Saloon's plugins like the Laravel Plugin. It's best that this
// middleware is run now because we want the user to still have an opportunity
// to overwrite anything applied by it.
$this->middleware()->merge(Config::globalMiddleware());
$this->middleware()->onRequest(new DetermineMockResponse, 'determineMockResponse');

$middleware->merge(Config::globalMiddleware());
// Next, we'll boot our plugins. These plugins can add headers, config variables and
// even register their own middleware. We'll use a tap method to simply apply logic
// to the PendingRequest. After that, we will merge together our request properties
// like headers, config, middleware, body and delay, and we'll follow it up by
// invoking our authenticators. We'll do this here because when middleware is
// executed, the developer will have access to any headers added by the middleware.

// Now we'll "boot" the connector and request. This is a hook that can be run after
// the core middleware that allows you to add your own properties that are a higher
// priority than anything else.
$this
->tap(new BootPlugins)
->tap(new MergeRequestProperties)
->tap(new MergeBody)
->tap(new MergeDelay)
->tap(new AuthenticatePendingRequest)
->tap(new BootConnectorAndRequest);

$this->bootConnectorAndRequest();
// Now, we'll register some default middleware for validating the request properties and
// running the delay that should have been set by the user.

// Now we'll merge the middleware added on the connector and the request. This
// middleware will have almost the final object to play with and overwrite if
// they desire.
$this->middleware()
->onRequest(new ValidateProperties, 'validateProperties')
->onRequest(new DelayMiddleware, 'delayMiddleware');

$middleware
->merge($this->connector->middleware())
->merge($this->request->middleware());

// Next, we'll register our ValidateProperties middleware. This will validate
// any properties on the pending request like headers.

$middleware->onRequest(new ValidateProperties, 'validateProperties');

// Next, we'll delay the request if we need to. This needs to be as near to
// the end as possible to apply delay right before the request is sent.

$middleware->onRequest(new DelayMiddleware, 'delayMiddleware');

// Next, we will execute the request middleware pipeline which will
// Finally, we will execute the request middleware pipeline which will
// process the middleware in the order we added it.

$middleware->executeRequestPipeline($this);
$this->middleware()->executeRequestPipeline($this);
}

/**
Expand Down Expand Up @@ -330,6 +263,26 @@ public function hasFakeResponse(): bool
return $this->fakeResponse instanceof FakeResponse;
}

/**
* Check if the request is asynchronous
*/
public function isAsynchronous(): bool
{
return $this->asynchronous;
}

/**
* Set if the request is going to be sent asynchronously
*
* @return $this
*/
public function setAsynchronous(bool $asynchronous): static
{
$this->asynchronous = $asynchronous;

return $this;
}

/**
* Get the response class
*
Expand All @@ -349,21 +302,13 @@ public function getResponseClass(): string
}

/**
* Check if the request is asynchronous
*/
public function isAsynchronous(): bool
{
return $this->asynchronous;
}

/**
* Set if the request is going to be sent asynchronously
* Tap into the pending request
*
* @return $this
*/
public function setAsynchronous(bool $asynchronous): static
protected function tap(callable $callable): static
{
$this->asynchronous = $asynchronous;
$callable($this);

return $this;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,23 +2,24 @@

declare(strict_types=1);

namespace Saloon\Http\Middleware;
namespace Saloon\Http\PendingRequest;

use Saloon\Http\PendingRequest;
use Saloon\Contracts\Authenticator;
use Saloon\Contracts\RequestMiddleware;

class AuthenticateRequest implements RequestMiddleware
class AuthenticatePendingRequest
{
/**
* Authenticate the pending request
*/
public function __invoke(PendingRequest $pendingRequest): void
public function __invoke(PendingRequest $pendingRequest): PendingRequest
{
$authenticator = $pendingRequest->getAuthenticator();

if ($authenticator instanceof Authenticator) {
$authenticator->set($pendingRequest);
}

return $pendingRequest;
}
}
21 changes: 21 additions & 0 deletions src/Http/PendingRequest/BootConnectorAndRequest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

declare(strict_types=1);

namespace Saloon\Http\PendingRequest;

use Saloon\Http\PendingRequest;

class BootConnectorAndRequest
{
/**
* Boot the connector and request
*/
public function __invoke(PendingRequest $pendingRequest): PendingRequest
{
$pendingRequest->getConnector()->boot($pendingRequest);
$pendingRequest->getRequest()->boot($pendingRequest);

return $pendingRequest;
}
}
33 changes: 33 additions & 0 deletions src/Http/PendingRequest/BootPlugins.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
<?php

declare(strict_types=1);

namespace Saloon\Http\PendingRequest;

use Saloon\Helpers\Helpers;
use Saloon\Http\PendingRequest;

class BootPlugins
{
/**
* Boot the plugins
*/
public function __invoke(PendingRequest $pendingRequest): PendingRequest
{
$connector = $pendingRequest->getConnector();
$request = $pendingRequest->getRequest();

$connectorTraits = Helpers::classUsesRecursive($connector);
$requestTraits = Helpers::classUsesRecursive($request);

foreach ($connectorTraits as $connectorTrait) {
Helpers::bootPlugin($pendingRequest, $connector, $connectorTrait);
}

foreach ($requestTraits as $requestTrait) {
Helpers::bootPlugin($pendingRequest, $request, $requestTrait);
}

return $pendingRequest;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,26 @@

declare(strict_types=1);

namespace Saloon\Http\Middleware;
namespace Saloon\Http\PendingRequest;

use Saloon\Enums\PipeOrder;
use Saloon\Http\Faking\Fixture;
use Saloon\Http\PendingRequest;
use Saloon\Http\Faking\MockResponse;
use Saloon\Contracts\RequestMiddleware;
use Saloon\Http\Middleware\RecordFixture;

class DetermineMockResponse implements RequestMiddleware
class DetermineMockResponse
{
/**
* Guess a mock response
*
* @throws \JsonException
* @throws \Saloon\Exceptions\DuplicatePipeNameException
* @throws \Saloon\Exceptions\FixtureException
* @throws \Saloon\Exceptions\FixtureMissingException
* @throws \Saloon\Exceptions\NoMockResponseFoundException
*/
public function __invoke(PendingRequest $pendingRequest): PendingRequest|MockResponse
public function __invoke(PendingRequest $pendingRequest): PendingRequest
{
if ($pendingRequest->hasMockClient() === false) {
return $pendingRequest;
Expand All @@ -40,10 +42,10 @@ public function __invoke(PendingRequest $pendingRequest): PendingRequest|MockRes

// If the mock response is a valid instance, we will return it.
// The middleware pipeline will recognise this and will set
// it as the "FakeResponse" on the request.
// it as the "FakeResponse" on the PendingRequest.

if ($mockResponse instanceof MockResponse) {
return $mockResponse;
return $pendingRequest->setFakeResponse($mockResponse);
}

// However if the mock response is not valid because it is
Expand Down
Loading

0 comments on commit bfd711d

Please sign in to comment.