greenflags/greenflags-php
Composer 安装命令:
composer require greenflags/greenflags-php
包简介
PHP SDK for GreenFlags feature flags: cached snapshot reads, geofence evaluation. Zero dependencies, billing-safe for PHP-FPM via pluggable snapshot cache.
README 文档
README
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
Table of Contents
- Why it exists
- The PHP-FPM problem (and the fix)
- Features
- Requirements
- Quick Start
- Usage Guide
- Laravel Recipe
- WordPress / WooCommerce Recipe
- API Reference
- Geofence
- Error Handling
- Billing Model
- Development
- Versioning
Why it exists
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.
The PHP-FPM problem (and the fix)
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.- Cache empty or expired? One
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).
Features
- ✅ Zero dependencies — Composer installs nothing else.
- ✅ Billing-safe in PHP-FPM via the snapshot cache +
sync(). - ✅ Fail-open — a failed refresh keeps the last loaded snapshot;
getFlagreturns your default for missing flags. - ✅ Client-side geofence evaluation — the end-user's location never leaves your server.
- ✅ Injectable HTTP layer — trivial to test, or to swap streams for Guzzle/cURL.
- ✅ PHP 8.1+,
declare(strict_types=1), readonly value objects.
Requirements
- PHP 8.1+ with
ext-json. - A GreenFlags API token, generated from the dashboard for a specific
project + environment. See the API docs for the full contract.
Quick Start
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 }
Usage Guide
// 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);
Ground rules
getFlag/isEnablednever throw for missing flags —getFlagreturns your default andisEnabledreturnsfalse.sync()/refresh()can throwGreenFlagsException— butsync()swallows network failures when it already has flags to serve (fail-open for long-running runtimes).- Pick your cache by deployment:
ApcuCachefor classic FPM,InMemoryCachefor Octane/queues/CLI, or write a Redis/Laravel adapter for multi-server fleets (one read per TTL for the whole fleet).
Laravel Recipe
// 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 / WooCommerce Recipe
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.
API Reference
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}. |
Methods
| 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. |
Geofence
Flags may carry a geofence (latitude/longitude/radius, configured in the dashboard). With coordinates set, each geofenced flag is evaluated locally:
- Inside the radius (on-edge counts as inside): the flag's normal value.
- Outside: the off value —
falseforbooleanflags,nullfor the rest. - No coordinates, or no geofence: the normal value, unaffected.
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);
Error Handling
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.
Billing Model
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.
Development
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
Versioning
Semver, while in 0.x: MINOR can include API changes, PATCH are fixes. Version-by-version detail in CHANGELOG.md.
Related
- API reference
@greenflags/client— JavaScript/TypeScript SDK@greenflags/react/@greenflags/vue— framework bindingsgreenflags— Dart/Flutter SDKgreenflagson PyPI — Python SDKgreenflags-go— Go SDK@greenflags/mcp— MCP server for AI agents
统计信息
- 总下载量: 0
- 月度下载量: 0
- 日度下载量: 0
- 收藏数: 0
- 点击次数: 0
- 依赖项目数: 0
- 推荐数: 0
其他信息
- 授权协议: MIT
- 更新时间: 2026-07-11