modstore/rotating-proxy-bundle
modstore/rotating-proxy-bundle is a PHP bundle for managing rotating proxy configurations and selecting proxies for outbound HTTP requests. It helps organize proxy pools and integrate rotation into scraping, testing, and other network-dependent tasks with minimal setup.
Reality check first: modstore/rotating-proxy-bundle is a Symfony bundle, not a Laravel package. There is no RotatingProxyServiceProvider, no config/rotating_proxy.php, and no package discovery. With 2 stars, 0 dependents and a 0.04 score, treat this as "read the source before you trust it" — you are adopting an obscure dependency, not a supported SDK.
Minimal steps:
Install and inspect what it actually pulls in:
composer require modstore/rotating-proxy-bundle
cat vendor/modstore/rotating-proxy-bundle/composer.json
The require block tells you whether it wraps a core modstore/rotating-proxy library (likely the real rotation logic) or contains everything itself. If there is a core library, depend on that directly and skip the bundle — the bundle only adds Symfony DI glue you cannot use in Laravel anyway.
Look here first inside vendor/modstore/rotating-proxy-bundle/src/:
DependencyInjection/Configuration.php — the authoritative list of config keys (proxy sources, rotation strategy, credentials).DependencyInjection/*Extension.php — how services are wired, which aliases exist.*Bundle.php — usually empty; ignore it.ProxyProvider, ProxyPool, ProxyManager, or Guzzle middleware classes — these are the pieces you will reuse.First use case: rotating outbound IPs for scraping or API calls in a queued job. Wire the underlying pool manually in an AppServiceProvider and hand it to Guzzle — do not attempt to bootstrap the Symfony kernel:
// AppServiceProvider::register()
$this->app->singleton('proxy.pool', function ($app) {
return new \Modstore\RotatingProxy\ProxyPool(
config('services.proxies.list')
);
});
Validate with a single Http::withOptions(['proxy' => $pool->next()])->get('https://httpbin.org/ip') before building anything on top of it.
Config-driven pool via a Laravel config file. Translate the Symfony tree from Configuration.php into config/proxies.php, populated from env. Keep credentials out of the file:
return [
'pool' => array_filter(explode(',', env('PROXY_POOL', ''))),
'strategy' => env('PROXY_STRATEGY', 'round_robin'),
'timeout' => 10,
'retries' => 2,
];
Adapter/wrapper service. Wrap the third-party classes behind your own interface so the rest of the app never imports Modstore\*. This is the single most valuable pattern here — it lets you swap in a maintained library (e.g. a Guzzle middleware or a RotatingProxy implementation) in an afternoon when this package rots.
interface ProxyRotator { public function next(): ?string; public function reportFailure(string $proxy): void; }
Register it as a singleton, expose via a facade if the team prefers Proxy::next().
Guzzle middleware for per-request rotation. Rather than passing proxies at every call site, attach a middleware that rewrites RequestOptions::PROXY and records which proxy was used:
$stack->push(Middleware::mapRequest(function ($request) use ($pool) {
return $request; // plus set proxy on the handler options, or use on_stats
}));
$stack->push(Middleware::retry($decider, fn () => $pool->next()));
Combine rotation with retry so a failed proxy triggers the next proxy, not the same one.
Queue jobs with proxy affinity. For sites that ban on IP switching mid-session, assign one proxy per job ($job->proxy) and keep it sticky; for bulk scraping, rotate per request. Store the proxy string in the job payload so retries reuse or deliberately abandon it.
Health checking / circuit breaker. Track success/failure per proxy in Redis with a TTL. Remove a proxy from rotation after N consecutive failures and re-admit it after a cooldown. This is not provided by the bundle and is where most of your value-add lives.
Laravel HTTP client integration. Prefer Http::withOptions(['proxy' => $rotator->next()]) per call over global middleware if you need fine-grained control over which requests rotate — global middleware will also proxy your internal calls to Mailgun, S3, etc., which you usually do not want. Scope it to a dedicated Http client instance.
Symfony coupling is the main trap. The bundle's extension class typically implements PrependExtensionInterface / references Symfony\Component\DependencyInjection\*. Instantiating it inside Laravel drags Symfony DI components into your composer.json and does nothing useful. Only reuse the plain PHP service classes; ignore the DependencyInjection/ and *Bundle.php files entirely.
No auto-discovery, no publishable config. php artisan vendor:publish will find nothing useful. Build your own config file; do not try to make config/packages/modstore_rotating_proxy.yaml work.
Maintenance risk is the headline. Two stars and zero dependents means: no security fixes, no Laravel 12 compatibility promises, no issue triage. Pin an exact version ("modstore/rotating-proxy-bundle": "1.0.0"), commit composer.lock, and consider vendoring the few classes you actually use into app/Support/Proxy/.
Proxy URL formats bite. http://user:pass@host:port with special characters in the password must be URL-encoded; socks5:// vs http:// changes Guzzle's CONNECT behaviour. Log the proxy string (redacting credentials) on every request so failures are traceable.
TLS failures are usually the proxy, not your code. A misbehaving proxy yields cURL error 56/35 or SSL certificate problem. Do not paper over it with verify => false; instead report the proxy to your health tracker and retry on a different one.
Timeouts and retries interact badly. The bundle rotates on failure only if you tell it to. Set explicit timeout and connect_timeout, cap retries at 2–3, and make retried requests idempotent — a POST retried through a new proxy can double-submit.
Sticky sessions. For login-protected scraping, rotating every request will invalidate your session. Use a "sticky" mode: hash the target domain to a proxy and hold it for the cookie's lifetime, only rotating on ban detection (usually a 403 or a captcha body).
Debugging checklist: log (target, proxy, status, latency) per request; add on_stats to Guzzle to capture total_time per proxy; assert in tests that your pool returns distinct proxies and that an exhausted pool throws rather than silently returning null (a null proxy means "direct connection" — the exact thing you were trying to avoid).
Extension points. If the package exposes a ProxyProviderInterface (or similar), implement it in app/Support/Proxy/ to source proxies from your own database or a rotating residential provider rather than a static list. That is the one place worth coupling to the package — everything else should go through your adapter.
How can I help you explore Laravel packages today?