Toggly Feature Management for PHP

September 6, 2026 · View on GitHub

Packagist License: MIT Documentation Website

Official PHP SDK for Toggly feature flags — Composer packages for core, Laravel, and WordPress.

Repository layout

PathPackage namePackagist publish surface
Repo root (src/, root composer.json)toggly/feature-management-phpThis repo (Packagist GitHub URL)
packages/laravel/toggly/laravelMirror ops-ai/Toggly.FeatureManagement.PHP.Laravel
packages/wordpress/toggly/wordpressMirror ops-ai/Toggly.FeatureManagement.PHP.Wordpress

Root composer.json is the publishable core package. Path repositories under packages/* exist for local CI so Laravel/WordPress can depend on core in-tree. Namespaces: Toggly\FeatureManagement\, Toggly\Laravel\, Toggly\WordPress\.

Installation

Core only

composer require toggly/feature-management-php

Laravel (includes core)

composer require toggly/laravel

WordPress (includes core)

composer require toggly/wordpress

toggly/laravel and toggly/wordpress are published on Packagist from the mirror repos (ops-ai/Toggly.FeatureManagement.PHP.Laravel and ops-ai/Toggly.FeatureManagement.PHP.Wordpress). For local monorepo development, path repositories under packages/* are configured in the root composer.json.

For non-Composer WordPress installs, copy or symlink the toggly/wordpress package directory to wp-content/plugins/toggly/ so WordPress loads toggly.php.

Migration from pre-1.0: if you previously relied on Laravel/WordPress classes from toggly/feature-management-php alone, also require toggly/laravel or toggly/wordpress. Core 1.0 no longer ships those namespaces.

Features

  • Full Feature Parity: Matches the functionality of the .NET Toggly.FeatureManagement library
  • Signed Definitions: ECDSA signature verification for secure feature definitions
  • Real-time Updates: WebSocket support for instant feature updates (with polling fallback)
  • Usage Statistics: Automatic tracking of feature usage and user analytics
  • Metrics Collection: Support for measurements, observations, and counters
  • Snapshot Providers: Cache, database, and file-based snapshot storage
  • Laravel Integration: Native Laravel service provider, facade, and middleware
  • WordPress Plugin: Full WordPress plugin with admin interface and hooks
  • PSR Standards: Built on PSR-4, PSR-11, PSR-16, PSR-18, and PSR-17

Quick Start

Laravel

Package auto-discovery registers the service provider and Toggly facade. Manual config/app.php registration is only needed if auto-discovery is disabled.

  1. Publish the configuration:
php artisan vendor:publish --tag=toggly-config
  1. Configure in .env:
TOGGLY_APP_KEY=your-app-key
TOGGLY_ENVIRONMENT=Production
TOGGLY_USE_SIGNED_DEFINITIONS=false
  1. Use in your code:
use Toggly\Laravel\Facades\Toggly;
use Toggly\FeatureManagement\Contracts\FeatureStateServiceInterface;
use Toggly\FeatureManagement\Core\MetricsService;

// Check if feature is enabled
if (Toggly::isEnabled('new-checkout')) {
    return view('checkout.v2');
}

// With evaluation context (identity / groups / claims / request preferred;
// legacy userId is still accepted)
$enabled = Toggly::isEnabled('premium-feature', [
    'identity' => (string) $user->id,
    'groups' => $user->groups ?? [],
]);

// State change handlers live on FeatureStateService (not the facade)
app(FeatureStateServiceInterface::class)->whenFeatureTurnsOn('new-api', function () {
    // Initialize new API
});

// Metrics live on MetricsService
$metrics = app(MetricsService::class);
$metrics->measure('checkout-completed', 125.50);
$metrics->observe('active-users', 1500);
$metrics->incrementCounter('api-calls', 1);
  1. Use middleware in routes (alias feature if not already registered):
Route::get('/new-feature', function () {
    return view('new-feature');
})->middleware('feature:new-feature');

WordPress

  1. Install the plugin by copying to wp-content/plugins/toggly/

  2. Activate the plugin in WordPress admin

  3. Configure in Settings > Toggly:

    • App Key
    • Environment
    • Base URL (optional)
    • Use Signed Definitions (optional)
  4. Use in templates:

<?php if (toggly_is_enabled('new-header')): ?>
    <?php get_template_part('header', 'new'); ?>
<?php endif; ?>
  1. Use shortcode:
[toggly_feature name="premium-content"]
    <!-- Premium content here -->
[/toggly_feature]
  1. Use hooks in functions.php:
add_action('toggly_feature_turns_on', function($featureKey) {
    if ($featureKey === 'new-theme') {
        // Activate new theme
    }
});

Core Library Usage

Basic Usage

use Toggly\FeatureManagement\Config\TogglySettings;
use Toggly\FeatureManagement\Core\FeatureProvider;
use Toggly\FeatureManagement\Core\FeatureManager;
use Toggly\FeatureManagement\Http\TogglyHttpClient;

$settings = new TogglySettings([
    'app_key' => 'your-app-key',
    'environment' => 'Production',
]);

$httpClient = new TogglyHttpClient(/* PSR-18 client */, /* PSR-17 factory */, $settings->getBaseUrl());
$featureProvider = new FeatureProvider($settings, $httpClient, /* state service */);
$featureManager = new FeatureManager($featureProvider, /* usage stats */, /* secure provider */);

