承接 zenmanage/zenmanage-php 相关项目开发

从需求分析到上线部署,全程专人跟进,保证项目质量与交付效率

邮箱:yvsm@zunyunkeji.com | QQ:316430983 | 微信:yvsm316

zenmanage/zenmanage-php

Composer 安装命令:

composer require zenmanage/zenmanage-php

包简介

Official PHP SDK for Zenmanage feature flags with local evaluation

README 文档

README

Build Status Codacy Badge

Add feature flags to your PHP 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

Installation

composer require zenmanage/zenmanage-php

Upgrading

See UPGRADING.md for details on breaking changes and migration steps.

Requirements: PHP 8.0+

Key Compatibility

  • Supported: case-sensitive server keys prefixed with srv_
  • Not supported in PHP SDK: client keys (cli_) and mobile keys (mob_) (initialization fails fast)

Get Started in 60 Seconds

  1. Get your server key (srv_...) from zenmanage.com
  2. Initialize the SDK:
use Zenmanage\Config\ConfigBuilder;
use Zenmanage\Zenmanage;

$zenmanage = new Zenmanage(
    ConfigBuilder::create()
    ->withEnvironmentToken('srv_your_server_key_here')
        ->build()
);
  1. Check a feature flag:
if ($zenmanage->flags()->single('new-dashboard')->isEnabled()) {
    // Show new dashboard
    return view('dashboard-v2');
}

// Show old dashboard
return view('dashboard');

That's it! 🎉

Common Use Cases

Roll Out a New Feature Gradually

// Check if user has access to beta features
$context = Context::single('user', $user->id, $user->name);

$betaAccess = $zenmanage->flags()
    ->withContext($context)
    ->single('beta-program')
    ->isEnabled();

if ($betaAccess) {
    // User is in beta program
    $features = $this->getBetaFeatures();
}

Note: Call withContext() on the flag manager to ensure context is sent to the API when loading rules.

A/B Testing

$context = Context::fromArray([
    'type' => 'user',
    'identifier' => $user->id,
    'name' => $user->name,
    'attributes' => [
        ['key' => 'country', 'values' => [['value' => $user->country]]],
    ],
]);

$variant = $zenmanage->flags()
    ->withContext($context)
    ->single('checkout-flow')
    ->asString();

if ($variant === 'one-page') {
    return view('checkout.onepage');
} else {
    return view('checkout.multipage');
}

Percentage Rollouts

Gradually roll out features to a percentage of your users. The SDK handles bucketing automatically using a deterministic CRC32B hash — no manual bucket logic needed.

use Zenmanage\Flags\Context\Context;

// Just provide a context with an identifier — the SDK does the rest
$context = Context::single('user', $user->id);

$flag = $zenmanage->flags()
    ->withContext($context)
    ->single('new-checkout-flow');

if ($flag->isEnabled()) {
    // This user is in the rollout percentage
    return view('checkout.new');
}

// This user is outside the rollout
return view('checkout.classic');

How it works:

  • Configure the rollout percentage (0–100%) and a unique salt in the Zenmanage dashboard
  • The SDK hashes salt:contextIdentifier to deterministically assign each user to a bucket (0–99)
  • Users whose bucket is below the percentage get the rollout value; others get the fallback
  • The same user always gets the same result (deterministic), and increasing the percentage never removes previously included users
  • Rollout rules can further refine targeting within the rollout group (e.g., only US users in the rollout)

Note: A context identifier is required for bucketing. Without one, the user always receives the fallback value.

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->flags()
    ->withContext($context)
    ->single('advanced-reports')
    ->isEnabled();

if ($advancedReports) {
    return $this->getAdvancedReports();
}

Configuration Values

// Get configuration values from flags
$apiTimeout = $zenmanage->flags()
    ->single('api-timeout', 5000)  // Default 5000ms
    ->asNumber();

$maxUploadSize = $zenmanage->flags()
    ->single('max-upload-mb', 10)
    ->asNumber();

$welcomeMessage = $zenmanage->flags()
    ->single('welcome-text', 'Welcome!')
    ->asString();

Kill Switch for Problem Features

