rollout/rox
rollout/rox is a Laravel package for managing feature rollouts and toggles. It helps you enable or disable features for specific users, segments, or rollout percentages, keeping release control in configuration and code.
Install and initialize once per process.
composer require rollout/rox
Look first at the repo README.md and src/Server/Rox.php — that is the public facade. The three classes you will spend 90% of your time with are Rox, RoxOptions, and the flag classes (RoxFlag, RoxInt, RoxString, RoxDouble).
Minimal boot:
use Rox\Server\Rox;
use Rox\Server\RoxOptions;
$options = new RoxOptions();
// storage + fetch interval + log level go here
Rox::setup('YOUR_APP_KEY', $options);
Rox::setUserContext(['id' => $user->id, 'email' => $user->email]);
$flag = Rox::getFlag('new-checkout');
if ($flag->isEnabled()) {
// new path
}
Do this in your framework bootstrap (Laravel AppServiceProvider::boot, Symfony bundle boot, or a front-controller bootstrap.php) before any controller or middleware runs. Your first real use case should be a single percentage-rollout boolean flag wrapped in an if, not an experiment — get the plumbing proven before you trust it.
Notes: the package name is rollout/rox while the repo is rollout/rox-php. Composer metadata, license file, and PHP version constraints should be checked in composer.json before you commit; the license is marked NOASSERTION in the repo metadata, so confirm legal acceptability explicitly.
Bootstrap singleton per request. PHP-FPM has no long-lived process, so Rox::setup() runs on every request. Keep it in one place and guard against double-initialization if your framework can boot twice (queues, tests, Octane/Swoole workers need special handling — see gotchas).
Storage adapter for cross-request persistence. The default in-memory storage loses the fetched configuration between requests, so every request re-fetches or falls back to defaults. Implement the package's storage interface backed by Redis, database, or your cache layer:
final class RedisRoxStorage implements RoxStorageInterface
{
public function get(string $key) { return $this->redis->get("rox:$key"); }
public function set(string $key, $value) { $this->redis->set("rox:$key", $value); }
}
$options->setStorage(new RedisRoxStorage($redis));
Context in middleware. Set the user context in a global middleware after the auth guard resolves the user, and consider Rox::freeze() right after to snapshot evaluations for the remainder of the request so a flag cannot flip mid-render.
public function handle($request, Closure $next)
{
Rox::setUserContext(['id' => (string) $request->user()->id]);
Rox::freeze();
return $next($request);
}
Remember to unfreeze (Rox unfreeze/reset equivalent) if you share a process across users (Octane, Swoole, queue workers).
Typed flags, not casts. Ask for the type you need and always pass a default at the call site so the code still behaves when Rox is unreachable:
$limit = Rox::getInt('page-size', 25)->getValue();
$title = Rox::getString('hero-title', 'Welcome')->getValue();
Container / service provider. Wrap the facade in your own FeatureFlags interface with isEnabled(string $key, bool $default): bool methods. This keeps Roc calls out of controllers, makes flags mockable in tests, and — critically for this package — lets you swap the SDK later without touching business code.
Testing. Register a test container / memory container returning deterministic roxy values, or subclass your wrapper with an in-memory map. Never hit the Rox backend in CI; assert both the "on" and "off" branches and default-value behavior.
Experiments and dynamic config. Use flag values as variant selectors, log exposure yourself (the SDK will not do product analytics for you), and pull dynamic configuration through the same wrapper so a backend outage degrades to hard-coded defaults.
Rox::getFlag() before Rox::setup() will fail or silently return defaults; there is no lazy init. Establish init in bootstrap and assert it in a smoke test.freeze() is per-process, not per-request in any shared-runtime environment. If you run Octane/Swoole or long-lived queue workers, freeze/unfreeze around the unit of work or you will leak one user's flags to the next.RoxOptions and log at a level you can actually see when debugging. Turn on verbose logging temporarily to dump the fetched roxy/configuration.RoxOptions; too aggressive hammers the backend, too lazy delays kill-switch propagation. For an incident kill switch, know your worst-case delay and have an env-var override path independent of Rox.NOASSERTION; last release January 2024, zero stars, zero dependents. Treat this as a low-maintenance dependency: pin the version, vendor the source if necessary, and do not build load-bearing architecture on undocumented behavior.How can I help you explore Laravel packages today?