Be part of JetBrains PHPverse 2026 on June 9 – a free online event bringing PHP devs worldwide together.

secondman's avatar

How to set up a feature flag in a service provider?

We're writing a new module that will take a good deal of time to finish, so we installed Pennant in order to manage this as a flagged feature until it's actually ready to deploy.

So I created a FeatureFlag that just resolves to false.

Then wrapped our routes in the boot method of the service provider like so:

public function boot(): void
{
  if (Feature::active(ModuleFlag::class)) {
    Route::group([], function () {
      $this->loadRoutesFrom(dirname(__DIR__).'/../routes/routes.php');
    });

    $this->loadJsonTranslationsFrom(dirname(__DIR__).'/..lang');
  }
}

But as you can imagine, this creates a QueryException since the app isn't even booted yet so there's no way for the feature to contact the db.

What's the proper way to handle a Pennant feature flag in a service provider?

0 likes
11 replies
LaryAI's avatar
Level 58

To handle a feature flag in a service provider without causing a QueryException due to the database not being available yet, you can use a different approach to check the feature flag status. One common method is to use a configuration or environment variable to control the feature flag during the bootstrapping phase.

Here's a step-by-step solution:

  1. Define an environment variable for the feature flag: Add an entry in your .env file to control the feature flag.

    FEATURE_MODULE_FLAG=false
    
  2. Update the service provider to check the environment variable: Modify your service provider to check the environment variable instead of querying the database directly.

    use Illuminate\Support\Facades\Route;
    
    public function boot(): void
    {
        if (env('FEATURE_MODULE_FLAG', false)) {
            Route::group([], function () {
                $this->loadRoutesFrom(dirname(__DIR__).'/../routes/routes.php');
            });
    
            $this->loadJsonTranslationsFrom(dirname(__DIR__).'/../lang');
        }
    }
    
  3. Use the environment variable to control the feature flag: This way, you can control the feature flag without causing a QueryException during the bootstrapping phase.

  4. Optional - Use a configuration file: If you prefer to manage feature flags in a configuration file, you can create a configuration file (e.g., config/features.php) and define your feature flags there.

    // config/features.php
    return [
        'module_flag' => env('FEATURE_MODULE_FLAG', false),
    ];
    

    Then, update your service provider to use the configuration value:

    use Illuminate\Support\Facades\Route;
    use Illuminate\Support\Facades\Config;
    
    public function boot(): void
    {
        if (Config::get('features.module_flag', false)) {
            Route::group([], function () {
                $this->loadRoutesFrom(dirname(__DIR__).'/../routes/routes.php');
            });
    
            $this->loadJsonTranslationsFrom(dirname(__DIR__).'/../lang');
        }
    }
    

By using an environment variable or a configuration file, you can safely control the feature flag during the bootstrapping phase without causing database-related exceptions. This approach ensures that your application can boot up correctly even if the database is not available yet.

secondman's avatar

@LaryAI

So Lary's suggestion is to not use Pennant at all ... LOL.

Geez

martinbean's avatar
Level 80

@secondman I just use the feature flag on the routes themselves:

Route::middleware('features:foo_module')->group(function () {
    // Routes only registered if feature is active...
});
1 like
secondman's avatar

@martinbean

Yeah I ended up making a middleware in the module, pushing it to the stack in the provider then using it on the routes just like you did here.

Appreciate ya!

1 like
MohamedTammam's avatar

Make sure the module service provider runs after AppServiceProvider.

secondman's avatar

@MohamedTammam

Modules function like composer packages, so the provider is loaded in the composer.json

Thanks though.

MohamedTammam's avatar

@secondman Yeah, but you can not adding it into composer discovery and add it manually to providers array.

Or am I missing something?

rodrigo.pedra's avatar

@secondman

For this use case, use @martinbean suggestion (and mark as best answer), as it will work properly with cached routes.

But, in case you need something similar in the future, consider deferring DB operations to a container hook, such as afterResolving:

<?php

namespace App\Providers;

use Illuminate\Routing\Router;
use Illuminate\Support\ServiceProvider;
use Laravel\Pennant\Feature;

class AppServiceProvider extends ServiceProvider
{
    public function register(): void
    {
        //
    }

    public function boot(): void
    {
        Feature::define('foo', fn () => true);

        $this->app->afterResolving(Router::class, function (Router $router) {
            if (Feature::active('foo')) {
                $router->get('/dummy');
            }
        });
    }
}
1 like
secondman's avatar

@rodrigo.pedra

Hey Rodrigo, been a minute since I've seen you on here.

That afterResolving take is a good one, that's pretty much what I was looking for, but I did go with @martinbean

Slick!

1 like
rodrigo.pedra's avatar

@secondman I've been a bit busy lately =)

Martin's solution is the way to go indeed, as it won't mess with route caching, and actually, it is the documented way to do so.

But I wanted to spread the word on the afterResolving hook, which can be useful on another cases.

Have a nice day =)

Please or to participate in this conversation.