Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to neatly handle Exceptions in Artisan Commands

Using Lumen to create an API - love Laravel but all the View's that come with it were overkill for the project I am creating.

Anyway, I've made a series of Commands which go out and collect data and stores it to the database.

<?php 

namespace App\Console\Commands;

use Illuminate\Console\Command;
use Symfony\Component\Console\Input\InputOption;

use App\User;

class GetItems extends Command {

    /**
     * The console command name.
     *
     * @var string
     */
    protected $name = 'GetItems';

    /**
     * The console command description.
     *
     * @var string
     */
    protected $description = "Get items and store it into the Database";

    /**
     * Execute the console command.
     *
     * @return void
     */
    public function fire()
    {
        $this->info("Collecting ...");

       $users = User::all();

       foreach( $users as $user)
       {
           $user->getItems();
       }

   }

    /**
     * Get the console command options.
     *
     * @return array
     */
    protected function getOptions()
    {
        return [];
    }

}

I've got 3 similar commands, each one collecting slightly different datasets.

Is there a way I can inject a middle-layer that catches an exception that comes from each of the fire() functions across my Commands? I was thinking of extending the Command Class - but wanted to see if there's already a way to do it that's recommended by the Framework creators (documentation/searching was no help).

I know the alternative would be to combine all the commands into one file and use options, but this makes it messy and harder to collaborate with.

Any suggestions?

like image 956
Moe Avatar asked Oct 18 '17 22:10

Moe


People also ask

Which artisan command makes another artisan command?

To call another Artisan command and save its output you should use $this->call() from your command.

What is exception handling in Laravel?

By default, the Laravel exception handler will convert exceptions into an HTTP response for you. However, you are free to register a custom rendering closure for exceptions of a given type. You may accomplish this via the renderable method of your exception handler.


1 Answers

The answer depends on what we want the application to do when the command throws an exception. The question doesn't describe a desired way to handle the exception, so let's look at a few options.

Laravel and Lumen projects include a central exception Handler class that we can use to define behaviors for different exceptions. This class handles any exceptions that bubble up from web requests and console commands.

Laravel uses the report() method in app/Exceptions/Handler.php to determine how to log an exception. We can add logic here for error reporting:

public function report(Exception $e)  
{
    if ($e instanceof CustomConsoleException) {
        // do something specific...
    }
    ...
}

The renderForConsole() method lets us customize how we want to display error and exception messages for console commands. The project's exception Handler usually doesn't contain this method definition, but we can override it in app/Exceptions/Handler.php if needed:

public function renderForConsole($output, Exception $e)
{
    $output->writeln('Something broke!'); 

    (new ConsoleApplication)->renderException($e, $output);
}

In the example above, $output is a reference to a Symfony\Component\Console\Output \OutputInterface object that we can use to write text to the console command's output streams.

As we might guess from above, the central exception handler is designed to deal with uncaught exceptions that our code doesn't handle at a lower level, so it's not very useful when we need to execute some specific action after an exception. In a similar fashion, we could override the reportException() and renderException() methods in app/Console/Kernel.php.

If we need to do something specific besides just acknowledging that a command threw an exception by showing a message, we really should write this logic in the command itself. To avoid duplicate code, we could use an abstract class that the three similar commands provide concrete implementations for:

abstract class AbstractGetItems extends Command 
{
    ...
    final public function fire() 
    {
        try {
            $this->getItems();
        } catch (Exception $e) {
            // handle exception... 
        }
    }

    abstract protected function getItems();
}

This abstract command forces child classes to implement the getItems() method, which the class calls automatically in fire(). We can add any other shared logic to this class. The child commands need only to define their specific implementation of getItems(), and the parent class will handle exceptions for them:

class GetSpecificItems extends AbstractGetItems 
{ 
    ... 
    protected function getItems() 
    {
        // fetch specific items...
    }
}
like image 143
Cy Rossignol Avatar answered Nov 09 '22 11:11

Cy Rossignol