At present, both Laravel framework and ThinkPHP framework only provide the methods to return JSON data, and even the error information should be returned in JSON format. The exception handling is not returned to us in JSON format, so we need to rewrite it ourselves.

We first create an ExceptionHandler. PHP extension from handler. PHP in the app/Exceptions directory

namespace App\Exceptions;
 
class ExceptionHandler extends Handler
{
 
}
Copy the code

In bootstrap/app.php, we use our custom ExceptionHandler class to replace the default Handler class

$app-> Singleton (Illuminate\Contracts\Debug\ExceptionHandler::class, App\Exceptions\ExceptionHandler::class );Copy the code

In the render method, we use APP_DEBUG in the.env file. If it is debug mode, we render the error in the default way. If it is not debug mode, we return JSON information

namespace App\Exceptions; use Exception; class ExceptionHandler extends Handler { public function render($request, Exception $exception) { if (env('APP_DEBUG')) { return parent::render($request, $exception); } return response()->json([ 'code' => $exception->getCode(), 'msg' => $exception->getMessage() ]); }}Copy the code

Now let’s test this by setting the APP_DEBUG value of.env to false. Then we intentionally miswrite the code and access the interface via Postman or a browser

Route::get('/', function () {echo 'Hello World! '});Copy the code

APP_DEBUG=true is still rendered by default to help us find errors

By default, exceptions are recorded in the storage/logs directory in the format of laravel-date (YYYY-MM-DD) with.log as the suffix

When we open the log file to check the error information recorded, we can find that the error information is very detailed. In addition to the error description, the call stack is also recorded, as shown in the figure below

Basically, the information in the red box is enough to get us wrong, it doesn’t need to be as detailed as it is now, so instead of keeping track of the call stack, we can rewrite the report method so let’s look at the framework report method, Code (SRC/Illuminate/Foundation/Exceptions/Handler. PHP), I used the red box up code is the call stack information, when we rewrite this method only need to completely copy all the code in this method to our custom report way, Then remove the code in the red box

We override the report method in our custom exceptionHandler.php exception handling class

public function report(Exception $exception)
{
    if ($this->shouldntReport($exception)) {
        return;
    }
 
    if (Reflector::isCallable($reportCallable = [$exception, 'report'])) {
        return $this->container->call($reportCallable);
    }
 
    try {
        $logger = $this->container->make(LoggerInterface::class);
    } catch (Exception $ex) {
        throw $exception;
    }
 
    $logger->error(
        $exception->getMessage()
    );
}
Copy the code

Then we request the interface again to go to see the error log records, did not record the call stack information can be found, but the following information is not enough, we can’t according to the following information to determine where the error occurred and which line of a file if you can at the time of record error message at the same time was recorded in the file and line error is better, So by modifying the report method

public function report(Exception $exception)
{
    if ($this->shouldntReport($exception)) {
        return;
    }
 
    if (Reflector::isCallable($reportCallable = [$exception, 'report'])) {
        return $this->container->call($reportCallable);
    }
 
    try {
        $logger = $this->container->make(LoggerInterface::class);
    } catch (Exception $ex) {
        throw $exception;
    }
 
    $logger->error(
        $exception->getMessage()." at ".$exception->getFile().":".$exception->getLine()
    );
}
Copy the code

In the code, I add the number of files and lines through the getFile() and getLine() methods of exception, save the code and access the interface again, look at the error log file, we can see that the file and the number of lines where the error occurred have been recorded, with this information we can basically find the error

That’s all we need for exceptionHandler.php until we implement the original requirement here

namespace App\Exceptions; use Exception; use Illuminate\Support\Reflector; use Psr\Log\LoggerInterface; class ExceptionHandler extends Handler { public function render($request, Exception $exception) { if (env('APP_DEBUG')) { return parent::render($request, $exception); } return response()->json([ 'code' => $exception->getCode(), 'msg' => $exception->getMessage() ]); } public function report(Exception $exception) { if ($this->shouldntReport($exception)) { return; } if (Reflector::isCallable($reportCallable = [$exception, 'report'])) { return $this->container->call($reportCallable); } try { $logger = $this->container->make(LoggerInterface::class); } catch (Exception $ex) { throw $exception; } $logger->error( $exception->getMessage()." at ".$exception->getFile().":".$exception->getLine() ); }}Copy the code

Then is not enough, we found that just we put on the server side error information returned to the client in JSON format, it is not allowed, we should only put some client error returned to the client, such as id and the password is less than six such illegal, and server error when we returned to the client only a vague information, For example, “server errors”, the actual server error information is recorded in the log so that developers can check for errors. So we need to define a ClientException for the special user to return client errors, using the following command to generate a ClientException

php artisan make:exception ClientException
Copy the code

Change the constructor to the following code

namespace App\Exceptions; use Exception; class ClientException extends Exception { public function __construct($code, $msg) { parent::__construct($msg, $code); }}Copy the code

Then we continue to modify exceptionHandler.php

namespace App\Exceptions; use Exception; use Illuminate\Support\Reflector; use Psr\Log\LoggerInterface; Class ExceptionHandler extends Handler {/** * @var int error code */ protected $code; /** * @var string error */ protected $message; protected $dontReport = [ ClientException::class ]; public function render($request, Exception $exception) { if ($exception instanceof ClientException) { $this->code = $exception->getCode(); $this->message = $exception->getMessage(); } else { if (env('APP_DEBUG')) { return parent::render($request, $exception); } $this->code = 500; $this->message = 'server error '; } return response()->json([ 'code' => $this->code, 'msg' => $this->message ]); } public function report(Exception $exception) { if ($this->shouldntReport($exception)) { return; } if (Reflector::isCallable($reportCallable = [$exception, 'report'])) { return $this->container->call($reportCallable); } try { $logger = $this->container->make(LoggerInterface::class); } catch (Exception $ex) { throw $exception; } $logger->error( $exception->getMessage()." at ".$exception->getFile().":".$exception->getLine() ); }}Copy the code

To explain the above modification, the exception classes of the dontReport attribute of Laravel will not be reported, because we do not need to record the client error information, so the exception classes added to the dontReport attribute will not be reported, because we do not need to record the client error information. Therefore, the exception classes added to the dontReport attribute will not be reported, because we do not need to record the client error information, so it is added to the dontReport attribute, and the render method divides the exception into two categories, one is client exception, and the other is server exception. We set the uniform server exception code to 500 and the error information to server error, and recorded the real error information in the error log to avoid exposing the server information to the client. Now let’s test the result of our override exception. If we want to return a client exception, such as no permission, such client exceptions are not recorded in the error log, and we don’t need to record them ourselves

Route::get('/', function () {throw new \App\Exceptions\ClientException(403, 'you don't have permission '); });Copy the code

For errors on the server side, such as missing semicolons, the client will only know that something is wrong with the server interface, but it will not know what the problem is

Route::get('/', function () {
    echo 'Hello World!'
});
Copy the code

However, the actual error information is recorded in the error log, and we can still use the error log to correct errors on our server

We can also include an alarm code in the Render method to send an email to the administrator if there is a server-side error. At this point, our rewrite of the Laravel exception handling class is complete. Hopefully, it will help you if you are planning to use Laravel for front and back end separation projects.