How do I redefine Slim v3's error handler? - php

I followed the Slim v3 documentation to a tee on how to redefine the framework's built in error handler so as to prevent errors from being outputted to the response with the following code:
$settings = require __DIR__ . '/../src/settings.php';
$app = new \Slim\App($settings);
// Errors to log rather than to end user
$c = $app->getContainer();
$c['errorHandler'] = function ($c) {
return function ($request, $response, $exception) use ($c) {
$c['logger']->error("Error 500 diverted: ".$exception->getMessage());
return $c['response']->withStatus(500)
->withHeader('Content-Type', 'text/html')
->write('Something went wrong on our side!');
};
};
And yet my API is still spitting out Slim's default handler with full stack traces and the telltale Slim Application Error string... albeit helpful, I'd much rather have this information going to Slim's log (monolog) and something less revealing facing the client. Is there any reason that this service redefinition is effectively being ignored?

This code works:
<?php
$app = new \Slim\App();
$container = $app->getContainer();
$container['errorHandler'] = function ($c) {
return function ($request, $response, $exception) use ($c) {
// log error here
return $c['response']->withStatus(500)
->withHeader('Content-Type', 'text/html')
->write('Something went wrong on our side!');
};
};
$container['phpErrorHandler'] = function ($c) {
return function ($request, $response, $error) use ($c) {
// log error here
return $c['response']->withStatus(500)
->withHeader('Content-Type', 'text/html')
->write('Something went wrong on our side (again)!');
};
};
$app->get('/exception', function ($req, $res, $args) {
// errorHandler will trap this Exception
throw new Exception("An error happened here");
});
$app->get('/php7', function ($req, $res, $args) {
$x = function (int $x) {
return $x;
};
// phpErrorHandler wil trap this Error
$x('test');
});
$app->get('/warning', function ($req, $res, $args) {
$x = UNDEFINED_CONSTANT;
});
$app->run();
As you can see, you need to register an errorHandler to catch Exceptions and a phpErrorHandler to catch PHP 7 Errors.
Note that neither will catch a PHP Notice as shown by the /warning route. To catch that too, you need to register your own error handler:
set_error_handler(function ($severity, $message, $file, $line) {
if (!(error_reporting() & $severity)) {
// This error code is not included in error_reporting, so ignore it
return;
}
throw new \ErrorException($message, 0, $severity, $file, $line);
});

Related

Laravel send json response from inner function

So Im building an API.
But I keep running into returning a json response from an inner function.
If I do the following then laravel sends a backlog or error log to the client. That should never ever happen in a api. So how do you do this in Laravel?
I want to return json immediately and stop excecuting without showing the client any other information
public function functionThatIsCalledByClient($data)
{
$this->validateSomething($data);
}
private function validateSomething($data)
{
if(! $data ) ) return response()->json(['error' => 'some message'], 400)->send();
return true;
}
You could use abort helper for that or, for complex cases, error handling.
In case of abort:
private function validateSomething($data)
{
if(! $data ) // you can create a custom helper function to wrap this code.
abort(400, json_encode(['error'=>'some error']), ['Content-Type: application/json']);
return true;
}
In case of general handler:
private function validateSomething($data)
{
if(! $data )
throw new \Exception('some message');
return true;
}
Inside app/Exceptions/Handler.php # render
public function render($request, Exception $e)
{
if($e instanceof Exception)
return response()
->json(['error' => $e->getMessage()], 400);
//->send(); should not be necessary
}
throw new GeneralException('invalid required extra quantity');

Do not abort response on ErrorException

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?
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.

How to reach the exception block