// Check feature
if ($featureManager->isEnabled('my-feature')) {
    // Feature is enabled
}

Snapshot Providers

Cache Provider (PSR-16)

use Toggly\FeatureManagement\Storage\SnapshotProviders\CacheSnapshotProvider;
use Toggly\FeatureManagement\Storage\SnapshotSettings;

$snapshotProvider = new CacheSnapshotProvider(
    $cache, // PSR-16 cache implementation
    new SnapshotSettings(['document_name' => 'toggly_features']),
    86400 // TTL in seconds
);

Database Provider (PDO)

use Toggly\FeatureManagement\Storage\SnapshotProviders\DatabaseSnapshotProvider;

$snapshotProvider = new DatabaseSnapshotProvider(
    $pdo, // PDO instance
    new SnapshotSettings(['document_name' => 'toggly_features'])
);

File Provider

use Toggly\FeatureManagement\Storage\SnapshotProviders\FileSnapshotProvider;

$snapshotProvider = new FileSnapshotProvider(
    '/path/to/snapshots',
    new SnapshotSettings(['document_name' => 'toggly_features.json'])
);

Configuration

TogglySettings

$settings = new TogglySettings([
    'app_key' => 'your-app-key',
    'environment' => 'Production',
    'base_url' => 'https://app.toggly.io/',
    'use_signed_definitions' => true,
    'allowed_key_ids' => ['key-id-1', 'key-id-2'],
    'refresh_interval' => 300, // 5 minutes
    'app_version' => '1.0.0',
    'instance_name' => 'server-1',
    'undefined_enabled_on_development' => false,
]);

Advanced Features

Feature State Change Handlers

$stateService = $container->get(FeatureStateServiceInterface::class);

// Register callback
$id = $stateService->whenFeatureTurnsOn('new-feature', function() {
    // Initialize feature
});

// Unregister
$stateService->unregisterFeatureStateChange('new-feature', $id);

Custom Metrics

$metricsService = $container->get(MetricsServiceInterface::class);

// Record measurement (aggregated over time)
$metricsService->measure('revenue', 1250.50);

// Record observation (point-in-time)
$metricsService->observe('active-users', 1500);

// Increment counter
$metricsService->incrementCounter('api-calls', 1);

Usage + metrics telemetry (gRPC parity)

UsageStatsProvider and MetricsService batch in-process and flush via sendStats() / sendMetrics() (or flush()). Timers are host-driven: register Laravel schedule / WP-Cron (see Laravel README). A best-effort shutdown flush is registered automatically.

