licensesos / php-sdk
PHP SDK for LicensesOS - License validation and activation for PHP applications
Requires
- php: ^8.1
- guzzlehttp/guzzle: ^7.0
Requires (Dev)
- mockery/mockery: ^1.6
- phpunit/phpunit: ^10.0
README
A PHP SDK for integrating with the LicensesOS license management API. Provides license validation, activation, and deactivation with built-in caching and WordPress integration.
Requirements
- PHP 8.1 or higher
- Guzzle HTTP client 7.0+
Installation
composer require licenseos/php-sdk
Quick Start
use LicensesOS\LicensesOsClient; $client = new LicensesOsClient('your_api_key'); // Validate a license $result = $client->validate('LIC-XXXX-XXXX-XXXX-XXXX', 'example.com'); if ($result->isValid()) { echo "License is valid!"; echo "Status: " . $result->status; echo "Expires: " . $result->expiresAt; }
Core Features
License Validation
Validate a license key for a specific domain or device:
$result = $client->validate( licenseKey: 'LIC-XXXX-XXXX-XXXX-XXXX', identifier: 'example.com', metadata: [ 'app_version' => '1.2.3', 'php_version' => PHP_VERSION, ] ); // Check validation status if ($result->isValid()) { // License is valid for this identifier } if ($result->isActive()) { // License status is "active" } if ($result->isExpired()) { // License has expired } if ($result->isRevoked()) { // License was revoked } // Access entitlements if ($result->hasEntitlement('plan')) { $plan = $result->getEntitlement('plan'); } // Get remaining activations $remaining = $result->getRemainingActivations();
License Activation
Activate a license for a domain or device:
$result = $client->activate( licenseKey: 'LIC-XXXX-XXXX-XXXX-XXXX', identifier: 'example.com' ); if ($result->isActivated()) { echo "Successfully activated!"; echo "Remaining activations: " . $result->getRemainingActivations(); } else { echo "Activation failed: " . $result->getErrorMessage(); // Error codes: ACTIVATION_LIMIT_REACHED, LICENSE_EXPIRED, etc. }
License Deactivation
Deactivate a license from a domain or device:
$result = $client->deactivate( licenseKey: 'LIC-XXXX-XXXX-XXXX-XXXX', identifier: 'example.com' ); if ($result->isDeactivated()) { echo "License deactivated. Remaining: " . $result->getRemainingActivations(); }
List Activations
Get all activations for a license:
$result = $client->listActivations('LIC-XXXX-XXXX-XXXX-XXXX'); echo "Total activations: " . $result->total; echo "Limit: " . $result->limit; echo "Remaining: " . $result->remaining; foreach ($result->getActiveActivations() as $activation) { echo $activation['identifier'] . " - " . $activation['activated_at']; } // Check if a specific identifier is activated if ($result->hasActivation('example.com')) { // This domain is activated }
Domain Normalization
The SDK automatically normalizes domain identifiers:
// All of these become "example.com" $client->validate($key, 'https://www.example.com/'); $client->validate($key, 'HTTP://EXAMPLE.COM:8080/path'); $client->validate($key, 'www.example.com'); $client->validate($key, 'example.com'); // Subdomain is preserved $client->validate($key, 'app.example.com'); // becomes "app.example.com" // IDN domains are converted to punycode $client->validate($key, 'münchen.de'); // becomes "xn--mnchen-3ya.de"
You can also normalize domains manually:
$normalized = LicensesOsClient::normalizeDomain('https://www.Example.COM/page'); // Returns: "example.com"
Caching
The SDK includes a caching layer for reduced API calls and offline tolerance.
Basic Caching
use LicensesOS\LicensesOsClient; use LicensesOS\Cache\LicenseCache; use LicensesOS\Cache\FileCache; $client = new LicensesOsClient('your_api_key'); $cache = new FileCache('/path/to/cache/dir'); $licenseCache = new LicenseCache($client, $cache); // Validate with caching (12-hour TTL) $result = $licenseCache->validate($licenseKey, $domain); // Force refresh from API $result = $licenseCache->validate($licenseKey, $domain, [], true); // Check if premium features should be allowed // (uses cache with 48-hour grace period for offline tolerance) if ($licenseCache->shouldAllowPremium($licenseKey, $domain)) { // Enable premium features }
Cache Configuration
$licenseCache = new LicenseCache( client: $client, cache: $cache, cacheTtl: 43200, // 12 hours (default) gracePeriod: 172800 // 48 hours (default) );
Available Cache Adapters
FileCache - Stores cache in JSON files:
use LicensesOS\Cache\FileCache; $cache = new FileCache('/var/cache/myapp', 'myapp');
WordPressCache - Uses WordPress transients:
use LicensesOS\Cache\WordPressCache; $cache = new WordPressCache('my_plugin');
Custom Cache - Implement CacheInterface:
use LicensesOS\Cache\CacheInterface; class RedisCache implements CacheInterface { public function get(string $key): mixed { /* ... */ } public function set(string $key, mixed $value, int $ttl): bool { /* ... */ } public function delete(string $key): bool { /* ... */ } }
WordPress Integration
The SDK includes ready-to-use WordPress integration classes.
Step 1: Create Your License Manager
use LicensesOS\WordPress\LicenseManager; class MyPluginLicense extends LicenseManager { private static ?self $instance = null; public static function getInstance(): self { if (self::$instance === null) { self::$instance = new self(); } return self::$instance; } protected function getApiKey(): string { return 'your_api_key_here'; } protected function getOptionPrefix(): string { return 'my_plugin'; } protected function getPluginVersion(): string { return MY_PLUGIN_VERSION; } }
Step 2: Create a Settings Page
use LicensesOS\WordPress\SettingsPage; class MyPluginSettings extends SettingsPage { protected function getLicenseManager(): LicenseManager { return MyPluginLicense::getInstance(); } protected function getPageTitle(): string { return 'My Plugin License'; } protected function getMenuTitle(): string { return 'License'; } protected function getMenuSlug(): string { return 'my-plugin-license'; } protected function getPurchaseUrl(): string { return 'https://yoursite.com/pricing'; } } // Register the settings page add_action('admin_menu', function() { $settings = new MyPluginSettings(); $settings->addMenuPage(); });
Step 3: Gate Premium Features
$license = MyPluginLicense::getInstance(); // Simple premium check if ($license->isPremium()) { // Enable all premium features } // Check specific entitlements if ($license->hasEntitlement('advanced_export')) { // Enable advanced export feature } // Get entitlement values $maxItems = $license->getEntitlement('max_items', 10);
Step 4: Background Validation (Optional)
Refresh license status periodically via cron:
// Register cron hook add_action('my_plugin_license_check', function() { $license = MyPluginLicense::getInstance(); if ($license->needsRefresh()) { $license->validate(true); } }); // Schedule daily check on activation register_activation_hook(__FILE__, function() { if (!wp_next_scheduled('my_plugin_license_check')) { wp_schedule_event(time(), 'daily', 'my_plugin_license_check'); } }); // Clear on deactivation register_deactivation_hook(__FILE__, function() { wp_clear_scheduled_hook('my_plugin_license_check'); });
Error Handling
The SDK throws two types of exceptions:
ApiException
Thrown when the API returns an error response:
use LicensesOS\Exceptions\ApiException; try { $result = $client->validate($licenseKey, $domain); } catch (ApiException $e) { echo "API Error: " . $e->getMessage(); echo "Error Code: " . $e->getErrorCode(); echo "HTTP Status: " . $e->getStatusCode(); }
Common error codes:
LICENSE_NOT_FOUND- License key doesn't existLICENSE_EXPIRED- License has expiredLICENSE_REVOKED- License was revokedACTIVATION_LIMIT_REACHED- No more activations availableRATE_LIMITED- Too many requestsAUTH_INVALID_API_KEY- Invalid API key
NetworkException
Thrown when unable to connect to the API:
use LicensesOS\Exceptions\NetworkException; try { $result = $client->validate($licenseKey, $domain); } catch (NetworkException $e) { // Network error - use cached data if available echo "Network Error: " . $e->getMessage(); }
Graceful Degradation Pattern
use LicensesOS\Exceptions\ApiException; use LicensesOS\Exceptions\NetworkException; function checkLicense(LicenseCache $cache, string $key, string $domain): bool { try { $result = $cache->validate($key, $domain); return $result->isValid(); } catch (NetworkException $e) { // Network error - fall back to grace period return $cache->shouldAllowPremium($key, $domain); } catch (ApiException $e) { // API error - license is invalid return false; } }
Best Practices
1. Cache API Responses
Always use the LicenseCache class to avoid excessive API calls:
// Good $cache = new LicenseCache($client, new FileCache('/tmp/cache')); $result = $cache->validate($key, $domain); // Bad - calls API every time $result = $client->validate($key, $domain);
2. Handle Network Errors Gracefully
Don't break your app when the API is unreachable:
// Good - uses grace period if ($licenseCache->shouldAllowPremium($key, $domain)) { enablePremiumFeatures(); } // Bad - might throw on network error $result = $client->validate($key, $domain); if ($result->isValid()) { enablePremiumFeatures(); }
3. Store License Key Securely
For WordPress, use options (they're in the database):
// Good update_option('my_plugin_license_key', $licenseKey); // Bad - don't store in plain files file_put_contents('license.txt', $licenseKey);
4. Validate on Reasonable Schedule
Don't validate on every page load:
// Good - validate if cache is stale if ($licenseCache->needsRefresh($key, $domain)) { $licenseCache->validate($key, $domain, [], true); } // Bad - validates every time $client->validate($key, $domain);
5. Soft Gate Features
Disable UI/features rather than breaking functionality:
// Good - soft gating function renderExportButton() { $license = MyPluginLicense::getInstance(); if ($license->isPremium()) { echo '<button>Export</button>'; } else { echo '<button disabled>Export (Pro)</button>'; echo '<a href="...">Upgrade to Pro</a>'; } } // Bad - hard gating that breaks UX function exportData() { if (!MyPluginLicense::getInstance()->isPremium()) { die('License required'); } }
API Reference
LicensesOsClient
| Method | Description |
|---|---|
validate(string $licenseKey, string $identifier, array $metadata = []) |
Validate a license |
activate(string $licenseKey, string $identifier, array $metadata = []) |
Activate a license |
deactivate(string $licenseKey, string $identifier) |
Deactivate a license |
listActivations(string $licenseKey) |
List all activations |
normalizeDomain(string $input) |
Normalize a domain (static) |
shouldAllowPremium(array $cachedState, int $graceTtl = 172800) |
Check cached state (static) |
LicenseCache
| Method | Description |
|---|---|
validate(...) |
Validate with caching |
shouldAllowPremium(string $licenseKey, ?string $identifier = null) |
Check premium status |
getCachedState(string $licenseKey, ?string $identifier = null) |
Get cached state |
clearCache(string $licenseKey, ?string $identifier = null) |
Clear cached data |
needsRefresh(string $licenseKey, ?string $identifier = null) |
Check if refresh needed |
WordPress\LicenseManager
| Method | Description |
|---|---|
getLicenseKey() |
Get stored license key |
setLicenseKey(string $key) |
Store license key |
clearLicenseKey() |
Clear license key and cache |
getStatus() |
Get cached status |
getLicenseData() |
Get cached license data |
isPremium() |
Check if premium features allowed |
hasEntitlement(string $key) |
Check for entitlement |
getEntitlement(string $key, mixed $default = null) |
Get entitlement value |
validate(bool $forceRefresh = false) |
Validate license |
activate(string $licenseKey) |
Activate license |
deactivate() |
Deactivate license |
needsRefresh() |
Check if refresh needed |
License
MIT