Weave Code
Weaver
Helps Laravel developers discover, compare, and choose open-source packages. See popularity, security, maintainers, and scores at a glance to make better decisions.
Feedback
Share your thoughts, report bugs, or suggest improvements.
Subject
Message

Server Sdk Laravel Package

launchdarkly/server-sdk

LaunchDarkly server-side SDK for PHP lets you evaluate feature flags, target users, and manage segments directly in your PHP applications, with streaming updates, polling, and local caching for fast, reliable flag delivery.

View on GitHub
Deep Wiki
Context7

Getting Started

launchdarkly/server-sdk is LaunchDarkly's official PHP server-side SDK. The star count is low because most PHP teams only touch LD through the JS/browser SDK — the package itself is vendor-maintained and released on a regular cadence, so treat it as production-grade but documentation-light: expect to read src/ more than you would for a bigger ecosystem package.

Install and wire it up

composer require launchdarkly/server-sdk

Check the SDK's composer.json for the PHP floor (8.1+ on recent majors) and extensions — older majors need ext-curl + ext-json for the event publisher.

Server-side SDK keys are secrets. Keep them out of anything prefixed VITE_ / MIX_:

LAUNCHDARKLY_SDK_KEY=sdk-xxxxxxxx
// config/services.php
'launchdarkly' => [
    'sdk_key' => env('LAUNCHDARKLY_SDK_KEY'),
    'timeout' => env('LAUNCHDARKLY_TIMEOUT', 3.0),
],

Register it as a lazy singleton — do not construct the client in a provider's boot(), or every artisan invocation pays for a network handshake:

// app/Providers/AppServiceProvider.php
$this->app->singleton(\LaunchDarkly\LDClient::class, function () {
    $key = config('services.launchdarkly.sdk_key');

    // v5+: constructor takes a Config object.
    $config = new \LaunchDarkly\Config($key);
    $config->setLogger(Log::getLogger());          // any PSR-3 logger
    $config->setTimeout(config('services.launchdarkly.timeout'));
    return new \LaunchDarkly\LDClient($config);

    // v4 and earlier: new \LaunchDarkly\LDClient($key, ['logger' => ..., 'timeout' => ...]);
});

First use case — a server-side kill switch. Build the evaluation target once and reuse it:

$client = app(\LaunchDarkly\LDClient::class);

$user = (new \LaunchDarkly\LDUserBuilder((string) $request->user()->id))
    ->email($request->user()->email)
    ->custom(['plan' => $request->user()->plan])
    ->build();

if ($client->variation('new-checkout', $user, false)) {
    // new flow
}

Where to look first

  1. The README quickstart and the config table — it is the only complete list of options.
  2. src/LaunchDarkly/LDClient.php — variation(), variationDetail(), allFlagsState(), track(), flush(), close().
  3. src/LaunchDarkly/Config.php — every knob, including the logger, timeouts and data-source hooks.
  4. src/LaunchDarkly/Integrations/ — file-based data sources for tests/local dev.
  5. launchdarkly/hello-php — the smallest working example; useful for confirming your config before blaming Laravel.

Implementation Patterns

Wrap the client behind your own interface. This is the single highest-value pattern. LDClient is awkward to fake, so define App\Support\FeatureFlags with enabled(string $flag, ?Context $ctx = null, bool $default = false) and bind the LD-backed implementation in the container. In tests, bind a fake that reads from an array. Everything downstream becomes trivially testable and the LD API churn (see LDUser → Context) stays in one file.

Resolve the context once, in middleware. Build the context in a ResolveLaunchDarklyContext middleware from the authenticated user and bind it into the container. Controllers/jobs then ask for flags without rebuilding targeting attributes on every call — and, critically, the same context object is used for both server evaluation and any frontend bootstrap.

Bootstrap the frontend with allFlagsState() instead of evaluating twice:

$state = $client->allFlagsState($context, [
    'clientSideOnly' => true,
    'withReasons'   => false,
]);

return Inertia::render('Dashboard', [
    'flags' => $state->jsonSerialize(), // hand to the JS SDK as bootstrap data
]);

Pass this through Inertia shared props or a /api/flags endpoint, and initialise the browser SDK with it — no flicker, no duplicate evaluation, one round trip.

Blade ergonomics. Add a @feature('flag') ... @endfeature directive (Blade::if('feature', fn ($f) => app(FeatureFlags::class)->enabled($f))) so templates stop importing SDK classes.

Tracking is where the value is. $client->track('checkout-completed', $context, ['valueCents' => $order->total]) turns flags into measurable experiments. Pair it with app()->terminating(fn () => $client->flush()) in AppServiceProvider so events actually leave the process before PHP-FPM tears down the request.

