reefki/laravel-geoip
A Laravel package for IP geolocation that resolves visitors' country, city, and coordinates from their IP address, with a clean API, caching, and support for multiple GeoIP drivers so you can localize content and personalize responses.
This release is a compatibility-and-correctness drop, so the first job is confirming your constraint matrix still resolves before you write any new code.
symfony/http-foundation v8 both raise the minimum PHP version. If your app or package still runs on an older PHP, Composer will silently hold you on v2.0.x — pin explicitly ("vendor/package": "^2.1") if you intend to move.composer update --dry-run and read the symfony/http-foundation resolution line. v8 support means the package now allows ^6.4|^7|^8; if you have a transitive constraint holding http-foundation at 7.x you'll stay there, which is fine — but you want to know which branch you're on before debugging.retry( and any closure or class that handles a failed HTTP attempt.Your first real use case is the one this release unblocks: retrying a flaky third-party API where the failure surfaces as something other than an Exception — a TypeError from a malformed payload, an Error from a typed DTO hydration step, or a connection-level throwable from the underlying client that doesn't extend Exception.
use Illuminate\Http\Client\ConnectionException;
$response = Http::retry(3, 200, function (Throwable $e) {
return $e instanceof ConnectionException
|| $e instanceof TypeError;
})->post('https://api.example.com/orders', $payload);
Type the callback as Throwable, not Exception. The closure signature is now wide enough to receive any throwable. If you write function (Exception $e) you are narrowing the parameter and will get a TypeError at call time when the package hands you an Error. Always declare Throwable in the closure.
Whitelist, don't blanket-retry. The value of the fix is that you can see non-Exception throwables — not that you should retry them. Assert on the specific failure modes you consider transient:
Http::retry(3, fn (int $attempt) => $attempt * 250, function (Throwable $e) {
return $e instanceof ConnectionException // DNS, TLS, timeout
|| ($e instanceof RequestException && $e->response?->serverError());
})->withHeaders(['Idempotency-Key' => $key])->post($url, $payload);
Keep retry policy in one place. Extract the decision into a small invokable class so the same policy applies across every client you own, and so you can unit-test it without hitting the network:
final class TransientFailure
{
public function __invoke(Throwable $e): bool
{
return $e instanceof ConnectionException
|| ($e instanceof RequestException && $e->response->serverError());
}
}
Http::retry(3, 200, new TransientFailure())->get($url);
Test the retry path with Http::fake() sequences. You can assert both the number of attempts and the final outcome, which is the cheapest way to prove the widened callback actually fires for the failure mode you care about:
Http::fake([
'api.example.com/*' => Http::sequence()
->pushStatus(503)
->pushStatus(503)
->push(['ok' => true], 200),
]);
Http::retry(3, 0, new TransientFailure())->post('https://api.example.com/orders')
->assertOk();
Http::assertSentCount(3);
Mirror the Testbench matrix in CI. With orchestra/testbench at ^8.0|^9.0|^10.0|^11.0 you can run your own suite against every supported Laravel line from one matrix. Add the lowest and highest supported combos as required checks and let the middle ones be informational — that keeps PR feedback fast while still catching a Laravel 13-only break.
Plan your Laravel 13 upgrade as a separate PR. Nothing in this release forces you onto 13; treat it as an opt-in. Bump laravel/framework, PHP, and http-foundation together, then re-run the retry assertions — those are the only codepaths whose behaviour changed.
Exception to Throwable is safe for closures, but if you have a class implementing a retry-related contract, PHP enforces the signature. An implementation declaring Exception where the contract now says Throwable is a fatal "Declaration must be compatible" — not a deprecation. Grep for Exception $e in anything that implements a package interface before upgrading.Error and TypeError almost always mean your code is wrong, not the network. Retrying them burns three attempts and a backoff sleep before surfacing the same defect, and it may hide the stack trace behind a retry-exhausted exception. Log the throwable class on every attempt so a sudden flood of TypeError retries is visible.TypeError raised in your own response handling can double-charge a customer.$sleep units and closures. Verify whether your version takes milliseconds or a closure receiving the attempt number — it differs across Laravel lines, and a wrong unit turns a 200 ms backoff into 200 seconds in staging.composer update refuses, run composer why-not symfony/http-foundation 8 — the blocker is nearly always another package with a hard ^7 constraint, and knowing which one saves an afternoon.How can I help you explore Laravel packages today?