Agent skill
Installable LLM skill for the PHP SDK (configure() + Client(user), evaluate, experiment + track, testing).
An installable agent skill for the PHP SDK. Fetch it with shipeasy docs skill --sdk php (--install writes it to your agent skills dir), or copy it below — the YAML frontmatter installs with it.
---
name: shipeasy-php
description: Use Shipeasy (feature flags, configs, kill switches, A/B experiments, i18n) from PHP. Covers configure() + Client($user), getFlag/getConfig/getKillswitch, universe()->assign(), track, testing, OpenFeature.
---
# Shipeasy PHP SDK
Server SDK for PHP 8.1+ (Laravel, Symfony, WordPress, Slim; PHP-FPM friendly —
fetches once per request, no background poll). Package: `shipeasy/shipeasy`.
> The documented surface is exactly **`Shipeasy\configure()`** (setup) and the
> bound **`new Shipeasy\Client($user)`** (use), plus the package-level functions
> below. For deeper docs, fetch any page/snippet from the manifest at
> <https://shipeasy-ai.github.io/sdk-php/manifest.json> (raw URLs below).
## Install
```bash
composer require shipeasy/shipeasy
```
## Configure once, bind a user per request
```php
require 'vendor/autoload.php';
use function Shipeasy\configure;
use Shipeasy\Client;
// Once at startup (SERVER key). Optional 2nd arg maps your user -> attribute map.
configure(getenv('SHIPEASY_SERVER_KEY'), fn ($u) => [
'user_id' => $u->id,
'plan' => $u->plan,
]);
// Per request — bind the user once, call with NO user argument:
$c = new Client($currentUser); // construct once per callsite
$enabled = $c->getFlag('new_checkout'); // bool, default false
$copy = $c->getConfig('billing_copy', ['x' => 1]); // typed value or default
$panic = $c->getKillswitch('payments_panic'); // bool
$d = $c->getFlagDetail('new_checkout'); // $d->value, $d->reason
```
`configure()` is first-config-wins and fetches once per request (PHP has no
background poll). `new Client($user)` throws if called before `configure()`.
**Quiet outside production.** All egress (fetch, `track`, exposures, `see()`,
telemetry) **defaults ON in production and OFF everywhere else**, decided from
`SHIPEASY_ENV` → `APP_ENV` → `ENV` (`production`/`prod` ⇒ on), falling back to the
`env` option. So on a dev machine / CI the SDK sends nothing until you opt in —
reads still resolve overrides/defaults. Force it with the `isNetworkEnabled`
option (`true`/`false`) or set `SHIPEASY_ENV=production`.
Reference: <https://shipeasy-ai.github.io/sdk-php/pages/configuration.md> ·
<https://shipeasy-ai.github.io/sdk-php/pages/flags.md> ·
<https://shipeasy-ai.github.io/sdk-php/pages/killswitches.md>
### Laravel
Don't hand-write a provider — the package auto-discovers `ShipeasyServiceProvider`.
Run `php artisan shipeasy:install` (add `--i18n` for the client key), publish
`config/shipeasy.php`, set `SHIPEASY_SERVER_KEY` in `.env`, and the provider calls
`configure()` for you on boot. Map your user model via the `attributes` config
(an invokable class name). Place the `@shipeasyBootstrap($user)` and `@shipeasyI18n`
Blade directives in your layout `<head>`. Reference:
<https://shipeasy-ai.github.io/sdk-php/pages/installation.md>
## Experiments + track (Client-only, end to end)
Experiments are read by **universe** (a mutual-exclusion pool — the unit lands in
≤1 experiment). `assign()` is side-effect free; the exposure fires **on read** —
the first `get()` logs one deduped exposure for the enrolled unit. Pass
`exposure: false` to peek without logging.
```php
$c = new Client($currentUser); // construct once per callsite
$a = $c->universe('checkout')->assign(); // Shipeasy\Assignment (no exposure yet)
$color = $a->get('color', 'blue'); // variant ?? universe default ?? fallback; first read logs the exposure
$peek = $a->get('color', 'blue', false); // peek — get($field, $fallback, exposure: false), no exposure
// $a->name, $a->group, $a->enrolled()
$c->track('checkout_success', ['amount' => 49]); // conversion for the bound user
```
Reference: <https://shipeasy-ai.github.io/sdk-php/pages/experiments.md> · track
snippet <https://shipeasy-ai.github.io/sdk-php/snippets/metrics/track.md>
## Error reporting — see()
```php
use function Shipeasy\see;
try {
chargeCard($order);
} catch (\Throwable $e) {
// extras fold into the terminal. NEVER ->causesThe(x)->extras([...])->to(y),
// which splits the consequence sentence in half.
see($e)->causesThe('checkout')->to('use the backup processor', ['order_id' => $id]);
}
```
A stray `->extras(...)` after `->to(...)` is ignored with a warning — it never
throws into the catch block, but the extras are dropped. To attach context from anywhere in a request without
threading it into the catch, buffer it with `Shipeasy\addExtras(['order_id' => $id])`;
every later `see()` in the same request merges it in. **PHP is share-nothing per
request:** under PHP-FPM the buffer resets per request; under a long-running
runtime (Swoole / RoadRunner / workers) call `Shipeasy\clearExtras()` at request
end.
`Shipeasy\controlFlowException($e)->because('expected')` marks an exception as
expected (reports nothing). `Shipeasy\seeViolation('name')->...->to(...)` reports a
non-exception. Reference:
<https://shipeasy-ai.github.io/sdk-php/pages/error-reporting.md> · snippet
<https://shipeasy-ai.github.io/sdk-php/snippets/ops/see.md>
## Testing (no network, no key)
```php
use function Shipeasy\configureForTesting;
use function Shipeasy\configureForOffline;
use function Shipeasy\overrideFlag;
use function Shipeasy\clearOverrides;
use Shipeasy\Client;
// Seed values up front; reads go through the ordinary new Client($user). Replaces
// prior config, so each test can reconfigure freely.
configureForTesting([
'flags' => ['new_checkout' => true],
'configs' => ['billing_copy' => ['headline' => 'Hi']],
'experiments' => ['checkout_button' => ['treatment', ['color' => 'green']]],
]);
$c = new Client(['user_id' => 'u_1']);
$c->getFlag('new_checkout'); // true
overrideFlag('new_checkout', false); // flip on the spot
clearOverrides(); // drop every override (incl. the seed)
// Offline: evaluate the REAL rules from a snapshot or JSON file, no network.
configureForOffline(['path' => 'shipeasy-snapshot.json']);
```
Reference: <https://shipeasy-ai.github.io/sdk-php/pages/testing.md>
## OpenFeature
```php
use OpenFeature\OpenFeatureAPI;
use Shipeasy\OpenFeature\ShipeasyProvider;
// Assumes Shipeasy\configure(...) ran — the no-arg provider resolves it.
OpenFeatureAPI::getInstance()->setProvider(new ShipeasyProvider());
```
Install `open-feature/sdk ^2.0` (optional dep). Booleans → gates; other types →
configs. Reference: <https://shipeasy-ai.github.io/sdk-php/pages/openfeature.md>
## i18n + SSR
Server-side: emit the bootstrap + i18n loader tags (the i18n tag carries the
**public client key**); the **browser** client SDK renders `t()`.
```php
use function Shipeasy\bootstrapScriptTag;
use function Shipeasy\devtoolsScriptTag;
use function Shipeasy\i18nScriptTag;
// Every argument is optional — clientKey / profile / projectId / cdnBaseUrl all
// come from the configure() opts.
$head = bootstrapScriptTag($user, ['anonId' => $anonId])
. i18nScriptTag()
. devtoolsScriptTag(); // devtools overlay: Shift+Alt+S or ?se=1
```
Reference: <https://shipeasy-ai.github.io/sdk-php/pages/i18n.md> ·
<https://shipeasy-ai.github.io/sdk-php/pages/advanced.md>Was this page helpful?Updated July 26, 2026