7

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?

Moe
  • 4,744
  • 7
  • 28
  • 37
  • Did you try to use `try {} catch (\Exception $e) {}` inside the `->fire()` method? What is the error that you get? Where is that exception coming from? – ljubadr Oct 18 '17 at 23:12
  • @ljubadr yes that would work in that class only, but because I have many similar classes, I'll have to repeat the try{} catch block. I feel it would be cleaner to throw an exception up the stack and catch it in the stack where `fire()` is called – Moe Oct 19 '17 at 04:05

1 Answers1

9

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...
    }
}
Cy Rossignol
  • 16,216
  • 4
  • 57
  • 83
  • Thanks for this Cy. It make senses. Does this apply for Artisan Commands specifically in Lumen too? – Moe Oct 19 '17 at 04:06
  • 1
    @Moe - You're welcome! And yes, the information in this answer also applies to Lumen. – Cy Rossignol Oct 19 '17 at 04:24
  • I guess I wanted to handle an Command level exception to send me an email with debug information – Moe Oct 19 '17 at 04:27
  • 1
    @Moe I think I did this in one project using the exception Handler that checks for `runningInConsole()`. [This article](https://laravel-news.com/email-on-error-exceptions) might give some ideas. – Cy Rossignol Oct 19 '17 at 04:55
  • 1
    Literally perfect, now I can just sub-class a custom exception. Thank you :) – Moe Oct 19 '17 at 05:07
  • I'm not sure what you mean by `(new ConsoleApplication)->renderException($e, $output);` but what worked for me was `app(ExceptionHandler::class)->renderForConsole($this->output, $e);` running laravel 5.6 – Jeff Puckett Jul 13 '18 at 06:21