Defaults encode the future. Always pass the post-cleanup value as the default (variation('new-checkout', $ctx, false) where false is the old behaviour only if you intend to roll back). When the flag is deleted, the code keeps working with no diff — that is the whole point of the exercise.

Queued jobs and Octane.

  • Queue workers: keep one long-lived client, flush() per job, and close() only via Queue::stopping().
  • Octane/Swoole/RoadRunner: the singleton survives between requests, which is exactly what you want — but flush() on RequestTerminated and never call close() mid-run.
  • Plain PHP-FPM: accept that you get a fresh client per request and lean on the Relay Proxy (below) rather than fighting it.

Local dev and CI. Point the SDK at a file data source (LaunchDarkly\Integrations\Files) with YAML fixtures, or run the launchdarkly/ld-relay Docker image and set the SDK host to it. Either way, tests must not depend on a live LD connection — evaluation is done locally from the downloaded payload, so a fixture-driven client behaves identically to production.

Gotchas and Tips

  • Streaming connections scale with workers, not with apps. Every PHP process that creates a client opens its own SSE connection to LaunchDarkly. PHP-FPM with 20 workers × 10 app servers is 200 connections; you can hit the environment's concurrent connection limit and get throttled. The vendor's documented answer for PHP is the Relay Proxy in daemon mode, which is also the fix for per-request handshake latency.
  • Cold starts can return defaults. In a short-lived request the stream payload may not have landed before your first evaluation, in which case you silently get the default. Never use the default to mean "fail open" for something security-relevant, and if you see intermittent wrong values in staging, this is the first thing to check.
  • Keep timeouts small. A blocking connect to LD on the request path is a latency bug waiting to happen. Set a 1–3s timeout and make sure your code path is usable with the default.
  • Guard console runs. php artisan migrate shouldn't create an LD client. Either rely on lazy resolution (preferred) or short-circuit when app()->runningInConsole().
  • flush() vs close(). flush() sends queued events and is safe to call repeatedly; close() shuts the client down permanently. In a worker, flushing per request and closing at shutdown is the correct pattern — closing after the first request leaves the rest of the worker's lifetime flagless.
  • Debug with variationDetail(), not logging. It returns the value, the variation index, isDefaultValue(), and a reason (RULE_MATCH, FALLTHROUGH, ERROR, PREREQUISITE_FAILED). When a flag "isn't working", the reason tells you whether it's targeting, a prerequisite, or a malformed context — usually in one shot.
  • Contexts replaced users. v5 moved from LDUser/LDUserBuilder to Context. The migration is mechanical but touches every call site, and the old builder is deprecated rather than removed. Pin the major version in composer.json and read the changelog before bumping.
  • Private attributes are a compliance requirement, not a nicety. Emails, names and anything else PII must be marked private (via the context builder's private-attribute mechanism or the config's private-attribute list) or they get shipped to LaunchDarkly in event payloads. Audit this before you go live in a regulated environment.
  • php artisan config:cache freezes env reads. The SDK key is read once into config, which is fine — but a key rotated in .env won't take effect until you re-cache. Also, an empty key usually means the client silently falls back to defaults rather than throwing, so add a boot-time assertion in production.
  • Unknown flag keys return the default silently. There's no exception. If a typo'd flag key means "feature off", you'll find out from users. Consider a staging check that asserts every flag key referenced in code exists in the LD project, or at least emit a log line when variationDetail() reports FLAG_NOT_FOUND.
  • Each environment needs its own key, and flag definitions don't propagate between them. Keep the key in config/services.php per environment and never share one across staging/production.
  • Metadata says NOASSERTION, the repo is Apache-2.0. If you have a licence-audit gate, verify against the repo's LICENSE file rather than the package metadata.
  • No official Laravel package exists. Community wrappers are thin and often target older SDK versions; you're better off with 30 lines of your own service provider plus a FeatureFlags interface. Expect to read the SDK source — with 39 stars there are very few blog posts to lean on, and most of them are out of date.
Weaver

How can I help you explore Laravel packages today?

Conversation history is not saved when not logged in.
Prompt
Add packages to context
No packages found.
novasa/laravel-language-center
opencage/geocode
adolfoholzer/parameters
livenetworks/ln-starter
shitoudev/permissionmanager
johannesclimacus/laravel-tui
hocvt/seotools
vitalybaev/sypexgeo
amphibee/themosis-ignition
gito-me/fast-excel
frankyso/laravel-ipaymu
geolize-studio/laravel-chile
boomerangui/chat
jinas/dhivatar
store-doc/laravel-storage-driver
plus-it/laravel-translation-manager
diephp/perhaps
diephp/telegraph
osmaviation/spreadsheet
microsoft/fluentui-system-icons