Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Slim 4 - How to handle errors by ourselves completely? #2757

Closed
lautiamkok opened this issue Jul 17, 2019 · 16 comments
Closed

Slim 4 - How to handle errors by ourselves completely? #2757

lautiamkok opened this issue Jul 17, 2019 · 16 comments
Labels
pending response Waiting on a response from the original poster

Comments

@lautiamkok
Copy link

lautiamkok commented Jul 17, 2019

How can we handle errors by ourselves completely?

I get this error below:

Uncaught Slim\Exception\HttpNotFoundException: Not found.

If I remove these lines:

// $callableResolver = $app->getCallableResolver();
// $responseFactory = $app->getResponseFactory();
// $errorMiddleware = new ErrorMiddleware($callableResolver, $responseFactory, true, true, true);
// $app->add($errorMiddleware);

Then, when we try to catch to the error through a middleware:

$app->add(function (Request $request, RequestHandler $handler) {

    try {
        $response = $handler->handle($request);
        $existingContent = (string) $response->getBody();
        ...
    } catch (\Exception $error) {
        $response = $handler->handle($error->getRequest());
        $data = [
            "status" => $error->getCode(),
            "messsage" => $error->getMessage()
        ];
        $payload = json_encode($data);
        $response->getBody()->write($payload);

        return $response
          ->withHeader('Content-Type', 'application/json')
          ->withStatus($status);
    };

});

We get the same error back:

Uncaught Slim\Exception\HttpNotFoundException: Not found.

We are trying to make our custom error in the JSON format, for example:

{"status":404,"message":"page not found"}

Any ideas?

@adriansuter
Copy link
Contributor

adriansuter commented Jul 17, 2019

By calling the handle() method of the handler in the catch-block again, the handler would throw the exception again. But this time, no one is there to catch this second exception.

In Slim 4 you could either define your custom error handler (and pass it to the ErrorMiddleware) or simply define custom error renderers (based on accepted content-types by your request or on a forced content-type).

More information can be found in the docs, currently in development: https://github.com/slimphp/Slim-Website/blob/gh-pages-4.x/docs/v4/middleware/error-handling.md

@lautiamkok
Copy link
Author

lautiamkok commented Jul 17, 2019

@adriansuter none of that actually works. giving up...

In my view, this should be made simpler. But it is way too complicated now.

@l0gicgate
Copy link
Member

@lautiamkok it does work, I think you're doing something wrong on your end. Where are you adding this middleware? It should be the very last middleware you add, right before that middleware should be the routing middleware, otherwise Slim automatically appends the RoutingMiddleware if it hasn't been appended manually, which means it would go in front of your error handling middleware, which means you wouldn't catch anything.

@l0gicgate l0gicgate added the pending response Waiting on a response from the original poster label Jul 17, 2019
@lautiamkok
Copy link
Author

lautiamkok commented Jul 17, 2019

@l0gicgate let me check later again and get back to you later. there are some errors in the code in the doc by the way

slim should have left all the error handling alone from the year dot. how it handles errors just does not make sense. for example in slim 3.12, if i want to handle 404 and other errors myself, I have to write two blocks of code:

$container = new \Slim\Container();
$container['errorHandler'] = function ($container) {
    return function ($request, $response, $exception) use ($container) {
        // print_r(get_class_methods($exception));
        $message = $exception->getMessage();
        $code = $exception->getCode();

        $data = [
            "status" => $code,
            "message" => $message
        ];
        $payload = json_encode($data);
        $response->getBody()->write($payload);

        return $response
            ->withHeader('Content-Type', 'application/json')
            ->withStatus($code);
    };
};
$container['notFoundHandler'] = function ($container) {
    return function ($request, $response) use ($container) {
        return $response->withStatus(404)
            ->withHeader('Content-Type', 'text/html')
            ->write('Page not found');
    };
};

It should just be one block of code. also, errorHandler and notFoundHandler work differently. they should work the same way.

@lautiamkok
Copy link
Author

My personal view on these lines below for handling error in slim 4:

$callableResolver = $app->getCallableResolver();
$responseFactory = $app->getResponseFactory();
$errorMiddleware = new ErrorMiddleware($callableResolver, $responseFactory, true, true, true);
$app->add($errorMiddleware);

I am not sure if this is a PSR standard or not but it is too ugly to read and to understand what is going on.

I don't know what these two $callableResolver and $responseFactory are doing there. if they are not helping us to understand by reading them, they shouldn't be there.

this is just my opinion.

@adriansuter
Copy link
Contributor

That is dependency injection. As the middleware needs to have the callable resolver and the response factory, they would be given to the middleware. That way, the middleware is as independent as possible from the rest.

@l0gicgate
Copy link
Member

I’m closing this as resolved. The docs are clear. If you dislike the architecture you can always use a different framework. You’re also more than welcome to contribute when those architecture decisions are being made, the PRs and discussions are public.

@lightswitch05
Copy link

lightswitch05 commented Mar 27, 2020

Another way to handle this is to define a default route after all your other route definitions:

    $app->any('{route:.*}', function(Request $request, Response $response) {
        $response = $response->withStatus(404, 'page not found');
        return $response;
    });

@b-hayes
Copy link

b-hayes commented Aug 31, 2022

I think a good answer would be to show exactly how a way to disable ALL error handling of any kind since I'm sure slim is already using specific exception types internally that can easily be caught. Or show how to specify a single error handler for all events so a controller view can be used and just display a 404/500 or whatever else message it needs to show accordingly.

Just saying docs are clear with no reference and if you don't like it move along isn't a great response.

