traffical / sdk
Traffical PHP SDK — deterministic feature flags, experiments, and warehouse-native experimentation. Conformance-tested against the language-agnostic Traffical SDK spec.
Requires
- php: ^8.1
- php-http/discovery: ^1.19
- psr/clock: ^1.0
- psr/http-client: ^1.0
- psr/http-factory: ^1.0
- psr/http-message: ^1.1 || ^2.0
- psr/log: ^2.0 || ^3.0
- psr/simple-cache: ^2.0 || ^3.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.59
- guzzlehttp/guzzle: ^7.8
- nyholm/psr7: ^1.8
- php-http/mock-client: ^1.6
- phpstan/phpstan: ^1.11
- phpunit/phpunit: ^10.5
Suggests
- guzzlehttp/guzzle: PSR-18 HTTP client for fetching config and sending events
- illuminate/support: Required for the Laravel service provider and facade
- open-feature/sdk: Required for the OpenFeature provider
- symfony/cache: PSR-16 shared cache store so PHP-FPM workers share one config bundle
- symfony/config: Required for the Symfony bundle configuration tree
- symfony/dependency-injection: Required for the Symfony bundle integration
- symfony/http-client: Alternative PSR-18 HTTP client
- symfony/http-kernel: Required for the Symfony bundle integration
This package is not auto-updated.
Last update: 2026-07-03 22:44:27 UTC
README
Traffical is one control plane for experiments, feature flags, and adaptive optimization. Instead of hard-coding decisions, you expose typed parameters — numbers, strings, booleans, and JSON, not just on/off toggles — and control behavior across web, mobile, push, and backend from a single place. Parameters are resolved locally in the SDK (sub-millisecond, no network round trips at runtime), and metrics are computed warehouse-native, against data you own. Start with a feature flag, graduate it to an A/B test, and let adaptive optimization shift traffic to the winning variant — all on the same parameter, without a deploy.
This package is the official PHP SDK (PHP ^8.1) that brings the Traffical parameter control plane to your
PHP services.
Features
- Local, in-process evaluation — resolve a cached config bundle with no per-decision network call.
- Typed parameters with safe defaults — bool / string / number / JSON, each with a caller-provided fallback.
- Layered experiments & targeting — Google-style layered isolation, condition/attribute segmentation, and progressive (percentage) rollouts.
- Adaptive optimization — contextual-bandit scoring evaluated client-side.
- BYO warehouse-native assignment logging — route structured assignment rows through your own pipeline (Segment, RudderStack, a DB, a queue) so assignment data never has to leave your infrastructure.
- Event tracking — exposure, decision, and custom track events, batched and flushed PHP-FPM-aware via
fastcgi_finish_request()so the response returns before events are sent. - Plugin system — hook into the
decide/exposure/tracklifecycle from day one. - PSR-first & framework-ready — PSR-18 HTTP, PSR-17 factories, PSR-16 cache, PSR-3 logger, and PSR-20 clock are all injectable; first-party Laravel, Symfony, and OpenFeature integrations.
Modes
- Bundle mode (default) — the SDK fetches a config bundle, caches it (shared across PHP-FPM workers via a PSR-16 store), and resolves every parameter locally. No per-decision network call.
- Server mode — resolution is delegated to the Traffical edge via
POST /v1/resolve(cached per request). Use it when you want zero client-side evaluation logic.
Further reading
- docs/warehouse-native.md — BYO assignment logging and warehouse-native metrics
- docs/plugins.md — the plugin lifecycle and built-in plugins
- docs/php-lifecycle.md — event batching and flushing under PHP-FPM
- docs/conformance.md — cross-language determinism and the bundle-vs-server tradeoffs
- examples/ — runnable examples: basic, server mode, warehouse-native BYO, custom plugin, plus Laravel and Symfony guides
Installation
composer require traffical/sdk
You also need a PSR-18 HTTP client and PSR-17 factories. Any compliant implementation works; the SDK auto-discovers them via php-http/discovery:
composer require guzzlehttp/guzzle nyholm/psr7
# or: composer require symfony/http-client nyholm/psr7
Quickstart (bundle mode)
use Traffical\Client; use Traffical\ClientOptions; $client = new Client(new ClientOptions( orgId: 'org_...', projectId: 'prj_...', env: 'production', apiKey: 'your_sdk_key', )); // Resolve parameters with defaults as the fallback. $params = $client->getParams( context: ['userId' => 'user-abc', 'country' => 'US'], defaults: ['checkout_button_color' => 'blue', 'discount_pct' => 0], ); $color = $params['checkout_button_color'];
Decide + track exposure
decide() returns a DecisionResult (assignments + metadata). Call trackExposure() when the user actually
sees the treatment so exposures are attributed correctly.
$decision = $client->decide( context: ['userId' => 'user-abc'], defaults: ['hero_variant' => 'control'], ); $variant = $decision->assignments['hero_variant']; // ...render the variant, then record that the user saw it: $client->trackExposure($decision); // Custom analytics events: $client->track('checkout_completed', ['revenue' => 49.0], $decision->decisionId); // Flush is automatic on shutdown; call explicitly in worker/CLI contexts: $client->flushEvents();
Server mode
Delegate resolution to the edge worker instead of evaluating a local bundle. Each getParams()/decide()
performs (and caches per request) a POST /v1/resolve.
$client = new Client(new ClientOptions( orgId: 'org_...', projectId: 'prj_...', env: 'production', apiKey: 'your_sdk_key', evaluationMode: 'server', ));
See docs/conformance.md for the bundle-vs-server tradeoffs.
BYO warehouse-native assignment logging
Pass an assignmentLogger to route structured rows through your own pipeline. The WarehouseNativeLogger
helper maps each entry to a snake_case row (including the stable policy_key/allocation_key used for
warehouse joins):
use Traffical\Client; use Traffical\ClientOptions; use Traffical\Warehouse\WarehouseNativeLogger; $logger = new WarehouseNativeLogger(function (array $row): void { // INSERT $row into your warehouse / CDP / queue. }); $client = new Client(new ClientOptions( orgId: 'org_...', projectId: 'prj_...', env: 'production', apiKey: 'your_sdk_key', assignmentLogger: $logger, disableCloudEvents: true, // keep assignment data on your own infra ));
Full guide: docs/warehouse-native.md.
Plugins
Hook into the SDK lifecycle (onBeforeDecision, onDecision, onExposure, onTrack, …). Built-ins:
DebugPlugin, DecisionTrackingPlugin, WarehouseNativeLoggerPlugin.
use Traffical\ClientOptions; use Traffical\Plugins\DebugPlugin; $options = new ClientOptions(/* ... */, plugins: [new DebugPlugin()]);
Full guide: docs/plugins.md.
Framework integrations
- Laravel — auto-discovered
TrafficalServiceProvider+Trafficalfacade. See examples/laravel.md. - Symfony —
TrafficalBundlewith atrafficalconfig tree. See examples/symfony.md. - OpenFeature — optional
TrafficalProvider(requiresopen-feature/sdk).
PHP lifecycle
The client registers a shutdown handler that calls fastcgi_finish_request() (when available) so the HTTP
response is returned to the user before events are flushed. See docs/php-lifecycle.md.
Configuration reference
ClientOptions is an immutable value object. Construct it with named arguments, or refine an existing instance
with the fluent with*() methods (each returns a new instance):
| Option | Default | Description |
|---|---|---|
orgId, projectId, env, apiKey |
— | Required scoping + auth |
baseUrl |
https://sdk.traffical.io |
Control-plane base URL |
localConfig |
null |
Bootstrap/offline ConfigBundle |
refreshIntervalMs |
60000 |
Cached bundle TTL |
evaluationMode |
bundle |
bundle (local) or server |
assignmentLogger |
null |
BYO warehouse logger (callable or AssignmentLogger) |
disableCloudEvents |
false |
Stop sending events to Traffical |
deduplicateAssignmentLogger |
true |
Dedup logger calls per request |
eventBatchSize |
10 |
Auto-flush threshold |
httpClient, requestFactory, streamFactory |
discovered | PSR-18/17 seams |
cache |
null |
PSR-16 shared store (FPM workers share one bundle) |
logger |
NullLogger |
PSR-3 logger |
clock |
system | PSR-20 clock |
plugins |
[] |
Plugin list |
Cross-language conformance
The PHP SDK shares the language-agnostic Traffical SDK spec with the
JS/TS and Swift SDKs: the same SHA-256 v2 (UTF-8 byte) bucketing, the same layered resolution engine, and the same
contextual-bandit scoring. Every release is gated on the spec's deterministic conformance vectors, so a given
unit buckets identically on every platform. The fixtures are pinned via the tests/sdk-spec git submodule and
run as part of composer conformance.
Development
composer install composer test # full PHPUnit suite (unit + conformance + integration) composer conformance # only the sdk-spec vectors composer phpstan # static analysis at level max composer cs-check # PSR-12 style check
Conformance fixtures are pinned via the tests/sdk-spec git submodule. After cloning:
git submodule update --init --recursive
License
MIT