// Quickly disable a problematic feature via dashboard
if ($zenmanage->flags()->single('new-payment-processor', false)->isEnabled()) {
    return $this->processWithNewSystem($payment);
} else {
    return $this->processWithLegacySystem($payment);
}

Setup for Your Application

Laravel Integration

Tip

There is an official Laravel integration on GitHub: zenmanage/zenmanage-laravel. Use it to plug Zenmanage directly into your Laravel app with minimal setup.

Create a service provider to make Zenmanage available throughout your app:

// app/Providers/ZenmanageServiceProvider.php
namespace App\Providers;

use Illuminate\Support\ServiceProvider;
use Zenmanage\Config\ConfigBuilder;
use Zenmanage\Zenmanage;

class ZenmanageServiceProvider extends ServiceProvider
{
    public function register()
    {
        $this->app->singleton(Zenmanage::class, function ($app) {
            return new Zenmanage(
                ConfigBuilder::create()
                    ->withEnvironmentToken(config('services.zenmanage.token'))
                    ->withCacheBackend('filesystem')
                    ->withCacheDirectory(storage_path('framework/cache/zenmanage'))
                    ->withCacheTtl(3600)
                    ->build()
            );
        });
    }
}
// config/services.php
return [
    // ...
    'zenmanage' => [
        'token' => env('ZENMANAGE_TOKEN'),
    ],
];

Then use dependency injection anywhere:

class DashboardController extends Controller
{
    public function __construct(private Zenmanage $zenmanage) {}

    public function index(Request $request)
    {
        $context = Context::single('user', $request->user()->email);
        
        $useNewDashboard = $this->zenmanage->flags()
            ->withContext($context)
            ->single('new-dashboard', false)
            ->isEnabled();

        return $useNewDashboard 
            ? view('dashboard-v2')
            : view('dashboard');
    }
}

Symfony Integration

# config/services.yaml
services:
    Zenmanage\Zenmanage:
        factory: ['App\Factory\ZenmanageFactory', 'create']
        arguments:
            $token: '%env(ZENMANAGE_TOKEN)%'
            $cacheDir: '%kernel.cache_dir%/zenmanage'
// src/Factory/ZenmanageFactory.php
namespace App\Factory;

use Zenmanage\Config\ConfigBuilder;
use Zenmanage\Zenmanage;

class ZenmanageFactory
{
    public static function create(string $token, string $cacheDir): Zenmanage
    {
        return new Zenmanage(
            ConfigBuilder::create()
                ->withEnvironmentToken($token)
                ->withCacheBackend('filesystem')
                ->withCacheDirectory($cacheDir)
                ->build()
        );
    }
}

Standalone PHP Application

// bootstrap.php or similar
$zenmanage = new Zenmanage(
    ConfigBuilder::create()
        ->withEnvironmentToken($_ENV['ZENMANAGE_TOKEN'])
        ->withCacheBackend('filesystem')
        ->withCacheDirectory(__DIR__ . '/cache/zenmanage')
        ->build()
);

// Make available globally (if needed)
$GLOBALS['zenmanage'] = $zenmanage;
// Or use a registry pattern, DI container, etc.

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->flags()
    ->withContext($context)
    ->single('premium-dashboard')
    ->isEnabled();

What you get:

  • type: Context type (user, organization, etc.)
  • identifier: Unique identifier for targeting
  • name: Human-readable display name
  • attributes: 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)

How Rule Evaluation Works

The SDK supports three types of rule selectors for targeting:

1. Segment Selector

Matches against a list of specific context identifiers:

If a rule value omits type (or sets it to null), only the identifier is compared.

// Rule: "Block these specific IPs"
// Selector: "segment", Values: [{"type": "user", "identifier": "140.248.31.37"}]

$context = Context::single('user', '140.248.31.37', 'Blocked User');
$result = $zenmanage->flags()
    ->withContext($context)
    ->single('allow-feature', true)
    ->isEnabled(); // Returns: false (matched segment)

2. Context Selector

Same as segment - matches against context type and identifier:

If a rule value omits type (or sets it to null), only the identifier is compared.

