How I Use Laravel Pennant in Day-to-Day Coding
(list of links below)
I frequently push to production, often as much as 1–2 times per hour. Any code that I am working on, which has a passing test, is included in this push. This process has been streamlined by the superb Continuous Integration flow I’ve established with GitHub Actions (see this article for more information) and the setup of Envoy for all my projects (more details at Envoy.io).
What this means is that as I work on a new feature or change, it’s already on production even before it is officially “ready”. This approach eliminates the stress of release days when staging code is merged into production code. The process is as simple as updating a .env file and running the “php artisan pennant:purge” command.
This article will demonstrate a few simple ways I use feature flags, and how I leverage Pennant, to facilitate this workflow. I won’t delve into the reasons why I don’t use GitFlow, staging branches, or similar methods — these tips are applicable regardless of your CI/CD flow.
The example I will use may seem basic, but it is scalable. It’s rare that a feature or change requires a long-lived branch to manage it. The key is to think and code in small batches. Each push is a small, self-contained Lego-like class or method that confidently connects to the previous one. And, of course, testing is crucial.
The foundation of this approach is a robust continuous integration setup (a single GitHub Actions file is enough — it’s surprisingly simple!). It’s essential to ensure that your tests are passing, and to use tools like PHPStan to catch anything you might have missed.
In the case that I need to transform a model from ‘belongs to’ to ‘polymorphic’, and the UI needs to be updated to accommodate this change, here’s how I’d go about it:
First, I install Pennant following the instructions provided in the documentation. One of my deployment hooks in Envoy is set to “php artisan purge:pennant”.
Once Pennant is installed, since I use Inertia, I add it to my “HandleInertiaRequests.php”.
public function share(Request $request): array
{
return array_merge(parent::share($request), [
'featureFlags' => Feature::all(),
]);
You could add a cache here, but let’s keep it simple for now.
The first code segment I write is the migrations to add the polymorphic fields to the table. I also create a test to ensure the relationships work, and then I push. I ensure the migrations allow for “nullable” values. This enables me to gradually transition from the current method of handling relationships to a new method while preserving the old one for the time being.
Then, while the build is running, I start working on the Controllers. As each method passes its test, I push the code. The new feature is still invisible to users until I need to update the UI. Remember, the UI isn’t the only place where I can enable or disable the feature — this could also be an Event Listener, a Scheduled command, or any other elements that won’t be “live” until we are ready.
I can activate the feature for local testing following the Pennant documentation. https://laravel.com/docs/10.x/pennant#defining-features
<?php
namespace App\Providers;
use App\Models\User;
use Illuminate\Support\Lottery;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;
class AppServiceProvider extends ServiceProvider
{
/**
* Bootstrap any application services.
*/
public function boot(): void
{
Feature::define('new_resource_type', function(){
if (app()->environment(['local', 'testing'])) {
return true;
}
if (env('FEATURE_FLAG_NEW_RESOURCE')) {
return true;
}
return false;
});
}
}
As the documentation suggests, you can add some pretty cool functionality here. For instance, I can include an is_admin type check when I am ready to reveal the feature to the site owner.
Then, I hide the new button that actually triggers the Controller or Modal to start sending data to the Controller and models, like this:
<button
v-if="usePage().props.featureFlags.new_resource_type"
@click="showAddResourceModal">Add Resource</button>
Now I can continue to work on the UI, see it functioning locally, and push to the mainline branch as I go. No one sees it but me. This process might involve 5–10 pushes of small code batches to production. If any one of these pushes somehow breaks production, there’s a LOT less code for me to sift through to identify the issue.
When I am ready to reveal the feature to the owner or the site for quality assurance, I update the function like so:
public function boot(): void
{
Feature::define('new_resource_type', function(User $user){
if (app()->environment(['local', 'testing'])) {
return true;
}
if (env('FEATURE_FLAG_NEW_RESOURCE') || $user->is_admin) {
return true;
}
return false;
});
}
When the feature is complete, I simply remove the feature flag check in the Vue.js file and in the code above. However, I might want to create a package that tracks how often the feature is used and by whom, so the owner can decide whether it’s worth keeping.
This article merely skims the surface of what you can do with the powerful Pennant library and the feature flag pattern. I hope you find these tips useful in your own development processes.
## Links
GitHub actions to get you going https://alfrednutile.info/posts/256
Envoy.io https://envoyer.io
Pennant https://laravel.com/docs/10.x/pennant
Feature Flags https://alfrednutile.info/posts/84 (old)