laravel / pennant

A simple, lightweight library for managing feature flags.
https://laravel.com/docs/pennant
MIT License
474 stars 48 forks source link

[1.x] Introduce before hook #111

Closed timacdonald closed 2 months ago

timacdonald commented 2 months ago

replaces https://github.com/laravel/pennant/pull/110

This PR introduces a "before" hook to class based features that allows you to intercept feature resolution before hitting the storage driver.

When a non-null value is returned from the before hook, it is used in place of retrieving the value from storage. Values returned from the before hook are never persisted to storage and are only ever used in-memory.

This allows you to:

  1. Start resolving / storing a feature after a given date.
  2. Schedule the release of a feature to everyone without changing the database values.
  3. Create a non-destructive temporary "kill switch" for a feature without having the purge the feature in the database. This is good if you want to turn a feature off and back on and maintain the existing resolved values for users in the database.

Example: Schedule the initial feature rollout after a given date

<?php

class MyFeature
{
    public function resolve($scope)
    {
        // After '2024-07-01 00:00:00', the feature will start resolving and
        // storing based on the lottery.
        return Lottery::odds(1, 100);
    }

    public function before($scope)
    {
        // Until '2024-07-01 00:00:00', everyone will not have the feature
        // available...
        if (Carbon::parse('2024-07-01 00:00:00')->isFuture()) {
            return false;
        }
    }
}

Example: Roll the feature out to everyone after a given date.

<?php

class MyFeature
{
    public function resolve($scope)
    {
        // Until '2024-07-01 00:00:00', the feature will be resolved and
        // storing based on the lottery.
        return Lottery::odds(1, 100);
    }

    public function before($scope)
    {
        // After '2024-07-01 00:00:00', everyone will have the feature activated.
        // The previously resolved values will persist in the database, allowing you
        // to revert to the previous set of users and resolved values if something
        // goes wrong after rollout.
        if (Carbon::parse('2024-07-01 00:00:00')->isPast()) {
            return true;
        }
    }
}

Example: provide a non-destructive killswitch

<?php

class MyFeature
{
    public function resolve($scope)
    {
        return Arr::random([
            'blue-sapphire',
            'seafoam-green',
            'tart-orange',
        ]);
    }

    public function before($scope)
    {
        // Changing the config value via an ENV flag allows you to quickly
        // disable a feature without changing the values stored in the
        // database.  When the feature is restored, the previously resolved
        // values will persist.
        if (Config::get('features.my-feature.disabled') === true) {
            return false;
        }
    }
}

Example: composing different usecases

<?php

class MyFeature
{
    public function resolve($scope)
    {
        return Arr::random([
            'blue-sapphire',
            'seafoam-green',
            'tart-orange',
        ]);
    }

    public function before($scope)
    {
        // Global kill switch...
        if (Config::get('features.my-feature.disabled') === true) {
            return false;
        }

         // Rollout to everyone after the given date...
         if (Carbon::parse('2024-07-01 00:00:00')->isPast()) {
            return true;
        }
    }
}
alexanderkroneis commented 2 months ago

This feels like a better and more laravely solution than #110. I love it.

alexanderkroneis commented 2 months ago

Just a quick hint: The link of this repository does not point to the current docs (it's linking to 10.x).

timacdonald commented 2 months ago

Updated that. Thanks for the heads up.