DEV Community

Cover image for Preventing Destructive Commands from Running in Laravel
Ash Allen
Ash Allen

Posted on • Originally published at ashallendesign.co.uk

Preventing Destructive Commands from Running in Laravel

Introduction

I recently stumbled upon a cool Laravel feature I hadn't come across before: the ability to prevent destructive commands from running in production.

So, for those of you like me who haven't heard of this feature before, I thought I'd write a quick article to share it with you. I think it's something you'll really like! I'll be adding to all my Laravel projects from now on.

Preventing Destructive Commands from Running in Laravel

In PR #51376 to Laravel in May 2024, Jason McCreary and Joel Clermont added the Illuminate\Console\Prohibitable trait to the framework, which was then included in the Laravel 11.9 release.

Adding this trait to an Artisan command provides a prohibit method that you can use to determine whether the command should be prevented from running.

Example Use Case

I can already think of a perfect use case for this. When I first started as a junior developer, I typed the command php artisan migrate:fresh --seed in my terminal, thinking I'd be refreshing my local database. Little did I know that my terminal was still connected to the production server. Thankfully, I realised my mistake before hitting ENTER, and I would have only wiped my own website's database, which had no users at the time. But it could have been a disaster - especially if it had been a client's site or app!

P.S. - Don't worry, I've learned my lesson since then! My terminal etiquette has improved significantly.

If I had hit ENTER, Laravel would have asked me to confirm that I wanted to run the command. But it wouldn't have stopped me from running it. So, if I had been in a rush and had not read the confirmation message correctly, I could have still wiped the production database.

That's where the Illuminate\Console\Prohibitable trait comes in. You can use it to prevent the command from running, even if you try to force it.

Prohibiting Laravel's Destructive Database Commands

Laravel ships with the following commands that have the Illuminate\Console\Prohibitable trait applied:

  • migrate:fresh - Illuminate\Database\Console\Migrations\FreshCommand
  • migrate:refresh - Illuminate\Database\Console\Migrations\RefreshCommand
  • migrate:reset - Illuminate\Database\Console\Migrations\ResetCommand
  • migrate:rollback - Illuminate\Database\Console\Migrations\RollbackCommand
  • migrate:wipe - Illuminate\Database\Console\WipeCommand

Let's take a look at how we could prevent these commands from running in a production environment:

declare(strict_types=1);

namespace App\Providers;

use Illuminate\Database\Console\Migrations\FreshCommand;
use Illuminate\Database\Console\Migrations\RefreshCommand;
use Illuminate\Database\Console\Migrations\ResetCommand;
use Illuminate\Database\Console\Migrations\RollbackCommand;
use Illuminate\Database\Console\WipeCommand;
use Illuminate\Support\ServiceProvider;

final class AppServiceProvider extends ServiceProvider
{
    // ...

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        WipeCommand::prohibit($this->app->isProduction());
        FreshCommand::prohibit($this->app->isProduction());
        ResetCommand::prohibit($this->app->isProduction());
        RefreshCommand::prohibit($this->app->isProduction());
        RollbackCommand::prohibit($this->app->isProduction());
    }
}
Enter fullscreen mode Exit fullscreen mode

As we can see in the code example above, in our App\Providers\AppServiceProvider class, we've used the boot method to prohibit the destructive commands from running in a production environment.

The prohibit method accepts a boolean parameter. If the parameter is true, the command will be prohibited from running. If the parameter is false, the command will be allowed to run.

If you were to try running one of these commands outside of a production environment, the commands would run as expected. But if you tried running one of these commands in a production environment, you'd see the following message:

WARN  This command is prohibited from running in this environment.
Enter fullscreen mode Exit fullscreen mode

Laravel also ships with a handy Illuminate\Support\Facades\DB::prohibitDestructiveCommands helper method so you can prevent the five commands from running in a production environment with a single line of code rather than needing to prohibit each one individually:

declare(strict_types=1);

namespace App\Providers;

use Illuminate\Support\Facades\DB;
use Illuminate\Support\ServiceProvider;