If there is no way to do this I'd agree it is just too complicated. Error handling is so simple.

@skizzo
Copy link

skizzo commented Aug 31, 2024

I'm unsuccessfully searching for a way to achieve what was so easily possible in Slim 3 – catching any kind of exception caused when calling a route, handling it myself and then displaying a JSON response with a custom HTTP status code, like mentioned in #2757 (comment).

Could anybody please point me into the right direction, it's crazy that this standard feature is not even mentioned in the docs. Thanks.

@odan
Copy link
Contributor

odan commented Aug 31, 2024

@skizzo It is possible. Just don't add the ErrorHandlingMiddleware and replace it with your own middleware that catches all Throwables and/or other more specific Exceptions.

@skizzo
Copy link

skizzo commented Sep 1, 2024

@odan , sorry but your "now draw the rest of the f***king owl" answer is not helpful at all. The issue subject literally says "how" and not "if" this is possible, and nobody here even bothered.

So in any case anybody else stumbles over this issue after hours of searching the web (for something that should be properly documented in the first place), I finally got it working like this in Slim 4.14 (PHP 8.x):


Step 1: Install a PSR-7 Implementation

We need to install a PSR-7 implementation, e.g.nyholm/psr7 with composer require nyholm/psr7.


Step 2: Create custom Error Handler

use Nyholm\Psr7\Factory\Psr17Factory;
use Psr\Http\Message\ResponseInterface;
use Psr\Http\Message\ServerRequestInterface;
use Slim\Interfaces\ErrorHandlerInterface;

class NBSlimErrorHandler implements ErrorHandlerInterface
{
  // invoked from outside of Slim
  public static function logException(Throwable $exception)
  {
    $statusCode = 500; // or anything else
    $errorData  = NBSlimErrorHandler::getExceptionData($exception);

    // feel free to use your own logger here
    NBLogger::error($errorData["message"], [
      "message" => $errorData["message"],
      "file"    => $errorData["file"],
      "line"    => $errorData["line"],
      "trace"   => $errorData["trace"],
    ]);

    header('Content-Type: application/json');
    http_response_code(500);
    echo json_encode([
      "success" => false,
      "error"   => $errorData,
    ]);
    exit();
  }

  // invoked by Slim
  public function __invoke(ServerRequestInterface $request, Throwable $exception, bool $displayErrorDetails, bool $logErrors, bool $logErrorDetails): ResponseInterface
  {
    $statusCode = 500; // or anything else
    $errorData  = NBSlimErrorHandler::getExceptionData($exception);

    // feel free to use your own logger here
    NBLogger::error($errorData["message"], [
      "message" => $errorData["message"],
      "file"    => $errorData["file"],
      "line"    => $errorData["line"],
      "trace"   => $errorData["trace"],
    ]);

    $psr17Factory = new Psr17Factory();
    $response     = $psr17Factory->createResponse($statusCode);
    $response->getBody()->write(json_encode([
      "success" => false,
      "error"   => $errorData,
    ]));
    return $response->withStatus($statusCode)->withHeader('Content-Type', 'application/json');
  }

  // Helper Function to extract data from an exception
  public static function getExceptionData(Throwable $exception)
  {
    $stripPath = getPublicHtmlFolder(); // see below

    $message = $exception->getMessage();
    $file    = implode("", explode($stripPath, $exception->getFile())); // remove $stripPath
    $line    = $exception->getLine();
    $trace   = $exception->getTraceAsString();
    $trace   = explode("\n", substr(implode("", explode($stripPath, $trace)), 1));
    return [
      "message" => $message,
      "file"    => $file,
      "line"    => $line,
      "trace"   => $trace,
    ];
  }

}

// for errors outside of a Slim Route handler
function log_exception(Throwable $exception)
{
  NBSlimErrorHandler::logException($exception);
}
set_exception_handler("log_exception");
ini_set("display_errors", "off");
error_reporting(E_ALL);

// helper function to determine the public_html folder
function getPublicHtmlFolder()
{
  $dirname        = dirname(__DIR__);
  $search         = "/public_html/";
  $pathPublicHtml = mb_substr($dirname, 0, mb_strpos($dirname, $search) + mb_strlen($search));
  return $pathPublicHtml;
}

Step 3: Use custom Error Handler in Slim App

// ...
$app = AppFactory::create();
$app->setBasePath("/your/base/path");

// Use custom Error Handler
$errorMiddleware = $app->addErrorMiddleware(true, true, true);
$errorMiddleware->setDefaultErrorHandler(NBSlimErrorHandler::class);

// Add Routing Middleware
$app->addRoutingMiddleware();
// ...

@odan
Copy link
Contributor

odan commented Sep 1, 2024

@skizzo

  1. I explained one way of how this could work, if you would be able to read my answer.
  2. This is not a free development / coding service here.
  3. You should learn how to communicate in a respectful way.

@skizzo
Copy link

skizzo commented Sep 1, 2024

I apologize for my tone, but it's just frustrating that issues like these are being closed with reasoning that sounds like "RTFM" when the docs obviously don't explain such a basic use case scenario – which is why this issue was created in the first place.

@b-hayes
Copy link

b-hayes commented Sep 2, 2024

I get your frustration @skizzo. If its so clear then why is it so hard for anyone to say it. Thanks for posting your solution im sure it will help someone. Feel bad for not posting what I did to resolve this now.

@niladri-basak-codeclouds
Copy link

niladri-basak-codeclouds commented Oct 10, 2024

@skizzo
Thanks man you are the life saver ❤

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pending response Waiting on a response from the original poster
Projects
None yet
Development

No branches or pull requests

8 participants