PHP SDK for GreenFlags feature flags: cached snapshot reads, geofence evaluation. Zero dependencies, billing-safe for PHP-FPM via pluggable snapshot cache.
greenflags/greenflags-php is a Laravel package for php sdk for greenflags feature flags: cached snapshot reads, geofence evaluation. zero dependencies, billing-safe for php-fpm via pluggable snapshot cache..
It currently has 0 GitHub stars and 0 downloads on Packagist (latest version v0.3.0).
Install it with composer require greenflags/greenflags-php.
Discover more Laravel packages by greenflags
or browse all Laravel packages to compare alternatives.
Last updated
Official PHP SDK for consuming GreenFlags feature flags from any PHP application: Laravel, Symfony, WordPress plugins, plain PHP.
Zero dependencies — PHP standard library only (streams + JSON). Built to minimize billable requests even in request-scoped PHP-FPM, via a pluggable snapshot cache: at most one network read per TTL per server, no matter how many requests you handle.
Status:
0.1.0. Full changelog inCHANGELOG.md.
composer require greenflags/greenflags-php
GreenFlags exposes a read endpoint (GET /v1/flags) where every 2xx response counts as a billable read. A naive integration that fetches flags on every request can generate thousands of unnecessary reads.
In Node/Go/Python, an in-memory snapshot survives between requests. Classic PHP-FPM tears the process down after every request — in-memory caching alone would still pay one read per request.
This SDK fixes that with a SnapshotCacheInterface (two methods: get/set a JSON string with a TTL) plus the sync() method:
sync(60) checks the cache. Fresh snapshot there? Zero network calls.GET /v1/flags (1 billable read), stored back with the TTL.Result: at most one read per minute per server (with ttl=60), whether you serve 10 or 10,000 requests. Adapters included: ApcuCache (shared across FPM workers) and InMemoryCache (Octane/workers/tests). A Redis or Laravel Cache adapter is ~5 lines (see the recipe below).
sync().getFlag returns your default for missing flags.declare(strict_types=1), readonly value objects.ext-json.project + environment. See the API docs for the full contract.use GreenFlags\Client;
use GreenFlags\Cache\ApcuCache;
$flags = new Client(
url: 'https://app.greenflags.dev',
apiToken: getenv('GREENFLAGS_API_TOKEN'),
cache: new ApcuCache(),
);
$flags->sync(60); // cache hit: 0 requests · cache miss: 1 billable read
if ($flags->isEnabled('new-checkout')) {
// ship it
}
// 1. Load the snapshot (cache-first; see sync() above)
$flags->sync(60);
// 2. Read flags — always from memory, never hits the network
$enabled = $flags->isEnabled('my-feature'); // bool sugar
$theme = $flags->getFlag('theme', 'light'); // string with default
$limit = $flags->getFlag('rate-limit', 100); // number with default
$config = $flags->getFlag('config', []); // json/array with default
// 3. List everything available
$all = $flags->getAllFlags(); // list<Flag>
$snapshot = $flags->getSnapshot(); // array<string, Flag>
// 4. Force a network refresh (1 billable read) — e.g. from a cron/artisan command
$flags->refresh(cacheTtlSeconds: 60);
getFlag / isEnabled never throw for missing flags — getFlag returns your default and isEnabled returns false.sync() / refresh() can throw GreenFlagsException — but sync() swallows network failures when it already has flags to serve (fail-open for long-running runtimes).ApcuCache for classic FPM, InMemoryCache for Octane/queues/CLI, or write a Redis/Laravel adapter for multi-server fleets (one read per TTL for the whole fleet).// app/Providers/AppServiceProvider.php
use GreenFlags\Client;
use GreenFlags\Cache\SnapshotCacheInterface;
use Illuminate\Support\Facades\Cache;
final class LaravelSnapshotCache implements SnapshotCacheInterface
{
public function get(): ?string
{
return Cache::get('greenflags.snapshot');
}
public function set(string $snapshotJson, int $ttlSeconds): void
{
Cache::put('greenflags.snapshot', $snapshotJson, $ttlSeconds);
}
}
public function register(): void
{
$this->app->singleton(Client::class, fn () => new Client(
url: config('services.greenflags.url', 'https://app.greenflags.dev'),
apiToken: config('services.greenflags.token'),
cache: new LaravelSnapshotCache(),
));
}
// anywhere (controller, middleware, Blade view via injection)
$flags = app(GreenFlags\Client::class);
$flags->sync(60);
if ($flags->isEnabled('new-checkout')) { ... }
With Redis as Laravel's cache driver, your entire fleet shares one snapshot: one billable read per minute total.
WordPress ships its own cache primitive — transients — which maps directly onto SnapshotCacheInterface. Drop this in a small plugin (or wp-content/mu-plugins/greenflags.php):
<?php
/**
* Plugin Name: GreenFlags
*/
use GreenFlags\Client;
use GreenFlags\Cache\SnapshotCacheInterface;
require __DIR__ . '/vendor/autoload.php'; // bundle the SDK with your plugin
final class WpTransientSnapshotCache implements SnapshotCacheInterface
{
public function get(): ?string
{
$value = get_transient('greenflags_snapshot');
return is_string($value) ? $value : null;
}
public function set(string $snapshotJson, int $ttlSeconds): void
{
set_transient('greenflags_snapshot', $snapshotJson, $ttlSeconds);
}
}
function gf(): Client
{
static $client = null;
if ($client === null) {
$client = new Client(
url: 'https://app.greenflags.dev',
apiToken: defined('GREENFLAGS_TOKEN') ? GREENFLAGS_TOKEN : '', // wp-config.php
cache: new WpTransientSnapshotCache(),
);
try {
$client->sync(60);
} catch (\GreenFlags\GreenFlagsException) {
// Fail-open: the store keeps working with defaults.
}
}
return $client;
}
function gf_is_enabled(string $key): bool
{
return gf()->isEnabled($key);
}
Use it anywhere in your theme or WooCommerce hooks:
// Toggle a checkout feature
add_action('woocommerce_before_checkout_form', function () {
if (gf_is_enabled('holiday-banner')) {
wc_print_notice(gf()->getFlag('holiday-banner-text', ''), 'notice');
}
});
Or expose a shortcode so editors can gate content from the block editor:
add_shortcode('greenflags', function ($atts, $content = '') {
$atts = shortcode_atts(['flag' => ''], $atts);
return gf_is_enabled($atts['flag']) ? do_shortcode($content) : '';
});
// [greenflags flag="summer-promo"]<p>Free shipping this week!</p>[/greenflags]
Define the token in wp-config.php (never in the database or a template):
define('GREENFLAGS_TOKEN', 'gf_...');
With an object cache plugin (Redis/Memcached), transients are shared across all PHP workers — one billable read per minute for the whole site.
new Client(...)| Parameter | Type | Description |
|---|---|---|
| url | string | API base URL, trailing slash optional. |
| apiToken | string | Environment-scoped token (gf_...). |
| coordinates | ?Coordinates | Optional end-user location for geofence evaluation. |
| cache | ?SnapshotCacheInterface | Optional snapshot cache — strongly recommended in PHP-FPM. |
| httpGet | ?callable | Optional HTTP layer: (string $url, array $headers): array{int, string}. |
| Method | Signature | Description |
|---|---|---|
| sync | (int $ttlSeconds = 60): void | Cache-first load: 0 requests on hit, 1 billable read on miss (stored back with the TTL). |
| refresh | (int $cacheTtlSeconds = 60): void | Forces 1 request, replaces the snapshot, updates the cache. |
| getSnapshot | (): array<string, Flag> | Evaluated snapshot, keyed by flag key. Local read. |
| getAllFlags | (): list<Flag> | Every evaluated flag. Local read. |
| getFlag | (string $key, mixed $default = null): mixed | Evaluated value or $default. Never throws for missing flags. |
| isEnabled | (string $key): bool | true only when the flag exists and evaluates to true. |
| setCoordinates | (?Coordinates): void | Sets/clears the location for geofence evaluation. No network. |
Flags may carry a geofence (latitude/longitude/radius, configured in the dashboard). With coordinates set, each geofenced flag is evaluated locally:
false for boolean flags, null for the rest.Fail-open, by design: a geofence is not a security boundary.
$flags->setCoordinates(new GreenFlags\Coordinates(19.4326, -99.1332));
$flags->isEnabled('store-promo');
$flags->setCoordinates(null);
try {
$flags->refresh();
} catch (GreenFlags\GreenFlagsException $err) {
Log::warning("flags refresh failed: {$err->errorCode} ({$err->status})");
}
| errorCode | status | Cause |
|---|---|---|
| INVALID_TOKEN | 401 | Token missing, invalid, or revoked |
| QUOTA_EXCEEDED | 429 | Monthly read quota exhausted |
| BILLING_NO_SUBSCRIPTION | 429 | The workspace has no active subscription |
| BILLING_CANCELED | 429 | Subscription canceled |
| BILLING_PAST_DUE | 429 | Payment past due |
| BILLING_TRIAL_EXPIRED | 429 | Trial expired |
| BILLING_LIMIT_REACHED | 429 | Billing limit reached |
| NETWORK_ERROR | 0 | The request failed before a response was received |
| PARSE_ERROR | response status | Body wasn't valid JSON, or was missing data.flags |
| REQUEST_ERROR | response status | Non-2xx response with no parseable error code |
Read methods never throw any of these — they're always local.
Only refresh() — or a sync() cache miss — makes a request; every 2xx counts as one billable read. With sync(60) + a shared cache (APCu per server, Redis per fleet), your cost ceiling is 1 read per TTL per cache, independent of traffic.
cd sdks/php
composer install
composer test # PHPUnit — mocked HTTP, no real network
# or without PHP installed locally:
docker run --rm -v "$PWD":/app -w /app composer:2 composer install
docker run --rm -v "$PWD":/app -w /app php:8.3-cli vendor/bin/phpunit
Semver, while in 0.x: MINOR can include API changes, PATCH are fixes. Version-by-version detail in CHANGELOG.md.
@greenflags/client — JavaScript/TypeScript SDK@greenflags/react / @greenflags/vue — framework bindingsgreenflags — Dart/Flutter SDKgreenflags on PyPI — Python SDKgreenflags-go — Go SDK@greenflags/mcp — MCP server for AI agentsServer processes serve many users, so identity is per call — resolve rollout/variants for a specific end user with:
$value = $client->getFlagForUser('new-checkout', $userId);
$enabled = $client->isEnabledForUser('new-checkout', $userId);
getFlag (no user) keeps returning the raw value (fail-open). Bucketing is deterministic and identical across every GreenFlags SDK and the server (docs/rollout-hash-spec.md).