// Rule: "Enable for specific users"
// Selector: "context", Values: [{"type": "user", "identifier": "john-doe"}]

$context = Context::single('user', 'john-doe', 'John Doe');
$result = $zenmanage->flags()
    ->withContext($context)
    ->single('beta-feature', false)
    ->isEnabled(); // Returns: true (matched context)

3. Attribute Selector

Matches against additional context attributes (plan, country, role, etc.):

// Rule: "Enable for enterprise plans"
// Selector: "attribute", Subtype: "plan", Values: ["enterprise"]

$context = Context::fromArray([
    'type' => 'user',
    'identifier' => 'user-123',
    'name' => 'Jane Doe',
    'attributes' => [
        ['key' => 'plan', 'values' => [['value' => 'enterprise']]],
        ['key' => 'country', 'values' => [['value' => 'US']]],
    ],
]);

$result = $zenmanage->flags()
    ->withContext($context)
    ->single('premium-features', false)
    ->isEnabled(); // Returns: true (plan matched)

Supported operators for all selectors:

  • equal - Exact match (most common)
  • contains - Value contains the comparison string
  • starts_with - Value starts with the comparison string
  • ends_with - Value ends with the comparison string
  • regex - Value matches regex pattern
  • greater_than, less_than, in, etc. (see OperatorEvaluator for full list)

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->flags()
    ->single('new-checkout', true)
    ->isEnabled();

// Configuration value with fallback
$timeout = $zenmanage->flags()
    ->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->flags()->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:

  1. API Value - If flag exists in Zenmanage
  2. Inline Default - Value passed to single('flag', default)
  3. Collection Default - From DefaultsCollection
  4. Exception - If none of the above
$defaults = DefaultsCollection::fromArray(['timeout' => 3000]);

// Uses API value if exists, otherwise inline (5000), then collection (3000)
$timeout = $zenmanage->flags()
    ->withDefaults($defaults)
    ->single('timeout', 5000)
    ->asNumber();

Performance - Caching Rules

The SDK caches flag rules to minimize API calls. Rules are fetched once, then served from cache.

Default Setup (In-Memory)

Out of the box, flags are cached in memory for the request duration. Zero configuration needed:

// First call fetches from API
$newUI = $zenmanage->flags()->single('new-ui')->isEnabled();

// Subsequent calls use memory cache (same request)
$newUI2 = $zenmanage->flags()->single('new-ui')->isEnabled(); // Instant

Good for: Most web applications, simple scripts

File System Cache (Persist Between Requests)

Cache rules to disk for faster performance across multiple requests:

$config = ConfigBuilder::create()
    ->withEnvironmentToken('srv_your_server_key')
    ->withCacheBackend('filesystem')
    ->withCacheDirectory('/var/cache/zenmanage')
    ->withCacheTtl(300) // 5 minutes
    ->build();

Good for: High-traffic sites, long-running processes, CLI applications

When to use:

  • Production websites (cache between page loads)
  • Background jobs (avoid repeated API calls)
  • CLI tools (faster execution)

Cache Duration

Control how long rules are cached:

// 5 minutes (good for rapid development)
->withCacheTtl(300)

// 1 hour (good for production)
->withCacheTtl(3600)

// 1 day (good for stable flags)
->withCacheTtl(86400)

Recommendation: Start with 5-10 minutes in production. Increase once flags are stable.

Disable Cache (Testing Only)

// Always fetch fresh from API
$config = ConfigBuilder::create()
    ->withCacheBackend('null')
    ->build();

Manually Refresh Rules

Force a fresh fetch from the API:

$zenmanage->flags()->refreshRules();

Logging & Debugging

Get visibility into what the SDK is doing by providing a PSR-3 logger:

use Psr\Log\LoggerInterface;

$config = ConfigBuilder::create()
    ->withEnvironmentToken('srv_your_server_key')
    ->withLogger($yourLogger)
    ->build();

What gets logged:

  • API requests and responses
  • Cache hits and misses
  • Rule evaluation results
  • Errors and exceptions

Example with Monolog:

use Monolog\Logger;
use Monolog\Handler\StreamHandler;

