Home Arrow Icon Knowledge base Arrow Icon Global Arrow Icon What are some examples of custom scopes in Laravel Pennant


What are some examples of custom scopes in Laravel Pennant


Laravel Pennant allows defining custom scopes to determine how feature flags are checked against different entities beyond the default user scope. Here are some detailed examples and explanations of custom scopes in Laravel Pennant:

Custom Scopes in Laravel Pennant

1. Using a Different Model as Scope (e.g., Team instead of User)

By default, Pennant checks features against the currently authenticated user. However, you can specify a different scope, such as a Team model, to handle feature flags for teams rather than individual users.

Example of defining a feature for a Team scope:

php
use App\Models\Team;
use Carbon\Carbon;
use Illuminate\Support\Lottery;
use Laravel\Pennant\Feature;

Feature::define('billing-v2', function (Team $team) {
    if ($team->created_at->isAfter(new Carbon('1st Jan, 2023'))) {
        return true;
    }

    if ($team->created_at->isAfter(new Carbon('1st Jan, 2019'))) {
        return Lottery::odds(1 / 100);
    }

    return Lottery::odds(1 / 1000);
});

To check this feature for a user's team, you pass the team instance to the `for` method:

php
if (Feature::for($user->team)->active('billing-v2')) {
    return redirect('/billing/v2');
}

This approach allows rolling out features at the team level instead of the user level[1].

2. Setting a Default Scope Globally

Instead of passing the scope explicitly every time with `Feature::for()`, you can set a default scope globally, such as the authenticated user's team. This is typically done in a service provider:

php
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;

class AppServiceProvider extends ServiceProvider
{
    public function boot(): void
    {
        Feature::resolveScopeUsing(fn ($driver) => Auth::user()?->team);
    }
}

After this, calling `Feature::active('billing-v2')` will automatically check the feature against the authenticated user's team, making the code cleaner and consistent[1].

3. Using Null as a Global Scope

If you want features to apply globally (to everyone), you can define your feature to accept `null` as the scope. Then, you can set the default scope resolver to return `null` so that features are checked globally without needing a specific user or team.

Defining a global feature:

php
Feature::define('new-feature', fn (null $scope) => false);

Setting the default scope to `null` in a service provider:

php
Feature::resolveScopeUsing(fn ($driver) => null);

Checking the feature globally:

php
Feature::for(null)->active('new-feature');

This approach is useful for small projects or features that should be toggled on/off for all users at once[4].

4. Class-Based Feature Definitions with Custom Scope

Laravel Pennant supports class-based features where you define a class with a `resolve` method that receives the scope (usually a User or any other model) to determine the feature's active state.

Example:

php
namespace App\Features;

use App\Models\User;
use Illuminate\Support\Lottery;

class NewApi
{
    public function resolve(User $user): mixed
    {
        return match (true) {
            $user->isInternalTeamMember() => true,
            $user->isHighTrafficCustomer() => false,
            default => Lottery::odds(1 / 100),
        };
    }
}

You can then check this feature by passing the user or another scope object:

php
use Illuminate\Support\Facades\Feature;

$instance = Feature::instance(NewApi::class);

You can also customize the stored feature name by adding a `$name` property to the class[1].

5. Implementing Custom Scope Identifier Serialization

If you use third-party Pennant drivers that do not know how to store Eloquent models as scope identifiers, you can implement the `FeatureScopeable` contract on your scope model to customize how the scope is serialized for different drivers.

Example:

php
namespace App\Models;

use FlagRocket\FlagRocketUser;
use Illuminate\Database\Eloquent\Model;
use Laravel\Pennant\Contracts\FeatureScopeable;

class User extends Model implements FeatureScopeable
{
    public function toFeatureIdentifier(string $driver): mixed
    {
        return match($driver) {
            'database' => $this,
            'flag-rocket' => FlagRocketUser::fromId($this->flag_rocket_id),
        };
    }
}

This allows Pennant to properly store and retrieve feature flags for your custom scopes across different drivers[1][9].

These examples show how Laravel Pennant's flexibility with scopes allows you to tailor feature flag checks to different levels of your application, such as users, teams, or globally, and how to customize the storage and resolution of these scopes.

Citations:
[1] https://laravel.com/docs/12.x/pennant
[2] https://laracasts.com/episodes/2685
[3] https://hackernoon.com/how-to-manage-feature-flags-with-laravel-pennant
[4] https://elliotderhay.com/blog/how-to-make-laravel-pennant-features-that-apply-globally
[5] https://laravel-news.com/feature-flags-with-laravel-pennant
[6] https://www.honeybadger.io/blog/a-guide-to-feature-flags-in-laravel/
[7] https://laravel.com/docs/11.x/eloquent
[8] https://laraveldaily.com/lesson/laravel-pennant/scopes-user-team-example
[9] https://laravel.com/docs/11.x/pennant