zenmanage / zenmanage-laravel
Zenmanage API SDK for Laravel
Installs: 1 017
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 1
Forks: 0
Open Issues: 0
pkg:composer/zenmanage/zenmanage-laravel
Requires
- php: >=8.1
- zenmanage/zenmanage-php: ^3.1.0
Requires (Dev)
- friendsofphp/php-cs-fixer: ^3.0
- phpstan/phpstan: ^1.10
- phpunit/php-code-coverage: ^10.0
- phpunit/phpunit: ^10.0
README
Add feature flags to your Laravel application in minutes. Control feature rollouts, A/B test, and manage configurations without deploying code.
Why Zenmanage?
- ๐ Fast: Rules cached locally - ~1ms evaluation time
- ๐ฏ Targeted: Roll out features to specific users, organizations, or segments
- ๐ก๏ธ Safe: Graceful fallbacks and error handling built-in
- ๐ Insightful: Automatic usage tracking (optional)
- ๐งช Testable: Easy to mock in tests
- ๐ง Laravel Native: Service provider, facade, and artisan commands included
Installation
composer require zenmanage/zenmanage-laravel
Requirements: Laravel 11+, PHP 8.1+
The service provider will be auto-discovered. If you need to manually publish the config:
php artisan vendor:publish --provider="Zenmanage\Laravel\ZenmanageServiceProvider"
Get Started in 60 Seconds
- Get your environment token from zenmanage.com
- Set your token in
.env:
ZENMANAGE_TOKEN=tok_your_token_here
- Check a feature flag:
use Zenmanage\Laravel\Facades\Zenmanage; if (Zenmanage::single('new-dashboard')->isEnabled()) { return view('dashboard-v2'); } return view('dashboard');
That's it! ๐
Configuration
The only required configuration is the Environment Token. Configuration values are set in config/zenmanage.php:
environment_token- Your Zenmanage environment token (required)cache_ttl- Cache duration in seconds (default: 3600)cache_backend- Cache strategy: 'memory' or 'filesystem' (default: 'memory')cache_directory- Directory for filesystem cache (optional)enable_usage_reporting- Enable automatic usage tracking (default: false)api_endpoint- API endpoint URL (default: https://api.zenmanage.com)
ZENMANAGE_TOKEN=tok_sample ZENMANAGE_CACHE_BACKEND=filesystem ZENMANAGE_CACHE_TTL=3600 ZENMANAGE_USAGE_REPORTING=false ZENMANAGE_API_ENDPOINT=https://api.zenmanage.com
Common Use Cases
Roll Out a Feature Gradually
use Zenmanage\Flags\Context\Context; use Zenmanage\Laravel\Facades\Zenmanage; // Check if user has access to beta features $context = Context::single('user', $user->id, $user->name); $betaAccess = Zenmanage::withContext($context) ->single('beta-program') ->isEnabled(); if ($betaAccess) { // User is in beta program $features = $this->getBetaFeatures(); }
A/B Testing
$context = Context::fromArray([ 'type' => 'user', 'identifier' => $user->id, 'name' => $user->name, 'attributes' => [ ['key' => 'country', 'values' => [['value' => $user->country]]], ], ]); $variant = Zenmanage::withContext($context) ->single('checkout-flow') ->asString(); if ($variant === 'one-page') { return view('checkout.onepage'); } return view('checkout.multipage');
Feature Toggles by Organization
$context = Context::fromArray([ 'type' => 'organization', 'identifier' => $user->organization->id, 'name' => $user->organization->name, 'attributes' => [ ['key' => 'plan', 'values' => [['value' => $user->organization->plan]]], ], ]); $advancedReports = Zenmanage::withContext($context) ->single('advanced-reports') ->isEnabled(); if ($advancedReports) { return $this->getAdvancedReports(); }
Configuration Values
// Get configuration values from flags $apiTimeout = Zenmanage::single('api-timeout', 5000)->asNumber(); $maxUploadSize = Zenmanage::single('max-upload-mb', 10)->asNumber(); $welcomeMessage = Zenmanage::single('welcome-text', 'Welcome!')->asString();
Kill Switch for Problem Features
// Quickly disable a problematic feature via dashboard if (Zenmanage::single('new-payment-processor', false)->isEnabled()) { return $this->processWithNewSystem($payment); } return $this->processWithLegacySystem($payment);
Working with Contexts
Contexts let you target flags to specific users, organizations, or any custom attributes. This is how you do gradual rollouts, A/B tests, and targeted features.
Simple Context (One Attribute)
use Zenmanage\Flags\Context\Context; // Target by user ID with name $context = Context::single('user', $user->id, $user->name); // Target by organization $context = Context::single('organization', $company->id, $company->name); // Target by user with just ID $context = Context::single('user', $user->id);
Rich Context (Multiple Attributes)
$context = Context::fromArray([ 'type' => 'user', 'identifier' => $user->id, 'name' => $user->name, 'attributes' => [ ['key' => 'organization', 'values' => [['value' => $user->company->slug]]], ['key' => 'plan', 'values' => [['value' => $user->subscription->plan]]], ['key' => 'role', 'values' => [['value' => $user->role]]], ['key' => 'country', 'values' => [['value' => $user->country]]], ], ]); $premiumFeatures = Zenmanage::withContext($context) ->single('premium-dashboard') ->isEnabled();
What you get:
type: Context type (user, organization, etc.)identifier: Unique identifier for targetingname: Human-readable display nameattributes: Array of additional attributes for advanced targeting (plan, role, country, etc.)
When to use contexts:
- Rolling out to specific users (beta testers)
- Organization-based features (enterprise vs. free)
- Regional features (different countries)
- Role-based access (admins, moderators)
- Plan-based features (pro vs. basic)
Safe Defaults - Never Break Your App
Always provide defaults for critical features. The SDK will use them if:
- Flag doesn't exist yet
- API is unreachable
- Network issues occur
Inline Defaults (Recommended)
// If 'new-checkout' doesn't exist, returns true $enabled = Zenmanage::single('new-checkout', true)->isEnabled(); // Configuration value with fallback $timeout = Zenmanage::single('api-timeout', 5000)->asNumber();
Default Collections (For Multiple Flags)
use Zenmanage\Flags\DefaultsCollection; $defaults = DefaultsCollection::fromArray([ 'new-ui' => true, 'max-upload-size' => 10, 'welcome-message' => 'Welcome to our app!', 'feature-x' => false, ]); $flags = Zenmanage::withDefaults($defaults); // All these will use defaults if flags don't exist $newUI = $flags->single('new-ui')->isEnabled(); $maxSize = $flags->single('max-upload-size')->asNumber(); $message = $flags->single('welcome-message')->asString();
Priority Order
When retrieving a flag, the SDK checks in this order:
- API Value - If flag exists in Zenmanage
- Inline Default - Value passed to
single('flag', default) - Collection Default - From
DefaultsCollection - Exception - If none of the above
$defaults = DefaultsCollection::fromArray(['timeout' => 3000]); // Uses API value if exists, otherwise inline (5000), then collection (3000) $timeout = Zenmanage::withDefaults($defaults) ->single('timeout', 5000) ->asNumber();
Retrieving Feature Flags
Using the Facade (Recommended)
use Zenmanage\Laravel\Facades\Zenmanage; // Get all flags $flags = Zenmanage::all(); // Get a single flag $flag = Zenmanage::single('flag-key'); // With default $flag = Zenmanage::single('flag-key', false);
Using Dependency Injection
use Zenmanage\Laravel\Contracts\Client; class DashboardController extends Controller { public function __construct(private Client $zenmanage) {} public function index() { if ($this->zenmanage->single('new-dashboard')->isEnabled()) { return view('dashboard-v2'); } return view('dashboard'); } }
Getting Flag Values
All Flags
$results = Zenmanage::all(); foreach ($results as $flag) { $key = $flag->getKey(); $type = $flag->getType(); // Get value based on type if ($flag->getType() === 'boolean') { $value = $flag->asBool(); } elseif ($flag->getType() === 'number') { $value = $flag->asNumber(); } else { $value = $flag->asString(); } }
Single Flag
$flag = Zenmanage::single('flag-key'); // Boolean check if ($flag->isEnabled()) { // Feature is enabled } // Get typed values $boolValue = $flag->asBool(); $stringValue = $flag->asString(); $numberValue = $flag->asNumber();
Reporting Feature Flag Usage
When your application uses a feature flag, it can notify Zenmanage of the usage. This helps Zenmanage determine which flags are active and which may have been abandoned. Note that single() automatically reports usage, so you typically don't need to call this manually.
use Zenmanage\Flags\Context\Context; use Zenmanage\Laravel\Facades\Zenmanage; // Report usage (optional - single() does this automatically) Zenmanage::reportUsage('flag-key'); // Report with context $context = Context::single('user', 'user-123'); Zenmanage::reportUsage('flag-key', $context);
Refreshing Rules
You can manually refresh the flag rules from the API:
use Zenmanage\Laravel\Facades\Zenmanage; Zenmanage::refreshRules();
Testing
Mock the Zenmanage facade in your tests:
use Zenmanage\Flags\Flag; use Zenmanage\Laravel\Facades\Zenmanage; public function test_new_feature_for_beta_users() { Zenmanage::shouldReceive('single') ->with('beta-feature') ->andReturn(new Flag(...)); // Your test code }
Or use ::fake() to disable actual API calls:
Zenmanage::fake(); // Now calls to Zenmanage won't hit the real API
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/zenmanage/zenmanage-laravel. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the Contributor Covenant code of conduct.
License
The library is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the Zenmanage's code bases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.
What is Zenmanage?
Zenmanage allows you to control which features and settings are enabled in your application giving you better flexibility to deploy code and release features.
Zenmanage was started in 2024 as an alternative to highly complex feature flag tools. Learn more about us.