$logger = new Logger('zenmanage');
$logger->pushHandler(new StreamHandler('path/to/zenmanage.log', Logger::DEBUG));

$config = ConfigBuilder::create()
    ->withLogger($logger)
    ->build();

Error Handling in Production

The SDK is designed for graceful degradation. Your app should never break because of feature flags.

Always Use Defaults for Critical Features

// Bad - will throw exception if flag doesn't exist
$showNewUI = $zenmanage->flags()->single('new-ui')->isEnabled();

// Good - falls back to false if anything goes wrong
$showNewUI = $zenmanage->flags()->single('new-ui', false)->isEnabled();

Handle API Failures

If the API is unreachable, the SDK will:

  1. Use cached rules (if available)
  2. Fall back to default values (if provided)
  3. Throw exception (if no defaults)

Recommended pattern:

try {
    $premiumEnabled = $zenmanage->flags()
        ->withContext($context)
        ->single('premium-features', false) // Default to false
        ->isEnabled();
        
    if ($premiumEnabled) {
        return $this->showPremiumDashboard();
    }
} catch (ZenmanageException $e) {
    // Log error but continue with default behavior
    $this->logger->warning('Flag check failed', ['error' => $e->getMessage()]);
    $premiumEnabled = false;
}

return $this->showStandardDashboard();

Retry Logic

The SDK automatically retries failed API calls (3 attempts with exponential backoff). You don't need to handle this.

Testing Your Feature Flags

Test your feature-flagged code without hitting the Zenmanage API.

Use Defaults in Tests

public function test_premium_users_see_dashboard()
{
    $zenmanage = new Zenmanage(
        ConfigBuilder::create()
            ->withEnvironmentToken('test-token')
            ->withCacheBackend('null') // No caching in tests
            ->build()
    );
    
    $defaults = DefaultsCollection::fromArray([
        'premium-dashboard' => true,
    ]);
    
    $enabled = $zenmanage->flags()
        ->withDefaults($defaults)
        ->single('premium-dashboard')
        ->isEnabled();
        
    $this->assertTrue($enabled);
}

Mock the Flag Manager

use PHPUnit\Framework\TestCase;
use Zenmanage\Flags\FlagManagerInterface;
use Zenmanage\Flags\Flag;

class CheckoutTest extends TestCase
{
    public function test_new_checkout_flow()
    {
        $flagManager = $this->createMock(FlagManagerInterface::class);
        $flagManager->method('single')
            ->willReturn(new Flag('new-checkout', 'New Checkout', true));
            
        $checkout = new CheckoutService($flagManager);
        
        $result = $checkout->processPayment($order);
        
        $this->assertTrue($result->usedNewFlow());
    }
}

Test Different Flag States

public function test_feature_disabled_shows_old_ui()
{
    $defaults = DefaultsCollection::fromArray(['new-ui' => false]);
    
    $flag = $this->zenmanage->flags()
        ->withDefaults($defaults)
        ->single('new-ui');
        
    $this->assertFalse($flag->isEnabled());
}

public function test_feature_enabled_shows_new_ui()
{
    $defaults = DefaultsCollection::fromArray(['new-ui' => true]);
    
    $flag = $this->zenmanage->flags()
        ->withDefaults($defaults)
        ->single('new-ui');
        
    $this->assertTrue($flag->isEnabled());
}

Requirements

  • PHP 8.0 or higher
  • Composer
  • Guzzle HTTP client (automatically installed)

Installation

composer require zenmanage/zenmanage-php

Development

Run tests:

composer test

Run static analysis:

composer phpstan

License

MIT

Support

统计信息

  • 总下载量: 2.88k
  • 月度下载量: 0
  • 日度下载量: 0
  • 收藏数: 0
  • 点击次数: 1
  • 依赖项目数: 1
  • 推荐数: 0

GitHub 信息

  • Stars: 0
  • Watchers: 1
  • Forks: 0
  • 开发语言: PHP

其他信息

  • 授权协议: MIT
  • 更新时间: 2025-02-14

承接程序开发

PHP开发

VUE

Vue开发

前端开发

小程序开发

公众号开发

系统定制

数据库设计

云部署

网站建设

安全加固