0

I'm writing a custom error handler for Slim/3.3.0 and I'm trying to figure out if it's worth reusing the same code to handle both errors and exceptions. To do so I've defined a custom error handler to convert errors into ErrorException instances:

require __DIR__ . '/../vendor/autoload.php';

set_error_handler (function ($errno, $errstr, $errfile, $errline) {
    if (!(error_reporting() & $errno)) {
        return true; // Do not run built-in handler
    }
    throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
});

$app = new \Slim\App(['settings' => ['displayErrorDetails' => false]]);
$container = $app->getContainer();
// [...]
$container['errorHandler'] = function (Slim\Container $c) {
    return new App\Handlers\Error($c->logger, $c['settings']['displayErrorDetails']);
};

I can then log uncaught exceptions and/or display my generic "There was an error" page to my liking (so far so good).

But now I want to handle minor issues (E_WARNING, E_NOTICE, etc.) differently: instead of aborting everything and showing the generic error page template, I want to be able to continue execution and/or display the error message inline (just like PHP does by default) and here's where I'm lost. The display inline part is easy but my scripts aborts right there:

namespace App\Handlers;

use Psr\Http\Message\ServerRequestInterface as Request;
use Psr\Http\Message\ResponseInterface as Response;

final class Error extends \Slim\Handlers\Error
{
    public function __invoke(Request $request, Response $response, \Exception $e)
    {
        if ($this->displayErrorDetails) {
            $response->write($e);
        } else {
            $this->saveToLog($e);
            $response->write('[ERROR]');
        }

        if ($this->isFatal($e)) {
            // Aborts scripts and displays error page (OK)
            return parent::__invoke($request, $response, $e);
        } else {
            // Seems to abort script (nothing else is shown from this poing)
            return $response;
        }
    }
}

... testing it this way:

$app->get('/warning-test', function (Request $request, Response $response) {
    $this->logger->info("Loading {$_SERVER['REQUEST_URI']}");
    $response->write('<h1>Warning test page</h1>');
    $response->write('<p>About to generate a warning:</p>');
    $this->logger->info("Generating warning...");
    1/0;
    $this->logger->info("Warning generated");

    $response->write('<p>This should display as well.</p>');
    // ... but it doesn't. Probably because Response is immutable and my copy
    // was superseded by a clone

    return $response;
});

What are my options?

Álvaro González
  • 142,137
  • 41
  • 261
  • 360
  • Once the error handler is invoked, Slim stops exeuction ... There is no way to continue that I am aware of – geggleto Aug 31 '16 at 17:50

1 Answers1

1

The set_error_handler() function takes as second parameter the error type therefore you can specify that only E_ERROR should use your custom error handler:

$errorTypes = E_ERROR;
set_error_handler (function ($errno, $errstr, $errfile, $errline) {
    if (!(error_reporting() & $errno)) {
        return true; // Do not run built-in handler
    }
    throw new \ErrorException($errstr, 0, $errno, $errfile, $errline);
}, $errorTypes);

When you want handle notices and warnings by yourself, you cannot throw an Exception as it basically cancels the normal route and only take the error response. You can do this without throwing an Exception like so:

$errorTypes = E_WARNING | E_NOTICE;
set_error_handler (function ($errno, $errstr, $errfile, $errline) {
    if (!(error_reporting() & $errno)) {
        return true; // Do not run built-in handler
    }
    \App\Handlers\Error::setNoticeOrWarning($errno, $errstr, $errfile, $errline);
}, $errorTypes);

Then you can check this later in middleware and display this.

jmattheis
  • 10,494
  • 11
  • 46
  • 58