Transport (prefer gRPC):

  1. When ext-grpc and Composer package google/protobuf are available, the SDK dials native gRPC Usage.SendStats / Metrics.SendMetrics against the metrics base URL (default https://app.toggly.io/).
  2. Otherwise it soft-fails and posts the same wire shape over HTTPS JSON to api/usage/stats and api/metrics (gateway-accepted path). Feature evaluation is never blocked by telemetry failures.

gRPC metadata: the SDK user-agent is sent as metadata key ua. PHP's gRPC extension lowercases HTTP/2 metadata keys; this matches .NET/Go/Node UA on the wire (case-insensitive).

# Optional native gRPC
pecl install grpc
composer require google/protobuf
$usage = $container->get(UsageStatsProvider::class);
$usage->recordUsage('checkout');
$usage->recordView('checkout'); // viewed / rendered
$usage->sendStats();

Wire payloads use multi-variant maps (variantStats / variantValues) and UTF-8 FNV-1a signed int32 identity hashes (Go/Node/Python parity).

Custom Context Provider

class MyContextProvider implements FeatureContextProviderInterface
{
    public function getContextIdentifier(): ?string
    {
        // Return unique user identifier
        return $this->getCurrentUserId();
    }

    // ... implement other methods
}

Evaluation context (filter parity)

Pass an EvalContext-shaped array to FeatureManager::isEnabled / evaluateDefinition:

$enabled = $featureManager->isEnabled('my-feature', [
    'identity' => (string) $user->id,
    'groups' => ['beta'],
    'claims' => ['role' => 'admin'],
    'request' => [
        'userAgent' => $_SERVER['HTTP_USER_AGENT'] ?? null,
        'acceptLanguage' => $_SERVER['HTTP_ACCEPT_LANGUAGE'] ?? null,
        'country' => null,
    ],
]);

// Or map headers (country: cf-ipcountry → x-vercel-ip-country → cloudfront-viewer-country)
use Toggly\FeatureManagement\Core\HttpRequestMapper;

$context = HttpRequestMapper::mergeIntoContext($headers, [
    'identity' => 'u',
    'groups' => ['beta'],
]);

Segment filters (BrowserFamily, BrowserLanguage, Country, DeviceType, OS / OperatingSystem) and UserClaims are evaluated in core FeatureManager. Classes under packages/laravel/src/Filters/ are legacy helpers and are not the evaluation path.

Requirements

  • PHP 7.4 or higher (8.2–8.4 recommended; CI covers 8.2, 8.3, and 8.4)
  • PSR-18 HTTP client (e.g., Guzzle, Symfony HTTP Client)
  • PSR-16 cache (optional, for snapshot provider)
  • PSR-11 container (optional, for dependency injection)

Laravel Requirements

  • Laravel 8.0 or higher
  • illuminate/support
  • illuminate/http

WordPress Requirements

  • WordPress 5.0 or higher
  • No external dependencies (uses WordPress APIs)

Architecture

The library follows a modular architecture:

  • Core Library: Framework-agnostic core functionality
  • Laravel Integration: Service provider, facade, and middleware (Laravel Filters/ classes are legacy; core FeatureManager owns filter evaluation)
  • WordPress Plugin: Full plugin with admin interface

Core Components

  • FeatureProvider: Fetches and manages feature definitions
  • FeatureManager: Evaluates features (including filter-parity segment / UserClaims filters) with stats tracking
  • HttpRequestMapper: Maps HTTP headers into EvalContext request
  • FeatureStateService: Manages state change notifications
  • UsageStatsProvider: Collects and sends usage statistics
  • MetricsService: Collects custom metrics for experiments
  • EcdsaSignatureVerifier: Verifies signed definitions
  • JwkManager: Manages JSON Web Keys for signature verification

Snapshot Providers

Three snapshot provider implementations are available:

  1. CacheSnapshotProvider: Uses PSR-16 cache (Redis, Memcached, etc.)
  2. DatabaseSnapshotProvider: Uses PDO (MySQL, PostgreSQL, SQLite)
  3. FileSnapshotProvider: Uses file system storage

Development

Running Tests

composer test

Code Style

The project follows PSR-12 coding standards.

Contributing

Please open an issue first for bugs and feature ideas, then follow CONTRIBUTING.md. Large PRs without prior discussion may be closed.

Security

Report vulnerabilities privately via GitHub Private Vulnerability Reporting. See SECURITY.md. Do not file public issues for security reports.

License

MIT

Support