Laravel Pennant Feature Flag package (Tutorial Part 1: Basics)
Have you faced a scenario when you made some releases on production only to find you later that it needed some more changes?
You can watch this video tutorial to see the implementation of this post.
Sometimes we want changes to go slowly. Sometimes for a very small set of users, sometimes we are just testing new changes and want users randomly to test them.
Even let's say you made some release and now your users are shouting that this is not intended behaviour can we quickly turn that feature off somehow?
In a small system, when it is not constantly you can just make another release but as you acquire more and more users this remains not an option.
The new addition to the laravel core package solves all these problems with the newly added package Pennant, of course since it is not a small function but a whole package there is a lot to cover.
Let's try to understand the basics in the first part of this tutorial series.
The Setup:
I have created a public repository with all setup here if you want to try things out:
https://github.com/thekrkv/amazon
Branch: Part1
let's start:
First, let's add the Pennant package to our project:
composer require laravel/pennant
after this let's publish pennant configuration and migration files by running this command:
php artisan vendor:publish --provider="Laravel\Pennant\PennantServiceProvider"
Now change your database credentials in your .env file and if you have already done that let's proceed to the next step.
Run migrate command:
php artisan migrate
Now, these steps can also be found on the official docs of laravel Pennant so if you want to read about the package first I will advise you to go threw them first so you have some understanding of the package basics. Docs are Here
First, we need different types of users ( just for the scenario I am assuming where we have 3 types of users internal, business and normal.
Update your user's migration (at folder: database/migrations/) up() method like this
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->string('customer')->nullable();
$table->rememberToken();
$table->timestamps();
});
}
Now edit your database seeder file like this:
public function run(): void
{
User::factory(1)->create([
'email' => 'test@amazon.test',
'customer' => 'internal',
]);
User::factory(1)->create([
'email' => 'user@amazon.test',
'customer' => 'normal',
]);
User::factory(1)->create([
'email' => 'business@amazon.test',
'customer' => 'business',
]);
}
This will create 3 users with 3 different customers type, by default user factory has a password hash added for “password” so with test@amazon.test and password as “password” you will be able to login into your application.
Till here we were doing some basic setups in our project if you have an application already and you want to apply some pennant logic to it, you can totally ignore some parts of it like editing migration and seed files.
So now we have our migration setup, seed setup and pennant library ready to use in our application.
The first step is to have a condition or function or a scenario based on which you want your feature to be enabled.
For example, this can be like:
- A function in your model like if the user type is business you want some features not to be enabled to them by default
- A condition based on the environment like if this app is in the testing environment you want some feature to be enabled
- A feature based on payment or user type like if a user signed up after today you want them to be using v2 of your API with the latest feature
These are some examples you can have any example based on your requirement on how you want to roll out your feature to your customers.
IMPORTANT NOTE: DO NOT ASSUME THIS PACKAGE TO ACT LIKE SOME ACCESS MANAGEMENT PACKAGE THIS BEHAVES VERY DIFFERENTLY AND SHOULD ONLY BE USED TO RELEASE NEW FEATURES
Let's add one condition in our User model to check if the user type is business.
open your User model and add this function
public function isBusiness(){
return $this->customer == 'business';
}
this function will return true if the customer type is business else will return false. we will use this function in our feature registration.
Now open your AppServiceProvider file which is in app/providers directory and write this feature definition. your AppServiceProvider file should look like this:
<?php
namespace App\Providers;
use App\Models\User;
use Illuminate\Support\Lottery;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Feature::define('new-dashboard', fn (User $user) => match (true) {
$user->isBusiness() => true,
default => Lottery::odds(1/100),
});
}
}
Notice here that I have used the classes at the top before calling their functions, many times when we start we forget to do that and our code stops working so make sure to use these classes like this at the top.
use App\Models\User;
use Illuminate\Support\Lottery;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;
The “default => Lottary::odds(1 / 100)” is here to say that I will also return true for 1 out of 100 customers who are not business. What this means is in our 1 in 100 users will get an old dashboard even if they are not business, you can play with this by changing it, or you can just commit this fn part if all you want to do is have a lottery in all your users to decide who will have the feature, In such case, you can write your feature definition like this
Feature::define('new-dashboard', Lottery::odds(1, 1000));
Now that we have defined our feature we can use it in our application logic wherever we want. I am going to show one below more we will talk about in future posts when we will explore this package further.
Let's say that I want our business users to not have a new dashboard design since they have not been informed yet and we will do it only after we are sure that there is no bug on our new dashboard and our free users can be a ginny pig for testing our new feature. We can do that like this:
<?php
use App\Http\Controllers\ProfileController;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use \Laravel\Pennant;
Route::get('/', function () {
if(Feature::active('new-dashboard')){
return Inertia::render('Welcome', [
'canLogin' => Route::has('login'),
'canRegister' => Route::has('register'),
'laravelVersion' => Application::VERSION,
'phpVersion' => PHP_VERSION,
]);
}else{
return Inertia::render('NewWelcome', [
'canLogin' => Route::has('login'),
'canRegister' => Route::has('register'),
'laravelVersion' => Application::VERSION,
'phpVersion' => PHP_VERSION,
]);
}
});
Here inside if condition we are checking if the Feature new-dashboard is active which will check what the condition returns when registering in the AppServiceProvider class if it returns true we will return true and we will serve them the old Welcome page else we will return them NewWelcome page.
This can also be written like this
<?php
use App\Http\Controllers\ProfileController;
use Illuminate\Foundation\Application;
use Illuminate\Support\Facades\Route;
use Inertia\Inertia;
use \Laravel\Pennant;
Route::get('/', function () {
if(Feature::active('new-dashboard')){
return // whatever you want to return to business users
}else{
return // whatever you want to return to other users
}
});
This covers the basics of the Pennant package for managing releases, there is a lot to explore into it which we will talk about in the upcoming part of this series.
Feel free to follow me on various platforms to get updates:
Twitter: @kumartheravi
Linkedin: KumarRaviSingh
Github: KumarRaviSingh