Skip to content

PHP Feature Flags Quickstart

This guide gets you from zero to evaluating a feature flag in a PHP application. The SDK uses PSR-compatible interfaces and works with any framework.

  • A Featureflip account with at least one feature flag created
  • An SDK key from your environment settings
  • PHP 8.2+
  • Composer
Terminal window
composer require featureflip/featureflip-php guzzlehttp/guzzle symfony/cache

This installs the SDK along with Guzzle (PSR-18 HTTP client and PSR-17 factories) and Symfony Cache (PSR-16 cache).

<?php
require __DIR__ . '/vendor/autoload.php';
use Featureflip\FeatureflipClient;
use Featureflip\Config;
use GuzzleHttp\Client as Guzzle;
use GuzzleHttp\Psr7\HttpFactory;
use Symfony\Component\Cache\Psr16Cache;
use Symfony\Component\Cache\Adapter\FilesystemAdapter;
$cache = new Psr16Cache(new FilesystemAdapter());
// Always give the HTTP client a timeout. The SDK fetches its configuration on
// the calling thread, and Guzzle's default is no timeout at all — so without
// one, an Evaluation API that stalls can block the request.
$guzzle = new Guzzle(['timeout' => 5, 'connect_timeout' => 2]);
$factory = new HttpFactory();
$config = new Config(
baseUrl: 'https://eval.featureflip.io',
cache: $cache,
httpClient: $guzzle,
requestFactory: $factory,
streamFactory: $factory,
);
$client = FeatureflipClient::get('your-sdk-key', $config);

The factory fetches your flag configuration when it is due a refresh, and registers a shutdown function to flush events automatically. The same check runs when you evaluate a flag, so a client held across many requests — Laravel Octane, RoadRunner, FrankenPHP, Swoole — keeps picking up changes rather than freezing at whatever it booted with.

The SDK key can also come from the FEATUREFLIP_SDK_KEY environment variable: pass an empty string and it is read from there.

$showBanner = $client->boolVariation('new-banner', ['user_id' => 'user-123'], false);
if ($showBanner) {
echo "Showing the new banner";
} else {
echo "Using the default experience";
}

Each variation method is typed to its return value. The default argument is returned when the flag is not found or evaluation fails.

$color = $client->stringVariation('banner-color', ['user_id' => 'user-123'], 'blue');
$limit = $client->numberVariation('rate-limit', ['user_id' => 'user-123'], 100);

The SDK registers a shutdown function that flushes pending analytics events when the PHP process exits. You can also flush or close manually:

$client->flush(); // Send buffered events immediately
$client->close(); // Flush events and finish the request (PHP-FPM)

For long-running processes, call $client->close() when your application shuts down.

The PHP SDK is a server-side SDK that evaluates flags locally in your PHP process. It fetches the full flag configuration from the Featureflip evaluation API and stores it in the PSR-16 cache you provide. Requests made before pollInterval has elapsed reuse that configuration without any API call. Because evaluation happens locally, boolVariation() and the other variation calls return immediately.

Unlike the other server SDKs, PHP has no background thread to poll on: it re-fetches when the interval has elapsed and something asks for a flag. Under PHP-FPM that is naturally once per interval across your workers; under a persistent runtime (Octane, RoadRunner, FrankenPHP, Swoole) the same check runs on the evaluation path, and you can call $client->refresh() from a tick hook to move the fetch between requests instead of inside one.

The stored configuration is kept, not expired on a timer. Only a successful fetch replaces it, so an evaluation API outage leaves your flags serving their last known good values instead of the defaults you pass at each call site.

Flags are stale or do not update after changes in the dashboard

Flag changes take effect once pollInterval has elapsed — 30 seconds by default. Lower it for faster propagation:

$config = new Config(pollInterval: 5, /* ... */);

Note this is a refresh interval, not a cache lifetime. A configuration that has been fetched successfully is kept, so if the Evaluation API becomes unreachable your flags keep serving their last known good values rather than falling back to the defaults you pass at each call site.

Every flag returns the default I passed

The SDK does not throw when it cannot load a configuration — it reports the failure and serves your defaults, so a bad SDK key or an unreachable Evaluation API looks like “every flag is off” rather than an exception. Two things to check:

if (!$client->isInitialized()) {
// Nothing was ever loaded — the SDK is serving your defaults.
}

and the log. The SDK writes to PHP’s error log by default, or to any PSR-3 logger you pass as logger:. A rejected key looks like:

[featureflip] flag configuration fetch failed: HTTP 401 from /v1/sdk/flags
- no cached configuration is available, so flags will serve their caller defaults

Verify the key belongs to the environment you expect, and that baseUrl includes the protocol (https://).

InvalidArgumentException from the cache layer

PSR-16 cache keys have reserved characters ({}()/\@:). The SDK handles this internally, but if you see this error, ensure you are using a PSR-16 compliant cache implementation. Symfony Cache and Laravel’s cache are both compatible.