laravel-pennant-unleash maintained by henzeb
Unleash driver for Laravel Pennant
Laravel Pennant is a lightweight feature flag package, but its built-in drivers store state locally. Unleash is a feature management platform that evaluates flags server-side with rich targeting strategies.
This package bridges the two: it registers an unleash Pennant driver so you can use the full Pennant API while
Unleash handles all flag evaluation.
Table of contents
Installation
composer require henzeb/laravel-pennant-unleash
Publish the config file:
php artisan vendor:publish --tag=laravel-pennant-unleash
To use Unleash with Pennant, you must add an unleash entry in the stores
section of config/pennant.php, equivalent to adding this yourself:
'stores' => [
'unleash' => [
'driver' => 'unleash',
],
// Pennant's built-in stores, e.g.:
'array' => [
'driver' => 'array',
],
],
In your .env, add the following. Use your own credentials, of course.
UNLEASH_URL=https://your-unleash-instance/api
UNLEASH_API_KEY=your-client-api-key
UNLEASH_INSTANCE_ID=your-instance-id
UNLEASH_APP_NAME=your-app-name # defaults to APP_NAME
UNLEASH_CACHE_DRIVER=array # defaults to CACHE_DRIVER
Custom client builder
If you have specific needs for the client builder, you can use Feature::buildUnleashClientUsing() to customize the
UnleashBuilder.
use Unleash\Client\UnleashBuilder;
Feature::buildUnleashClientUsing(function (UnleashBuilder $builder): UnleashBuilder {
return $builder->withStrategy(new MyCustomStrategy());
});
Or return a completely new UnleashBuilder instance to bypass the package's own configuration:
use Unleash\Client\UnleashBuilder;
Feature::buildUnleashClientUsing(function (UnleashBuilder $builder): UnleashBuilder {
return UnleashBuilder::create()
->withAppUrl(config('unleash.app_url'))
->withInstanceId(config('unleash.instance_id'))
->withAppName(config('unleash.app_name'));
});
Context
The scope passed to Pennant is turned into an UnleashContext, so it can be matched by strategy constraints in
the Unleash admin UI.
Authenticated users
Pass a user (or anything implementing Illuminate\Contracts\Auth\Authenticatable) and the driver sends the auth
identifier as the Unleash currentUserId. In Unleash, target these with a userId constraint.
Feature::for($user)->active('my-feature');
Eloquent models
Pass an Eloquent model and the driver sends its class (or morph map alias, if configured) and key as custom context properties. In Unleash, target these with class and id constraints.
Feature::for($tenant)->active('my-feature');
For example, $tenant = App\Models\Tenant::find(42) sends:
[
'class' => 'App\Models\Tenant', // or the morph map alias, e.g. 'tenant'
'id' => '42',
]
so an Unleash strategy constraint on class with value App\Models\Tenant (or your morph map alias) combined
with an id constraint on 42 will match this scope.
Plain strings
Pass a plain string and the driver sends it as a custom context property. In Unleash, target these with a scope constraint.
Feature::for('some-identifier')->active('my-feature');
For example, Feature::for('some-identifier') sends:
[
'scope' => 'some-identifier',
]
so an Unleash strategy constraint on scope with value some-identifier will match this scope.
UnleashContext
Henzeb\Pennant\Unleash\Configuration\UnleashContext is a Pennant-aware context object you can construct and pass
as the scope directly, to set any Unleash context field (userId, sessionId, ipAddress, environment,
currentTime, custom properties) yourself.
use Henzeb\Pennant\Unleash\Configuration\UnleashContext;
$context = new UnleashContext(
currentUserId: '42',
ipAddress: '1.2.3.4',
sessionId: 'abc123',
customContext: ['region' => 'eu-west'],
);
Feature::for($context)->active('my-feature');
It also has a static make factory and supports Laravel's Conditionable trait:
$context = UnleashContext::make(currentUserId: '42')
->when($request->has('region'), fn($ctx) => $ctx->setCustomProperty('region', $request->region));
FeatureScopeable
Any object can become scopable by implementing Laravel\Pennant\Contracts\FeatureScopeable. Pennant calls
toFeatureIdentifier before passing the scope to the driver, so the driver receives whatever you return from it.
Return an UnleashContext (or any Unleash\Client\Configuration\Context) to have it used for flag evaluation.
This is the right approach when you have domain objects — such as a Tenant or Team — that you want to pass
directly as a scope without registering a global context resolver.
use Henzeb\Pennant\Unleash\Configuration\UnleashContext;
use Laravel\Pennant\Contracts\FeatureScopeable;
use Unleash\Client\Configuration\Context;
class Tenant implements FeatureScopeable
{
public function toFeatureIdentifier(string $driver): mixed
{
return match ($driver) {
'unleash' => UnleashContext::make(customContext: ['tenantId' => (string) $this->id]),
default => $this->id,
};
}
}
Feature::for($tenant)->active('my-feature');
Custom context resolver
If you need to map an arbitrary scope to an Unleash context, register a resolver in a service provider:
use Henzeb\Pennant\Unleash\Configuration\UnleashContext;
use Unleash\Client\Configuration\Context;
Feature::resolveUnleashContextUsing(function (mixed $scope): ?Context {
if ($scope instanceof Tenant) {
return UnleashContext::make(customContext: ['tenantId' => $scope->id]);
}
return null;
});
Returning null sends no context to Unleash.
Variants
Feature::value('my-feature') resolves the Unleash variant
for the given feature and scope:
- If the feature (or the matched variant) is disabled, it returns
false. - If the variant has no payload, it returns the variant's name.
- If the variant has a
stringorcsvpayload, it returns the raw payload value as a string. - If the variant has a
jsonpayload, it returns the decoded value (array).
Feature::value('my-feature');
// 'my-variant' — variant with no payload
// 'hello' — variant with a string/csv payload
// ['foo' => 'bar'] — variant with a json payload
Testing this package
composer test
Changelog
Please see CHANGELOG for more information on what has changed recently.
Contributing
Please see CONTRIBUTING for details.
Security
If you discover any security related issues, please email henzeberkheij@gmail.com instead of using the issue tracker.
Credits
License
The MIT License. Please see License File for more information.