Skip to main content

PHP

A server-side PHP SDK for Flagpole feature flags — a cache-backed client on cURL with zero Composer dependencies, and a Laravel integration.

Updates are by polling, not streaming

PHP requests are short-lived, so the SDK does not hold a WebSocket or a background poller. Instead it caches the flag set — pass a PSR-16 cache (the Laravel integration wires one automatically) and one request per FlagpoleConfig::$cacheTtl window (default 30 seconds) refetches from the API; the rest read the cache.

Without a cache, every initialize() refetches. Call refresh() to force a fetch and update the cache.

Table of Contents​

Features​

  • 🐘 Zero Composer dependencies in the core — ext-curl + ext-json
  • 🗄️ PSR-16 cache-backed: shares one flag fetch across requests
  • ⚡ Non-blocking reads: isEnabled() is an in-memory lookup after the first load
  • 🌍 Environment Targeting: load and evaluate flags per environment
  • 🎨 Laravel: auto-discovered provider, Flagpole facade, @feature Blade directive, feature: route middleware
  • 🔐 Fail-safe: unknown or unreachable flags return your configured fallback

Installation​

composer require flagpole/sdk

Requirements​

  • PHP 8.2+, ext-curl, ext-json
  • Optionally a PSR-16 (psr/simple-cache) implementation — strongly recommended outside Laravel

Quick Start​

use Flagpole\FlagpoleClient;
use Flagpole\FlagpoleConfig;

$client = new FlagpoleClient(
new FlagpoleConfig(
apiKey: 'fp_live_your_api_key',
environments: ['production'],
fallbacks: ['new-checkout' => false],
),
$psr16Cache, // optional; without it every initialize() refetches
);

$client->initialize();

if ($client->isEnabled('new-checkout')) {
// ...
}

$flag = $client->flag('new-checkout'); // ?FeatureFlag
$all = $client->allFlags(); // array<string, FeatureFlag>

API Reference​

FlagpoleClient​

MethodDescription
__construct(FlagpoleConfig $config, ?Psr\SimpleCache\CacheInterface $cache = null, ?Transport $transport = null)Construct. Performs no I/O.
initialize(): voidLoad flags (from cache if fresh, else the API). Throws on a failed fetch.
refresh(): voidForce a fetch and refresh the cache. Throws on failure.
isEnabled(string $name): boolFallback for unknown / disabled / non-matching flags. Never throws.
flag(string $name): ?FeatureFlagThe full flag, or null
allFlags(): arrayarray<string, FeatureFlag>
isInitialized(): boolWhether a load has completed
lastError(): ?FlagpoleExceptionThe error from the most recent implicit load

FlagpoleConfig​

Constructor (all named): apiKey, environment (FlagpoleEnvironment, default Production), environments (?array, default all), cacheTtl (int seconds, default 30), requestTimeout (int seconds, default 10), fallbacks (array<string, bool>), baseUrlOverride (?string).

FeatureFlag​

Readonly: id, name, description, isEnabled (raw switch — prefer isEnabled() on the client), project, organization, conditions (array), environments (list<string>, empty means "all"), createdAt, updatedAt (raw ISO-8601 strings).

Exceptions​

Flagpole\Exception\FlagpoleException (abstract) with ApiException (has ->statusCode, ->body), InvalidResponseException, and TransportException.

Laravel​

The service provider is auto-discovered. Publish the config and set your key:

php artisan vendor:publish --tag=flagpole-config
FLAGPOLE_API_KEY=fp_live_your_api_key
FLAGPOLE_ENVIRONMENTS=production
FLAGPOLE_CACHE_TTL=30

Reading flags​

use Flagpole\Laravel\Facades\Flagpole;
use Flagpole\FlagpoleClient;

// facade
if (Flagpole::isEnabled('new-checkout')) { /* … */ }

// injected (bound as a singleton, using Laravel's cache store)
Route::get('/checkout', fn (FlagpoleClient $flagpole) =>
$flagpole->isEnabled('new-checkout') ? 'new' : 'legacy');

Blade​

@feature('new-checkout')
<x-new-checkout />
@else
<x-legacy-checkout />
@endfeature

Route middleware​

// 404 when off; feature:beta-access,403 to pick the status
Route::get('/beta', BetaController::class)->middleware('feature:beta-access');

Configuration​

Environments​

environments is a filter — it controls which flags are loaded and how isEnabled evaluates targeting. A flag with no environments always applies.

Backend host​

FlagpoleEnvironmentREST
Developmenthttp://localhost:5000
Staginghttps://api.staging.useflagpole.dev
Productionhttps://useflagpole-api.onrender.com

Using your own HTTP client​

The core uses cURL. To route requests through your app's client, implement Flagpole\Transport\Transport and pass it as the third constructor argument (in Laravel, bind it in the container — the provider picks it up).

Error Handling​

isEnabled() returns the configured fallback (default false) for any flag it can't resolve and never throws.

initialize() and refresh() throw a FlagpoleException:

use Flagpole\Exception\ApiException;

try {
$client->initialize();
} catch (ApiException $e) {
logger()->warning("Flagpole rejected the request: {$e->statusCode}");
// carry on — isEnabled() will use fallbacks
}

Best Practices​

1. Always pass a cache​

Outside Laravel, pass a PSR-16 cache (Symfony Cache, symfony/cache, APCu, Redis…). Without one every request that touches a flag makes an HTTP call.

2. Initialize once per request​

Call initialize() in a middleware or service-provider boot so the first check already has flags. isEnabled() also loads lazily on first use.

3. Tune the cache TTL​

30s suits most rollouts. Drop it for kill-switch flags, or call refresh() from an admin action / webhook.

4. Always set fallbacks for critical flags​

new FlagpoleConfig(apiKey: '…', fallbacks: ['payments-v2' => false]);

Troubleshooting​

Flags are stale​

Expected up to one cacheTtl window. Lower it or call refresh().

Flags are always the fallback value​

  • Wrong or expired API key
  • The flag targets environments not in your environments list
  • The first fetch failed — check $client->lastError() or catch the exception from initialize()

Every request is slow​

You're running without a cache. Pass a PSR-16 cache (Laravel does this for you).

Contributing​

The SDK lives in the flagpole-sdks monorepo under packages/server/php.

git clone https://github.com/flagpole-corp/flagpole-sdks.git
cd flagpole-sdks/packages/server/php

composer install
vendor/bin/phpunit
vendor/bin/phpstan analyse

License​

MIT

Support​