Getting started

Create a client

Build an Envless client, share one with Envless::init, or pass your own HTTP client. Every option, retries, timeouts, connections, Guzzle and PSR-18, and the raw escape hatch.

Envless\Envless is the client. Build one where you need it, or configure a shared one once at startup and fetch it anywhere else.

php
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

HowReads the environmentUse it for
An instancenew Envless(...), or Envless::createClient(...)For whatever you leave outDependency injection, tests, more than one workspace
The shared clientEnvless::init(...) once, then Envless::getClient()For whatever you leave outScripts and apps that want one client everywhere
php
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.

php
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.

OptionDefaultNotes
token:ENVLESS_TOKENThe API key. Trimmed, and refused before any request if it holds anything outside visible ASCII.
baseUrl:ENVLESS_API_ENDPOINT, then https://api.envless.cloudA host without a scheme gets https://, and a local host such as localhost or 127.0.0.1 gets http://.
httpClient:Envless\Http\CurlHttpClientAnything implementing Envless\Http\HttpClient. See below.
maxRetries:3Retries after the first attempt, for the requests that are safe to repeat. 0 turns them off.
timeout:30Seconds 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:falseSkips 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.

php
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:

php
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.

php
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.

php
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:

Laravel
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.