承接 greenflags/greenflags-php 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

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 in CHANGELOG.md.

composer require greenflags/greenflags-php

Table of Contents

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:

  1. sync(60) checks the cache. Fresh snapshot there? Zero network calls.
  2. 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; getFlag returns 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 / isEnabled never throw for missing flagsgetFlag 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).
  • Pick your cache by deployment: 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).

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 — false for boolean flags, null for 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

统计信息

  • 总下载量: 0
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 0
  • 依赖项目数: 0
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 0
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2026-07-11

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固