Skip to main content

Advanced Usage

These examples build on the variables in the core entry point: $settings, $http, $state, $provider, $usage and $features.

State and definition callbacks​

Subscribe on the same state service injected into the provider. Callbacks take no arguments; capture application values with use when needed. Save the returned IDs so a worker can unregister listeners when their owner is released.

$onId = $state->whenFeatureTurnsOn('new-dashboard', static function (): void {
error_log('new-dashboard turned on');
});
$offId = $state->whenFeatureTurnsOff('new-dashboard', static function (): void {
error_log('new-dashboard turned off');
});
$definitionsId = $state->whenDefinitionsChange(static function (): void {
error_log('Feature definitions changed');
});

$provider->refreshFeatures();

$state->unregisterFeatureStateChange('new-dashboard', $onId);
$state->unregisterFeatureStateChange('new-dashboard', $offId);
$state->unregisterDefinitionsChange($definitionsId);

An on/off subscription can invoke its callback immediately if that state is already known. These are provider state notifications, not per-user targeting notifications or authorization events.

Custom metric sources​

Each registry method accepts one callback and returns a registration ID. Measurement and counter callbacks return maps of metric key to numeric value. Observation callbacks return maps of metric key to [count, value] pairs.

use Toggly\FeatureManagement\Core\{MetricsRegistryService, MetricsService};

$registry = new MetricsRegistryService();
$measurementId = $registry->registerMeasurements(static fn (): array => [
'queue-depth' => 3.0,
]);
$observationId = $registry->registerObservations(static fn (): array => [
'checkout-time' => [2, 1.5],
]);
$counterId = $registry->registerCounters(static fn (): array => [
'completed-orders' => 2.0,
]);
$metrics = new MetricsService($settings, $http, $provider, $registry);
$metrics->sendMetrics();
$registry->unregisterMetrics($measurementId);
$registry->unregisterMetrics($observationId);
$registry->unregisterMetrics($counterId);

The numbers above are illustrative fixed values; replace them with values owned by your application. Callbacks remain registered until unregistered, so ensure your data source defines whether subsequent reads are current values or new increments. Flush the metrics service in the process that collected them.

Authorization for secured features​

The SDK calls isAllowed($featureKey) for a secured feature. Bind authorization to the current authenticated request; evaluation context is not forwarded into this interface. A flag is an additional condition, not a replacement for your application's normal authorization checks.

use Toggly\FeatureManagement\Contracts\FeatureAuthorizationServiceInterface;
use Toggly\FeatureManagement\Core\FeatureManager;

final class RequestFeatureAuthorization implements FeatureAuthorizationServiceInterface
{
public function __construct(private array $allowedFeatureKeys) {}

public function isAllowed(string $featureKey): bool
{
return in_array($featureKey, $this->allowedFeatureKeys, true);
}
}

// Supply only keys authorized by your server-side policy for this request.
$authorization = new RequestFeatureAuthorization(['premium-content']);
$features = new FeatureManager($provider, $usage, $provider, $authorization);
$enabled = $features->isEnabled('premium-content', ['identity' => 'alice']);

Variants​

A variant is a named configuration chosen by the definitions service for one identity. Build an isolated provider with enable_variants and identity set before its first refresh. This example intentionally has no snapshot, so it cannot load another identity's persisted variants.

use GuzzleHttp\Client;
use GuzzleHttp\Psr7\HttpFactory;
use Toggly\FeatureManagement\Config\TogglySettings;
use Toggly\FeatureManagement\Core\{FeatureProvider, FeatureStateService};
use Toggly\FeatureManagement\Http\TogglyHttpClient;

$variantSettings = new TogglySettings([
'app_key' => getenv('TOGGLY_APP_KEY') ?: '',
'environment' => 'Production',
'base_url' => getenv('TOGGLY_BASE_URL') ?: 'https://definitions.toggly.io/',
'use_signed_definitions' => true,
'enable_variants' => true,
'identity' => 'alice',
'enable_live_updates' => false,
]);
$variantHttp = new TogglyHttpClient(new Client(), new HttpFactory(), $variantSettings->getBaseUrl());
$variantProvider = new FeatureProvider($variantSettings, $variantHttp, new FeatureStateService());
try {
$variantProvider->refreshFeatures();
$variant = $variantProvider->getVariant('pricing-test');
$name = $variant['name'] ?? 'default';
$configuration = $variant['configurationValue'] ?? null;
$value = $variantProvider->getVariantValue('pricing-test');
} finally {
$variantProvider->shutdown();
}

getVariant returns {name, configurationValue} or null when there is no enabled variant. Neither FeatureManager nor the Laravel facade exposes this method. Ordinary isEnabled context does not retarget variants; use the correct provider identity scope from the start.

Long-running hosts​

Your application owns polling and telemetry schedules. This bounded CLI loop illustrates the lifecycle; replace the loop condition with your worker's stop signal and perform application work between iterations. It uses $metrics from the metric setup above and core objects configured for this worker.

$nextRefresh = 0;
$nextFlush = 0;
$stopAt = time() + 60;
try {
while (time() < $stopAt) {
$now = time();
if ($now >= $nextRefresh) {
$provider->refreshFeatures();
$nextRefresh = $now + $settings->refreshInterval;
}
$provider->tick();
if ($now >= $nextFlush) {
$usage->flush();
$metrics->flush();
$nextFlush = $now + 60;
}
usleep(100000);
}
} finally {
$usage->flush();
$metrics->flush();
$provider->shutdown();
}

tick() drives WebSocket processing; it does not replace the explicit refresh schedule. Enable live updates in the settings before constructing the provider when the host supports them. No separate WebSocket object needs injection.

For workers handling different users, pass evaluation context per call and create request-local telemetry and authorization objects. Do not cache a user-specific evaluation solely by flag key. In Laravel, read the persistent-worker limitations before sharing the adapter's singleton services across requests.

Testing your integration​

Use deterministic definition responses through your PSR HTTP client and execute the real manager. Verify enabled and disabled branches, unknown flags, the first refresh, two different user contexts, snapshot restart behavior and shutdown flushing. A subclass of FeatureManager still requires its real constructor dependencies unless you supply a constructor of your own. For application unit tests, inject the narrow feature-check interface your application uses instead of creating a partially initialized SDK manager.