dive-be/laravel-feature-flags

This package is abandoned and no longer maintained. No replacement package was suggested.

Handle feature flags within your Laravel app

1.1 2022-02-11 16:15 UTC

This package is auto-updated.

Last update: 2023-02-04 14:42:36 UTC


README

Please use much better alternatives:

What problem does this package solve?

"A feature flag is a software development process used to enable or disable functionality remotely without deploying code. New features can be deployed without making them visible to users. Feature flags help decouple deployment from release letting you manage the full lifecycle of a feature." (Source)

Installation

You can install the package via composer:

composer require dive-be/laravel-feature-flags

Once composer has finished, you must publish the configuration and migration:

php artisan feature:install

This is the contents of the published config file:

return [

    /**
     * The name of the cache key that will be used to cache your app's features.
     */
    'cache_key' => 'feature_flags',

    /**
     * The feature model that will be used to retrieve your app's features.
     */
    'feature_model' => Dive\FeatureFlags\Models\Feature::class,
];

If you don't need multi-locale support, you are now good to go.

Multiple languages support

This package provides first-class support for multiple languages using Spatie's excellent Laravel translatable package.

composer require spatie/laravel-translatable

Next, go to the configuration file and change feature_model to:

Dive\FeatureFlags\Models\TranslatableFeature::class

Finally, find the migration and uncomment the comment while also deleting everything in front of it. It should read:

// ...
$table->json('message')->nullable();
$table->timestamp('disabled_at')->nullable();
// ...

Usage

For a full list of what's available to you, please refer to the Feature contract for an exhaustive list.

Setting up your app's initial features

Seeding the (initial) features can be done in a regular Laravel seeder.

php artisan make:seeder FeaturesTableSeeder

Here is an example of what it might look like:

use Dive\FeatureFlags\Models\Feature;
use Illuminate\Database\Seeder;
use Illuminate\Support\Facades\DB;

class FeaturesTableSeeder extends Seeder
{
    public function run()
    {
        DB::table('features')->upsert([
            [
                'description' => 'Registrations that come through our partnerships',
                'label' => 'Public registrations',
                'message' => 'The registration period has ended. Thanks for participating in our programme.',
                'name' => 'registrations',
                'scope' => Feature::getDefaultScope(),
            ],
            [
                'description' => 'Display the App version on the homepage',
                'label' => 'Application version',
                'message' => 'Version is hidden',
                'name' => 'version',
                'scope' => Feature::getDefaultScope(),
            ],
        ], ['scope', 'name'], ['description', 'label', 'message']);
    }
}

Resolving the manager

This package provides every possible way to resolve a Feature instance out of the IoC container. We've got you covered!

Facade

use Dive\FeatureFlags\Facades\Feature;

Feature::find('dashboard');

or using the alias (particularly helpful in Blade views)

use Feature;

Feature::disabled('dashboard');

Helpers

feature('dashboard');
feature_disabled('dashboard');
feature_enabled('dashboard');
feature_verify('dashboard');

Dependency injection

use Dive\FeatureFlags\Contracts\Feature;

public function index(Feature $feature)
{
    $feature->verify('dashboard');

    return view('layouts.dashboard');
}

Service Location

app('feature')->find('dashboard');

Scoping

The package allows you to define a custom scope along with the feature (not to be confused with Laravel's global scopes). Particularly useful when you need to use the same name for different parts of your application. Most of the package's functions/methods accept an additional $scope argument.

Refer to the Feature contract for an exhaustive list.

Changing the default wildcard scope

If you wish to use a different scope rather than the * sign the package uses by default when creating and checking/verifying features, you may change this in your AppServiceProvider's boot method:

use Dive\FeatureFlags\Models\Feature;

class AppServiceProvider extends ServiceProvider
{
    public function boot()
    {
        Feature::setDefaultScope('my-wonderful-app');
    }
}

Seeding

Refer to the seeding section above on how to scope your features.

Blade directives 🗡

@disabled

You can use the @disabled directive to conditionally display content in your views depending on a feature's current state. Using this directive first will make the feature's message property automatically available as a scoped variable inside the block. Examples:

@disabled('registrations')
    <div class="alert warning">
        {{ $message }} <!-- Automatically injected -->
    </div>
@enabled
    <div class="alert info">
        Welcome to our public registrations.
    </div>
@enddisabled

@enabled

You can also use the @enabled directive to do the same thing as above. However, the $message variable will not be available inside the @disabled block when using this directive first.

@enabled('registrations')
    <div class="alert info">text</div>
@disabled
    <div>$message is not available here</div>
@endenabled

@can(not)

This package also registers itself at Laravel's Gate. The visitor does not have to be auhenticated in order to use it:

@can('feature', 'dashboard')
    <a href="/dashboard" target="_blank">View Dashboard</a>
@else
    <small>Dashboard is currently disabled</small>
@endcan

Guarding parts of your application 💂🏼

There are multiple ways to prevent users from accessing disabled parts of your application.

A FeatureDisabledException will be thrown in all cases if the feature is not enabled.

Route middleware

Route::get('registrations', [RegistrationsController::class, 'index'])->middleware('feature:registrations');

Manual checking using 'verify'

Controller example

Assume your typical controller:

class RegistrationsController extends Controller
{
    public function __construct(Feature $feature)
    {
        $feature->verify('registrations');
    }
    
    public function index()
    {
        return view('registrations.index');
    }
}

Livewire example

This is especially useful in contexts where you cannot really use route middleware, such as Livewire actions:

use Dive\FeatureFlags\Facades\Feature;
use Dive\Wishlist\Facades\Wishlist;

class HeartButton extends Component
{
    public function add($id)
    {
        Feature::verify('wishlist');
        
        Wishlist::add($id);
    }

    public function render()
    {
        return view('livewire.heart-button');
    }
}

PS: Be sure to check out our Wishlist package as well 😉

Access Gate

This package also registers itself at Laravel's Gate providing you the ability to check a feature's state through them. This means that you can do things like the following:

Route::get(...)->middleware('can:feature,dashboard');
Gate::authorize('feature', 'dashboard');

However, there is one key difference. Laravel's Gate will throw an AccessDeniedHttpException, while the package's own checks will throw a FeatureDisabledException (which extends the former exception class). So, if you need to know the exception's type, you are highly adviced not to use gates.

Artisan commands 🧑‍🎨

Toggling a feature on/off

Since the features are managed through a Feature Eloquent model, you can definitely use solutions such as Laravel Nova to do this.

However, when developing locally, you might want to easily turn a feature on/off. You can do this using the command below:

php artisan feature:toggle {name} {scope?}

Displaying list of all features

Use the command below to display a table of all features and their corresponding state:

php artisan feature:list

Available options: compact, disabled, enabled, scope.

Clearing the cache

The features are cached forever to speed up subsequent checks. If you don't use Eloquent to update or alter records, you must reset the cache manually:

php artisan feature:clear

Note: when you create/update features through Eloquent, the cache busting is done for you automatically.

Testing

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 oss@dive.be instead of using the issue tracker.

Credits

License

The MIT License (MIT). Please see License File for more information.