moonspot / phlag-client
Client library for reading feature flags from the Phlag application
Installs: 0
Dependents: 0
Suggesters: 0
Security: 0
Stars: 0
Watchers: 0
Forks: 0
Open Issues: 0
pkg:composer/moonspot/phlag-client
Requires
- php: ^8.2
- guzzlehttp/guzzle: ^7.0
- moonspot/value-objects: ^1.0 || ^2.0
Requires (Dev)
This package is auto-updated.
Last update: 2025-11-23 00:44:50 UTC
README
PHP client library for the Phlag feature flag management system
This library provides a simple, type-safe interface for querying feature flags from a Phlag server. It handles authentication, environment management, and error handling so you can focus on feature rollouts.
Features
- 🎯 Type-safe flag retrieval - Get boolean, integer, float, or string values
- 🌐 Environment-aware - Configure once, query a specific environment
- 🔄 Immutable environment switching - Easy multi-environment queries
- ⚡ Simple API - Clean, fluent interface with convenience methods
- 🛡️ Robust error handling - Specific exceptions for different error conditions
- ✅ Fully tested - Comprehensive test coverage with PHPUnit
Requirements
- PHP 8.2 or higher
- Composer
- A running Phlag server instance
Installation
Install via Composer:
composer require moonspot/phlag-client
Quick Start
use Moonspot\PhlagClient\PhlagClient; // Create a client for a specific environment $client = new PhlagClient( base_url: 'http://localhost:8000', api_key: 'your-64-character-api-key', environment: 'production' ); // Check if a feature is enabled if ($client->isEnabled('feature_checkout')) { // Show the new checkout flow } // Get typed configuration values $max_items = $client->getFlag('max_items'); // returns int or null $price_multiplier = $client->getFlag('price_multiplier'); // returns float or null $welcome_message = $client->getFlag('welcome_message'); // returns string or null // For high-traffic apps, enable caching $cached_client = new PhlagClient( base_url: 'http://localhost:8000', api_key: 'your-api-key', environment: 'production', cache: true // Fetches all flags once, caches for 5 minutes );
Usage
Creating a Client
The PhlagClient constructor requires three parameters:
$client = new PhlagClient( base_url: 'http://phlag.example.com', // Your Phlag server URL api_key: 'your-api-key-here', // 64-char API key from Phlag admin environment: 'production' // Environment to query );
Subdirectory Installation
If your Phlag server is installed in a subdirectory, include the full path in the base URL:
$client = new PhlagClient( base_url: 'https://www.example.com/phlag', // Note: includes /phlag subdirectory api_key: 'your-api-key', environment: 'production' ); // Client will correctly request: // https://www.example.com/phlag/flag/production/feature_name
The client automatically handles trailing slashes, so both https://www.example.com/phlag and https://www.example.com/phlag/ work correctly.
Checking Feature Flags
The isEnabled() method is perfect for boolean feature toggles:
if ($client->isEnabled('feature_new_dashboard')) { // Feature is active }
Heads-up: isEnabled() returns true only for actual boolean true values. Non-existent flags, inactive flags, and non-boolean values all return false.
Getting Flag Values
Use getFlag() to retrieve any flag type:
// SWITCH flags return bool $enabled = $client->getFlag('feature_checkout'); // true or false // INTEGER flags return int or null $max_items = $client->getFlag('max_items'); // 100 or null // FLOAT flags return float or null $multiplier = $client->getFlag('price_multiplier'); // 1.5 or null // STRING flags return string or null $message = $client->getFlag('welcome_message'); // "Hello!" or null
When flags return null:
- The flag doesn't exist
- The flag isn't configured for the environment
- The flag is outside its temporal constraints (start/end dates)
Note: SWITCH flags return false when inactive, not null.
Working with Multiple Environments
You can switch environments without creating new client instances:
$prod_client = new PhlagClient( base_url: 'http://phlag.example.com', api_key: 'your-api-key', environment: 'production' ); // Create a new client for staging (immutable pattern) $staging_client = $prod_client->withEnvironment('staging'); // Original client unchanged echo $prod_client->getEnvironment(); // "production" echo $staging_client->getEnvironment(); // "staging" // Query both environments $prod_enabled = $prod_client->isEnabled('feature_beta'); $staging_enabled = $staging_client->isEnabled('feature_beta');
Performance & Caching
For high-traffic applications, enable file-based caching to dramatically reduce API calls:
$client = new PhlagClient( base_url: 'http://localhost:8000', api_key: 'your-api-key', environment: 'production', cache: true, // Enable caching cache_file: null, // Auto-generate filename (optional) cache_ttl: 300 // Cache for 5 minutes (optional) ); // First call fetches all flags from API (1 request) $enabled = $client->isEnabled('feature_checkout'); // Subsequent calls use cached data (0 requests) $max = $client->getFlag('max_items'); $price = $client->getFlag('price_multiplier');
How Caching Works
When caching is enabled:
- First request: Client fetches ALL flags for the environment via
/all-flagsendpoint - Cache storage: Flags stored in memory AND persisted to disk
- Subsequent requests: Served from in-memory cache (no API calls)
- Cache expiration: After TTL expires, next request refreshes from API
- Cross-request persistence: Cache file survives between PHP requests
Cache File Location
By default, cache files are stored in the system temp directory with auto-generated names:
// Auto-generated filename format sys_get_temp_dir() . '/phlag_cache_{hash}.json' // Hash is MD5 of base_url + environment // Example: /tmp/phlag_cache_a1b2c3d4e5f6.json
Custom cache file:
$client = new PhlagClient( base_url: 'http://localhost:8000', api_key: 'your-api-key', environment: 'production', cache: true, cache_file: '/var/cache/app/phlag_prod.json' );
Cache Management
Warming the cache (preload before first request):
$client->warmCache(); // Immediately fetches and caches all flags
Clearing the cache (force fresh fetch):
$client->clearCache(); // Removes cache file and in-memory data // Next request will fetch fresh from API $value = $client->getFlag('feature');
Checking cache status:
if ($client->isCacheEnabled()) { echo "Cache file: " . $client->getCacheFile() . "\n"; echo "TTL: " . $client->getCacheTtl() . " seconds\n"; }
When to Use Caching
✅ Good use cases:
- High-traffic applications with frequent flag checks
- Flags that change infrequently (hourly, daily)
- Reducing API load and network latency
- Improving response times (sub-millisecond flag checks)
❌ When to avoid caching:
- You need real-time flag updates (seconds matter)
- Flags change very frequently
- Low-traffic applications (caching overhead not worth it)
- Single flag check per request
Performance Impact
Without caching:
- API calls: N (one per
getFlag()call) - Network overhead: ~10-50ms per call
- Total overhead: N × 10-50ms
With caching:
- API calls: 1 per TTL period (default 5 minutes)
- First request: ~10-50ms (fetch all flags)
- Subsequent requests: <1ms (memory lookup)
- Cache file I/O: ~1-2ms on first load per PHP request
Example savings (100 flag checks per request, 1000 requests/minute):
- Without cache: 100,000 API calls/minute
- With cache (300s TTL): ~20 API calls/minute (99.98% reduction)
Error Handling
The client throws specific exceptions for different error conditions:
use Moonspot\PhlagClient\Exception\AuthenticationException; use Moonspot\PhlagClient\Exception\InvalidEnvironmentException; use Moonspot\PhlagClient\Exception\InvalidFlagException; use Moonspot\PhlagClient\Exception\NetworkException; use Moonspot\PhlagClient\Exception\PhlagException; try { $value = $client->getFlag('my_flag'); } catch (AuthenticationException $e) { // Invalid API key (401) error_log('Bad API key: ' . $e->getMessage()); } catch (InvalidFlagException $e) { // Flag doesn't exist (404) error_log('Flag not found: ' . $e->getMessage()); } catch (InvalidEnvironmentException $e) { // Environment doesn't exist (404) error_log('Environment not found: ' . $e->getMessage()); } catch (NetworkException $e) { // Connection failed, timeout, etc. error_log('Network error: ' . $e->getMessage()); } catch (PhlagException $e) { // Other errors (500, etc.) error_log('Phlag error: ' . $e->getMessage()); }
All exceptions extend PhlagException, so you can catch them all with a single block:
try { $value = $client->getFlag('my_flag'); } catch (PhlagException $e) { // Handle any Phlag error error_log('Error fetching flag: ' . $e->getMessage()); $value = null; // Use a safe default }
API Reference
PhlagClient
__construct(string $base_url, string $api_key, string $environment, bool $cache = false, ?string $cache_file = null, int $cache_ttl = 300)
Creates a new client instance.
Parameters:
$base_url- Base URL of your Phlag server (e.g.,http://localhost:8000)$api_key- 64-character API key from the Phlag admin panel$environment- Environment name (e.g.,production,staging,development)$cache- Enable file-based caching (default:false)$cache_file- Custom cache file path (default: auto-generated in system temp directory)$cache_ttl- Cache time-to-live in seconds (default:300)
getFlag(string $name): mixed
Retrieves a flag value. When caching is enabled, serves from cache after first request.
Parameters:
$name- Flag name
Returns: The flag value (bool, int, float, string, or null)
Throws:
AuthenticationException- Invalid API keyInvalidFlagException- Flag doesn't exist (cache disabled only)InvalidEnvironmentException- Environment doesn't existNetworkException- Network communication failedPhlagException- Other errors
isEnabled(string $name): bool
Convenience method for checking SWITCH flags.
Parameters:
$name- Flag name
Returns: true if the flag value is boolean true, false otherwise
Throws: Same as getFlag()
getEnvironment(): string
Gets the current environment name.
Returns: The environment name
withEnvironment(string $environment): self
Creates a new client for a different environment. Cache settings are preserved but a new cache file is generated.
Parameters:
$environment- New environment name
Returns: New PhlagClient instance (immutable pattern)
warmCache(): void
Preloads the flag cache immediately. Useful for warming cache during application startup.
Heads-up: No-op if caching is disabled.
Throws: Same as getFlag() for API errors
clearCache(): void
Clears in-memory and file cache, forcing fresh fetch on next request.
Heads-up: No-op if caching is disabled.
isCacheEnabled(): bool
Checks if caching is enabled.
Returns: true if caching is enabled
getCacheFile(): string
Gets the cache file path (even if file doesn't exist yet).
Returns: Absolute path to the cache file
getCacheTtl(): int
Gets the cache time-to-live in seconds.
Returns: Cache TTL in seconds
Development
Running Tests
# Install dependencies composer install # Run all tests composer test # Run just unit tests composer unit # Run just linting composer lint # Fix code style composer fix
Project Structure
phlag-client/
├── src/
│ ├── Client.php # HTTP client wrapper
│ ├── PhlagClient.php # Main public API
│ └── Exception/ # Exception hierarchy
├── tests/
│ ├── ClientTest.php # Client tests
│ └── PhlagClientTest.php # PhlagClient tests
└── composer.json
Troubleshooting
404 Errors with Subdirectory Installation
If you're getting 404 errors and your Phlag server is installed in a subdirectory:
Problem: Base URL doesn't include the subdirectory path
Solution: Make sure your base URL includes the full path to Phlag
// Wrong - missing subdirectory $client = new PhlagClient('https://www.example.com', 'key', 'prod'); // Correct - includes /phlag subdirectory $client = new PhlagClient('https://www.example.com/phlag', 'key', 'prod');
Stale Cache Data
If you're seeing outdated flag values when caching is enabled:
Problem: Cache hasn't expired yet
Solutions:
- Use shorter TTL:
new PhlagClient($url, $key, $env, cache: true, cache_ttl: 60) - Manually clear:
$client->clearCache() - Disable caching if you need real-time updates
Cache File Permission Errors
If cache files aren't being created:
Problem: No write permission to temp directory or custom cache path
Solution: Ensure PHP has write access to sys_get_temp_dir() or your custom cache directory
// Check permissions $cache_file = $client->getCacheFile(); $cache_dir = dirname($cache_file); if (!is_writable($cache_dir)) { echo "No write permission to: $cache_dir\n"; }
Heads-up: Cache write failures are logged but don't throw exceptions. The client gracefully degrades to non-cached operation.
Connection Timeouts
The default timeout is 10 seconds. If you're experiencing timeouts with a slow network or heavily loaded server, you may need to adjust Guzzle's timeout configuration by extending the Client class.
Invalid API Key Errors
Make sure you're using the full 64-character API key exactly as shown in the Phlag admin panel. Keys are case-sensitive and must be copied completely.
Contributing
Contributions welcome! This project follows strict coding standards:
- PSR-1 and PSR-12 compliance
- 1TBS brace style
- snake_case for variables/properties
- camelCase for methods
- Type declarations on all methods
- Protected visibility (not private) unless truly encapsulated
- PHPDoc blocks in conversational style
Run composer fix before committing to ensure code style compliance.
License
BSD 3-Clause License - see LICENSE file for details.
Credits
Built by Brian Moon (brian@moonspot.net)
Dependencies:
- Guzzle HTTP Client - HTTP communication
Support
For bugs and feature requests, please use the GitHub issue tracker.
For questions, contact brian@moonspot.net.