isimmons's avatar

Lumen: debug mode not showing stack trace

This may be an intentional part of lightening the framework but I was wondering.

Testing out Lumen for the first time I get the "page not found" and "whoops something went wrong" pages when debug is false but when I set debug to true I just get the old fashioned php error which I haven't seen in years.

For example, a intentional call to an undefined method simply gives me this

Fatal error: Call to undefined method App\Services\Database\Crud::all() in D:\projects\lumen-api\app\Models\User\User.php on line 11

Whereas with Laravel I would get a full stack trace.

Wondering if I should install filp/whoops.

0 likes
6 replies
isimmons's avatar

Just realized these aren't exceptions but php fatal and parse errors so an exception handler won't handle this. Yet I never saw these kind of errors in Laravel. How is it that Laravel converts these into pretty exception handler type pages?

isimmons's avatar

Placing the following in a class takes care of getting an exception for bad method call

public function __call($method, $args)
 {
    if(! method_exists($this, $method)) throw new \BadMethodCallException("Call to undefined method {$method}()");
    
    return $method($args);

 }

But still getting parse errors. How can I catch those and throw an exception instead?

isimmons's avatar

Ok now I'm getting somewhere with this. It shows the symfony exception page but also shows the raw php error above it so I think the only issue now is that it's not handling the error soon enough in the request.

I did some copying from Laravel's Foundation\Bootstrap\HandleExceptions class into Lumens Laravel\Lumen\Application class where it handles exceptions. The main thing is the missing "register_shutdown_function" followed by a few other protected functions it calls.

    /**
     * Set the error handling for the application.
     *
     * @return void
     */
    protected function registerErrorHandling()
    {
        error_reporting(-1);

        set_error_handler(function ($level, $message, $file = '', $line = 0) {
            throw new ErrorException($message, 0, $level, $file, $line);
        });

        set_exception_handler(function ($e) {
            $this->handleUncaughtException($e);
        });
/// starts here by adding  register_shutdown_function
        register_shutdown_function(function(){
            if ( ! is_null($error = error_get_last()) && $this->isFatal($error['type'])) {
                $this->handleException($this->fatalExceptionFromError($error));
            }
        });
    }

    protected function isFatal($type)
    {
        return in_array($type, [E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]);
    }
    public function handleException($e)
    {
        $this->getExceptionHandler()->report($e);

        if ($this->app->runningInConsole())
        {
            $this->renderForConsole($e);
        }
        else
        {
            $this->renderHttpResponse($e);
        }
    }

    protected function fatalExceptionFromError(array $error)
    {
        return new FatalErrorException(
            $error['message'], $error['type'], 0, $error['file'], $error['line']
        );
    }

    protected function getExceptionHandler()
    {
        return $this->app->make('Illuminate\Contracts\Debug\ExceptionHandler');
    }

    protected function renderHttpResponse($e)
    {
        $this->getExceptionHandler()->render($this->app['request'], $e)->send();
    }
/// the above gets me symfony exception pages for parse errors but still shows raw php error above it
prograhammer's avatar

I'm getting a blank page, no errors at all? I have debug set to TRUE in my .env as well. I'm trying to figure out what is going on. I'm using Homestead and HHVM, so I might try and remove HHVM and see if it's related to that...

prograhammer's avatar

@isimmons thanks for pointing me in the right direction...

First, make sure you have these settings in your php.ini:

/etc/hhvm/php.ini

 hhvm.server.implicit_flush = true
 hhvm.error_handling.call_user_handler_on_fatals = true

Then I just stuck all the code (with some fixes from this hhvm github issue) into a file in my App/Exceptions folder:

app/Exceptions/HandleExceptionsFix.php

<?php namespace App\Exceptions;

use ErrorException;
use Illuminate\Contracts\Foundation\Application;
use Symfony\Component\Console\Output\ConsoleOutput;
use Symfony\Component\Debug\Exception\FatalErrorException;

class HandleExceptionsFix {


    protected $app;

    protected static $fatalError = null;

    public function bootstrap(Application $app)
    {
        $this->app = $app;

        error_reporting(-1);

        set_error_handler([$this, 'handleError']);

        set_exception_handler([$this, 'handleException']);

        register_shutdown_function([$this, 'handleShutdown']);

        if ( ! $app->environment('testing'))
        {
            ini_set('display_errors', 'Off');
        }
    }

    public function handleError($level, $message, $file = '', $line = 0, $context = array())
    {

        // **** Add this, loads fatal error
        if ($level & (1 << 24)) {
            self::$fatalError = array(
                'message' => $message,
                'type' => $level,
                'file' => $file,
                'line' => $line
            );
        }

        if (error_reporting() & $level)
        {
            throw new ErrorException($message, 0, $level, $file, $line);
        }
    }

    public function handleException($e)
    {
        $this->getExceptionHandler()->report($e);

        if ($this->app->runningInConsole())
        {
            $this->renderForConsole($e);
        }
        else
        {
            $this->renderHttpResponse($e);
        }
    }

    protected function renderForConsole($e)
    {
        $this->getExceptionHandler()->renderForConsole(new ConsoleOutput, $e);
    }

    protected function renderHttpResponse($e)
    {
        $this->getExceptionHandler()->render($this->app['request'], $e)->send();
    }

       // Updated this to check for the fatal error
    public function handleShutdown()
    {
        $error = error_get_last();

        if(self::$fatalError){
            $error = self::$fatalError;
        }

        if ( ! is_null($error) && $this->isFatal($error['type']))
        {
            $this->handleException($this->fatalExceptionFromError($error, 0));
        }
    }


    protected function fatalExceptionFromError(array $error, $traceOffset = null)
    {
        return new FatalErrorException(
            $error['message'], $error['type'], 0, $error['file'], $error['line'], $traceOffset
        );
    }


    protected function isFatal($type)
    {
        // *** Add type 16777217 that HVVM returns for fatal
        return in_array($type, [16777217, E_ERROR, E_CORE_ERROR, E_COMPILE_ERROR, E_PARSE]);
    }


    protected function getExceptionHandler()
    {
        // return $this->app->make('Illuminate\Contracts\Debug\ExceptionHandler');
        return new \App\Exceptions\Handler();   // <-- call our app's handler instead
    }

}

And in bootstrap/app.php I call it right after $app is set:

bootstrap/app.php

  $hhvmfix = new App\Exceptions\HandleExceptionsFix;
  $hhvmfix->bootstrap($app);

(and make sure you have Dotenv::load(__DIR__.'/../'); uncommented in the file so you can see the full information available depending on environment settings)

Please or to participate in this conversation.