3scale/3scale_ws_api_for_php
PHP client for the 3scale Web Services API, enabling PHP applications to integrate with 3scale API management. Provides a lightweight wrapper for working with accounts, applications, services, and related API operations through a simple, consistent interface.
The 3scale Web Service API client is a plain (non-namespaced) PHP library, so wiring it into Laravel takes a couple of extra steps compared to a typical package.
Install / register the classes. The library ships global classes (ThreeScaleClient, ThreeScaleAuthorizeResponse, ThreeScaleReportResponse, ThreeScaleServiceManagementClient, etc.) that are not PSR-4 friendly. If you install it via Composer from a VCS repository, add a classmap autoload entry in your app's composer.json:
"autoload": {
"classmap": ["vendor/3scale/3scale_ws_api_for_php/lib"]
}
Then composer dump-autoload and php artisan optimize:clear.
Configure it. Add credentials to config/services.php and .env (never commit the provider key):
'threescale' => [
'provider_key' => env('THREESCALE_PROVIDER_KEY'),
'host' => env('THREESCALE_HOST', 'https://su1.3scale.net'),
'port' => env('THREESCALE_PORT', 443),
'protocol' => env('THREESCALE_PROTOCOL', 'https'),
],
v2.7.1 changed the expected host/port shape for the SaaS backend. Set the host with the scheme (https://su1.3scale.net) and port 443. If your .env currently has su1.3scale.net + 80, or a value like su1.3scale.net:80, that is the breaking change to fix before deploying.
Bind it once in a service provider so nothing else in the app touches constructor arguments:
$this->app->singleton(\ThreeScaleClient::class, fn () => new \ThreeScaleClient(
config('services.threescale.provider_key'),
config('services.threescale.host'),
config('services.threescale.port'),
config('services.threescale.protocol'),
));
First use case: gate an API endpoint. Call authorize($appId, $appKey) in a middleware, read isSuccess() / getUsageReports(), and return 403 on DENIED or 429 when a usage limit is exhausted. Verify against a staging 3scale account first — a bad host/port after this release surfaces as a connection error, not a clean API error.
Wrap the client, don't scatter it. The library's classes are global and its responses have inconsistent helper coverage. Build a small ThreeScale service with authorize(), report(), authorizeAndReport() returning your own value objects. This is the single most valuable move: it isolates the classmap/global-namespace weirdness and lets you fake it in tests.
Authorization middleware for API traffic. Resolve the app ID/key from headers (X-App-Id / X-App-Key are the 3scale convention), call authorize, cache the "plan + limits" portion of the result for a short TTL (30–60s) for high-traffic keys, and short-circuit denied requests. Cache the response, not the decision to skip the call entirely — 3scale owns rate-limit accounting.
Report usage asynchronously. report() is a network call; never do it inline in a hot request path. Push each billable event to a queue job, or use terminate() middleware so the response is already flushed. The client accepts an array of transactions, so a scheduled job that flushes a usage_events table every minute in batches of ~20 is the usual pattern — it also gives you retry and replay semantics 3scale won't give you.
Idempotency via transaction_id. Always set a stable transaction ID (e.g. order-1042 or a UUID persisted with the event). On retries after a timeout you'll otherwise double-bill. Store report result rows so a failed batch can be re-sent.
Fail-open vs fail-closed is a deliberate decision. Put it behind a config flag. For metering/reporting, fail-open (log and move on). For authorization on a paid API, decide per-route. Add a circuit breaker (Cache::remember of a "3scale unreachable" flag with a short TTL) so a 3scale outage doesn't add 3× curl timeouts to every request.
Service Management API for provisioning. Use ThreeScaleServiceManagementClient with an access token (a separate credential from the provider key) when a customer signs up in your app: create application, subscribe to a plan, fetch the app key, store it. Treat this like any onboarding side effect — queue it, and make the job idempotent by looking up existing applications first.
Testing. Because the library uses curl directly, Http::fake() will not intercept it — no matter how many times you try. Bind a fake implementation of your wrapper interface in the container and assert on calls; keep one opt-in integration test against a sandbox account.
v2.7.1 specifics. The new client header (tracking the plugin version to the SaaS backend) is automatic — no code change, but it means your traffic is now attributable by version in 3scale's logs, which is genuinely useful when debugging whether an old deploy is still calling out. The host/port change is the part that needs action: update config/services.php, the .env files for every environment, and any hard-coded constructor calls in tests or scripts.
authorize. Search the codebase for su1.3scale.net, :80, and '80' before deploying 2.7.1, including Docker/CI env files and Laravel Forge/Envoyer environment variables.SSL certificate problem: unable to get local issuer certificate. Fix curl.cainfo in php.ini or set it explicitly. Also confirm egress on 443 in security groups/firewalls — 80 may be open where 443 isn't.php artisan config:cache bakes the host/port in. After changing .env, always php artisan config:clear locally; on deploy, ensure the cache rebuild happens after the new files land.use \ThreeScaleClient; at the top of a file, but there's no namespace to distinguish it. If another dependency defines ThreeScaleResponse, you'll get a fatal class collision. Keep all usage inside your wrapper service so a future fork/replacement is a one-file change.isSuccess() is not optional. The client returns response objects for both transport-level and API-level failures and does not throw in most paths. Every call site must check isSuccess() and branch on ThreeScaleResponse status constants; otherwise a failed report looks like a success.curl_close(). Run your test suite with deprecations promoted to errors once to see whether anything in your own glue code is at fault before blaming the library.How can I help you explore Laravel packages today?