spatie/guzzle-redirect-history-middleware
Guzzle middleware that records redirect chains for each request. Attach RedirectHistoryMiddleware to your handler stack to capture every Location hop and inspect the final URL and intermediate redirects via a RedirectHistory instance.
Installation:
composer require spatie/guzzle-redirect-history-middleware
Add the middleware to your Guzzle client stack:
use Spatie\GuzzleRedirectHistoryMiddleware\RedirectHistoryMiddleware;
$client = new Client([
'middleware' => [
RedirectHistoryMiddleware::class,
],
]);
First Use Case: Track redirects in a simple request:
$response = $client->get('https://example.com/redirect-chain');
$redirects = $client->getConfig('redirect_history'); // Access via config
// or via middleware instance (if stored)
Middleware Integration:
middleware config array.RetryMiddleware, JsonMiddleware).Accessing Redirect History:
$history = $client->getConfig('redirect_history');
$middleware = $client->getConfig('middleware')[0];
$history = $middleware->getRedirectHistory();
Custom Storage:
RedirectHistoryMiddleware:
class CustomMiddleware extends RedirectHistoryMiddleware {
public function __construct() {
$this->history = collect(); // Use Laravel Collections
}
}
Debugging Redirects: Log redirects in tests or production:
$client->get('https://api.example.com')->then(function ($response) {
$redirects = $response->getClient()->getConfig('redirect_history');
Log::debug('Redirect chain:', $redirects);
});
Conditional Logic: Use redirect history to enforce policies (e.g., block external redirects):
$response = $client->get('https://example.com');
$redirects = $response->getClient()->getConfig('redirect_history');
if ($redirects->contains(function ($redirect) {
return str_starts_with($redirect['uri'], 'https://external.com');
})) {
throw new \RuntimeException('External redirect detected!');
}
Testing: Assert redirect behavior in PHPUnit:
$response = $client->get('https://example.com/redirect');
$this->assertCount(2, $response->getClient()->getConfig('redirect_history'));
Middleware Order:
RedirectHistoryMiddleware before other middleware that might modify responses (e.g., HistoryMiddleware).'middleware' => [
RedirectHistoryMiddleware::class,
HistoryMiddleware::class, // Will capture final response
],
History Persistence:
$client->getConfig('redirect_middleware') = $middleware;
Loop Detection:
allow_redirects config to limit loops:
$client = new Client([
'allow_redirects' => [
'max' => 5,
],
]);
Inspect Raw History: The history array contains:
[
[
'uri' => 'https://example.com/old',
'status' => 301,
'headers' => ['Location' => 'https://example.com/new'],
],
// ...
]
Use dd() or Log::debug() to inspect.
Disable Middleware Temporarily: Remove it from the stack to isolate issues:
$client = new Client(['middleware' => []]);
Custom History Format:
Override getRedirectHistory() to transform data:
class CustomMiddleware extends RedirectHistoryMiddleware {
public function getRedirectHistory() {
return $this->history->map(function ($redirect) {
return [
'from' => $redirect['uri'],
'to' => $redirect['headers']['Location'] ?? null,
];
});
}
}
Event-Based Hooks: Extend to trigger events on redirects:
class EventMiddleware extends RedirectHistoryMiddleware {
public function __construct() {
$this->history = [];
event(new RedirectStarted('https://example.com'));
}
protected function recordRedirect($request, $response) {
parent::recordRedirect($request, $response);
event(new RedirectCompleted($response->getEffectiveUri()));
}
}
Laravel Integration: Bind the middleware to the container for easy access:
$app->singleton('redirectHistoryMiddleware', function ($app) {
return new RedirectHistoryMiddleware();
});
Then inject it into services:
public function __construct(private RedirectHistoryMiddleware $middleware) {}
How can I help you explore Laravel packages today?