final class AppServiceProvider extends ServiceProvider
{
    // ...

    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        DB::prohibitDestructiveCommands($this->app->isProduction());
    }
}
Enter fullscreen mode Exit fullscreen mode

As we can see in the code example, we've used the Illuminate\Support\Facades\DB::prohibitDestructiveCommands helper method and passed it a boolean value. This is equivalent to the previous example but is more concise. I'll be adding this line of code to all my Laravel projects from now on.

Prohibiting Your Own Artisan Commands

You may want to use the Illuminate\Console\Prohibitable trait in your own Artisan commands. For example, you might have a command that deletes all data from a third-party service, such as Stripe, so you'll want to prevent that from running in a production environment. Or, you may want to prevent a command from running until a particular feature flag is enabled.

Let's look at how you could use this trait in your own commands.

Imagine you have created a command which makes API calls to Stripe to clear all your data. We'll assume we don't want this command to run in a production environment and is only intended for local development purposes. Let's take a look at what the command might look like:

declare(strict_types=1);

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Illuminate\Console\Prohibitable;

final class ClearStripeData extends Command
{
    use Prohibitable;

    protected $signature = 'app:clear-stripe-data';

    // ...

    public function handle(): int
    {
        if ($this->isProhibited()) {
            return self::FAILURE;
        }

        // Delete Stripe data...
    }
}
Enter fullscreen mode Exit fullscreen mode

In the command above, we can see that we're using the Illuminate\Console\Prohibitable trait in our App\Console\Commands\ClearStripeData command.

We've also added a check at the beginning of the handle method to see if the command is prohibited from running. If it is, we return self::FAILURE, preventing the rest of the command from running. If it's not prohibited, the rest of the command will continue running as expected.

Even though we've added the Illuminate\Console\Prohibitable trait to our command, the command will still run as expected because we've not actually prohibited it from running yet.

So we'll head over to our App\Providers\AppServiceProvider class and add the following line of code to prohibit the ClearStripeData command from running in a production environment:

declare(strict_types=1);

namespace App\Providers;

use App\Console\Commands\ClearStripeData;
use Illuminate\Support\ServiceProvider;

final class AppServiceProvider extends ServiceProvider
{
    /**
     * Bootstrap any application services.
     */
    public function boot(): void
    {
        ClearStripeData::prohibit();
    }
}
Enter fullscreen mode Exit fullscreen mode

Now, if we were to try running the app:clear-stripe-data command in a production environment, we'd see the following message:

WARN  This command is prohibited from running in this environment.
Enter fullscreen mode Exit fullscreen mode

Pretty cool, right?

Of course, you might be looking at this and thinking, "Why wouldn't I just replace the check at the top of the command with if ($this->app->isProduction())?" And you'd be right; that's a totally valid approach and might be more suitable for you.

But I think the Illuminate\Console\Prohibitable trait is a nice way to encapsulate the logic for prohibiting a command from running in a single place. It's particularly nice if you want to reuse the same logic across multiple commands.

Conclusion

In this article, we've looked at how to prevent destructive commands from running in Laravel applications. We've also seen how we can use the Illuminate\Console\Prohibitable trait to prevent our own commands from running in a production environment.

If you enjoyed reading this post, you might be interested in my 220+ page ebook, "Battle Ready Laravel", which covers similar topics in more depth.

Or, you might want to check out my other 440+ page ebook, "Consuming APIs in Laravel", which teaches you how to use Laravel to consume APIs from other services.

If you'd like to be updated each time I publish a new post, feel free to sign up for my newsletter.

Keep on building awesome stuff! 🚀

Top comments (5)

Collapse
 
fstrube profile image
Franklin Strube

This should be the first commit in your repo!

Collapse
 
ashallendesign profile image
Ash Allen

Heck yeah! 🔥

Collapse
 
drfcozapata profile image
Francisco Zapata

I must try it.
Thanks for sharing.
Blessings

Collapse
 
ashallendesign profile image
Ash Allen

It's no problem at all! I hope it comes in handy 😄

Collapse
 
mobiloud profile image
MobiLoud

Nice!