Envless\Envless is the client. Build one where you need it, or configure a shared one once at startup and fetch it anywhere else.
use Envless\Envless;
$envless = new Envless(timeout: 10, maxRetries: 2);
foreach ($envless->variables->iterate('api', 'production', product: 'billing') as $variable) {
echo $variable['name'], ' ', $variable['updatedAt'], PHP_EOL;
}Two ways to build one
| How | Reads the environment | Use it for | |
|---|---|---|---|
| An instance | new Envless(...), or Envless::createClient(...) | For whatever you leave out | Dependency injection, tests, more than one workspace |
| The shared client | Envless::init(...) once, then Envless::getClient() | For whatever you leave out | Scripts and apps that want one client everywhere |
use Envless\Envless;
Envless::init(timeout: 15);
$identity = Envless::getClient()->me->get();
echo $identity['workspaceId'], PHP_EOL;Envless::getClient() builds the shared client from ENVLESS_TOKEN and ENVLESS_API_ENDPOINT the first time it is called if init never ran, and returns the same instance after that. Envless::resetClient() drops it, so the next getClient() builds a fresh one, which is what a test suite wants between cases.
Working with more than one workspace
A key is bound to one workspace, so a second workspace needs a second key and a second client. Clients share nothing, so build as many as you need.
use Envless\Envless;
$production = new Envless(token: (string) getenv('ENVLESS_PRODUCTION_TOKEN'));
$staging = new Envless(token: (string) getenv('ENVLESS_STAGING_TOKEN'));
echo $production->workspace->get()['slug'], PHP_EOL;
echo $staging->workspace->get()['slug'], PHP_EOL;Options
Every option is a named argument, and every one is optional.
| Option | Default | Notes |
|---|---|---|
token: | ENVLESS_TOKEN | The API key. Trimmed, and refused before any request if it holds anything outside visible ASCII. |
baseUrl: | ENVLESS_API_ENDPOINT, then https://api.envless.cloud | A host without a scheme gets https://, and a local host such as localhost or 127.0.0.1 gets http://. |
httpClient: | Envless\Http\CurlHttpClient | Anything implementing Envless\Http\HttpClient. See below. |
maxRetries: | 3 | Retries after the first attempt, for the requests that are safe to repeat. 0 turns them off. |
timeout: | 30 | Seconds each attempt may take, redirects and the response body included. An int or a float, and 0 turns the limit off. Every method also takes timeout: for one call. |
userAgent: | envless-sdk-php/<version> | Replaces the User-Agent header. |
disableUpdateNotice: | false | Skips the check for a newer release on Packagist. |
A client is immutable once built. To change an option, build another one.
Retries
GET, HEAD, PUT and DELETE requests are retried on 408, 429, 500, 502, 503 and 504. The wait doubles from half a second, or is whatever Retry-After asks for, and is capped at 30 seconds. POST and PATCH are never retried, because a create that timed out may already have landed, and neither is a request that got no answer at all. Once the retries run out, the last failure is thrown as it would have been without them.
Errors shows how to retry a write safely yourself, which for variables->apply() means re-reading the baseline.
Timeouts
timeout is a hard deadline for each attempt, covering the connection, the headers, any redirects and the whole response body, so a server that trickles a body cannot hold a call open past it. When it passes, the call throws Envless\Exception\NetworkException with isTimeout() true. Retries each get a fresh deadline.
use Envless\Envless;
use Envless\Exception\NetworkException;
$envless = new Envless();
try {
$envless->variables->listAll('api', 'production', timeout: 5);
} catch (NetworkException $error) {
if (!$error->isTimeout()) {
throw $error;
}
fwrite(STDERR, 'Envless did not answer within 5 seconds.' . PHP_EOL);
}Connections
The bundled cURL client keeps one connection alive between requests, so a run of reads pays for one TLS handshake. Requests that change data, POST and PATCH, always start on a fresh connection, so a connection that drops mid-request can never make one run twice. After a fork(), the child opens its own connection instead of sharing the parent's, and $envless->close() releases the connection when you are done.
A client is not safe to use from two coroutines at the same time. Under Swoole, OpenSwoole or a similar runtime, give each coroutine its own client.
Bring your own HTTP client
Envless\Http\CurlHttpClient takes a CA bundle, a proxy and raw cURL options, and honours the usual proxy environment variables:
use Envless\Envless;
use Envless\Http\CurlHttpClient;
$http = new CurlHttpClient(
caBundle: '/etc/ssl/certs/corporate.pem',
proxy: 'http://proxy.internal:3128',
);
$envless = new Envless(httpClient: $http);Guzzle, or any other PSR-18 client, plugs in through Envless\Http\Psr18HttpClient. It finds PSR-17 factories with php-http/discovery, or takes them as its second and third arguments.
use Envless\Envless;
use Envless\Http\Psr18HttpClient;
$envless = new Envless(httpClient: new Psr18HttpClient(new GuzzleHttp\Client()));Retries, redirects and the deadline stay in the SDK, and the key is never sent to another origin when a redirect leaves the API. The timeout and the redirect rules hold for the bundled cURL client and for Guzzle; another PSR-18 client keeps its own timeout and redirect settings. Anything implementing Envless\Http\HttpClient, a single send(HttpRequest $request): HttpResponse method, works too, which is the easiest way to fake the API in a test.
The raw escape hatch
$envless->raw->request() reaches an endpoint no method wraps yet, with the client's key, base URL, timeout and retries. It unwraps the response envelope, returns data, and throws the same typed exceptions as every method.
use Envless\Envless;
$envless = new Envless();
$data = $envless->raw->request('/workspace', 'GET');
echo json_encode($data, JSON_PRETTY_PRINT | JSON_THROW_ON_ERROR), PHP_EOL;It takes query: as an array of query parameters, body: as anything json_encode() can encode, and timeout: for that one call.
Reuse one client
A client is cheap to build but keeps its connection, so build one per process and share it, rather than one per call. In Laravel, bind it as a singleton:
use Envless\Envless;
use Illuminate\Support\ServiceProvider;
final class EnvlessServiceProvider extends ServiceProvider
{
public function register(): void
{
$this->app->singleton(Envless::class, static fn(): Envless => new Envless());
}
}In Symfony, Envless\Envless: ~ under services: in config/services.yaml makes the container build it from the environment. Then type-hint Envless $envless in any controller, command or job.