So I am messing around with symfony router component and I created a small wrapper.
One thing that came up was how do I get a request to throw a 500 in unit tests? The method in question is:
public function processRoutes(Request $request) {
try {
$request->attributes->add($this->_matcher->match($request->getPathInfo()));
return call_user_func_array($request->attributes->get('callback'), array($request));
} catch (ResourceNotFoundException $e) {
return new RedirectResponse('/404', 302);
} catch (Exception $e) {
return new RedirectResponse('/500', 302);
}
}
And the test in question is:
public function testFiveHundred() {
$router = new Router();
$router->get('/foo/{bar}', 'foo', function($request){
return 'hello ' . $request->attributes->get('bar');
});
$response = $router->processRoutes(Request::create('/foo/bar', 'GET'));
$this->assertEquals(500, $response->getStatusCode());
}
Right now the test will fail because we are defined and the status code will be 200. Is there something special I can do to the Request object I create, to make it throw a 500?
I think you got several options here you can play with:
Decide that a specific path will always throw an exception.
This will force you to make some changes in your code.
public function processRoutes(Request $request) {
...
if ($request->getRequestUri() == '/path/that/throws/exception') {
throw Exception('Forced to throw exception by URL');
}
...
}
public function testFiveHundred() {
...
$response = $router->processRoutes(Request::create('/path/that/throws/exception', 'GET'));
...
}
Make a DummyRequest object that will extends your original Request class and make sure this object will raise an Exception (for example - you know for sure that you use the getPathInfo(), so you can use this).
class DummyRequest extends Request {
public function getPathInfo() {
throw new Exception('This dummy request object should only throw an exception so we can test our routes for problems');
}
}
public function testFiveHundred() {
...
$dummyRequest = new DummyRequest();
$response = $router->processRoutes($dummyRequest);
...
}
Since the function getRequestUri of our $dummyRequest throws an exception, your call to $router->processRoutes will have our dummy to throw that exception.
This is a general idea, you would probably need to play a bit with the namespaces and the functions there (I didn't test it, however this should work).

Php Slim not handle errors with the custom errorHandler

My custom error handler is not working with Slim 3 framework. Instead of getting a 500 error, I get a response with status 200 and the html error details are in the body.
Here is my minimal, verifiable and complete example:
$c = new \Slim\Container();
$c['errorHandler'] = function ($c) {
return function($request, $response, $exception) use ($c) {
return $c['response']->withStatus(500)
->withHeader('Content-Type', 'text/html')
->write('Something went wrong!');
};
};
$app = new \Slim\App($c);
$app->any('/foo', function($request, $response, $args) {
$data = json_encode($request->nonExistingMethod()); // error!
return $response->withJson($data);
});
$app->run();
How do I need to refactor this sample to make it work? I suspect it's related with the Fatal nature of the error. But in this case how to deal with that?
Reference: http://www.slimframework.com/docs/handlers/error.html
Edit 1
For use in an api style web application, the final solution I'm using with minor changes from the response of this question:
function checkForError() {
$last = error_get_last();
if ($last) {
#header("HTTP/1.0 500 Internal Server Error");
echo json_encode($last); // optional, includes error details in json format
}
}
error_reporting(0);
register_shutdown_function('checkForError');
you cannot catch all errors like this
there is however one way to catch all except memory errors (or only those that tried to allocate more than your error handler needs)
function checkForError() {
$last = error_get_last();
if ($last) {
#header("HTTP/1.0 500 Internal Server Error");
echo 'we failed... sry';
}
}
register_shutdown_function('checkForError');
updated with 500 status header

Access function variable inside class method

I searched and there are a lot of answers but I cannot find one I need because I do not know even how to create correct question. here is the example.
$app->map('/v1/:module/:group/:action(/:id)', function ($module, $group, $action, $id = NULL) use ($app) {
$method = ucfirst($app->request->getMethod());
$file = "modules/{$module}/{$group}/{$method}{$action}.php";
if(!file_exists($file)) {
$app->halt(404, Error::_('API Processor was not found!', 404));
}
include_once $file;
$app->stop();
})
This is my API method by slim restful framework. Now for this Error::_('API Processor was not found!', 404) I have
class Error {
public static function _($msg, $code = 500) {
global $module, $group, $action;
return json_encode(array(
'error' => true,
'code' => $code,
'message' => $msg,
'module' => $module
));
}
}
What I want os to get access to $module, $group, $action variables without passing them into that function. But in my case $module is NULL.
{
"error":true,
"code":404,
"message":"API Processor was not found!",
"module":null
}
Possible?
You should be able to meet those requirements, if I understood your question correctly, by using the Slim Error Handling functionality. If it were my project, I'd create a custom exception to throw wherever you're planning on using your custom error function.
NOTE: All of the code below is untested and written off the top of my head. Caveat emptor and all that.
class CustomErrorException extends \Exception
{
}
Then I would throw that exception wherever I'd otherwise use my custom error function.
if(!file_exists($file)) {
throw new CustomErrorException('API Processor was not found!', 404);
}
Finally, I'd write an error function that looks something like this:
$app->error(function (\Exception $e) use ($app) {
if ($e instanceof CustomErrorException) {
// Parse $path to get $module, $group, and $action
// (Seems like that would work based on the route in your example: '/v1/:module/:group/:action(/:id)')
$path = $app->request->getPath();
// Possible new method signature for Error::_
Error::_($e->getMessage(), $e->getCode(), $module, $group, $action);
// Render an error page, $app->halt(), whatever.
}
});
That should help DRY your code up a bit and allow you to dump those global variables.

Categories