Exception handling of Laravel core analysis (code)

01-28-2024

The content of this article is about the exception handling (code) of Laravel core analysis, which has certain reference value. Friends who need it can refer to it and hope to help you. Exception handling is a very important but easily overlooked language feature in programming. It provides a mechanism for developers to handle runtime errors. For program design, correct exception handling can prevent the details of the program from being revealed to users, provide developers with a complete error backtracking stack, and at the same time improve the robustness of the program.

In this article, we will briefly sort out the exception handling ability provided by Laravel, and then talk about some practices of using exception handling in development, how to use custom exceptions, and how to expand Laravel's exception handling ability.

Register exception Handler

Here, we have to go back to the bootstrap stage before the Kernel processes the request. In the bootstrap stage, Laravel sets the system exception handling behavior and registers the global exception handler in the Illuminate \ Foundation \ bootstrap \ HandleExceptions section:

class HandleExceptions { 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 = []) { if (error_reporting() & $level) { throw new ErrorException($message, 0, $level, $file, $line); } } }

Set _ exception _ handler ([$ this,' handleException']) registers the handleexception method of HandleExceptions as the global processor method of the program:

public function handleException($e) { if (! $e instanceof Exception) { $e = new FatalThrowableError($e); } $this->getExceptionHandler()->report($e); if ($this->app->runningInConsole()) { $this->renderForConsole($e); } else { $this->renderHttpResponse($e); } } protected function getExceptionHandler() { return $this->app->make(ExceptionHandler::class); } //Render the abnormal response of CLI request. protected function renderForConsole(Exception $e) { $this->getExceptionHandler()->renderForConsole(new ConsoleOutput, $e); } //Render the abnormal response of HTTP request. protected function renderHttpResponse(Exception $e) { $this->getExceptionHandler()->render($this->app['request'], $e)->send(); }

In the processor, the exception is mainly reported through the report method of ExceptionHandler, here, the exception is recorded in the storage/laravel.log file, and then the response of the exception is rendered according to the request type and output to the client. The ExceptionHandler here is an example of the \App\Exceptions\Handler class, which was registered in the service container at the beginning of the project:

// bootstrap/app.php /* |-------------------------------------------------------------------------- | Create The Application |-------------------------------------------------------------------------- */ $app = new Illuminate\Foundation\Application( realpath(__DIR__.'/../') ); /* |-------------------------------------------------------------------------- | Bind Important Interfaces |-------------------------------------------------------------------------- */ ...... $app->singleton( Illuminate\Contracts\Debug\ExceptionHandler::class, App\Exceptions\Handler::class );

Here, by the way, the set_Error_handler function is used to register the error handler function, because in some old codes or class libraries, the trigger_error function of PHP is mostly used to throw errors, and the Exception handler can only handle exception but not error. Therefore, in order to be compatible with the old class libraries, we usually use set_error_handler to register the global error handler method, and after catching the error in the method, we will turn the error into an exception and throw it again, so that all the codes in the project can throw exception instances when they are not executed correctly.

/** * Convert PHP errors to ErrorException instances. * * @param int $level * @param string $message * @param string $file * @param int $line * @param array $context * @return void * * @throws \ErrorException */ public function handleError($level, $message, $file = '', $line = 0, $context = []) { if (error_reporting() & $level) { throw new ErrorException($message, 0, $level, $file, $line); } }

Commonly used Laravel exception examples

In Laravel, corresponding exception instances are thrown for common program exceptions, which enables developers to capture these runtime exceptions and do subsequent processing according to their own needs (for example, calling another remedy in catch, recording exceptions to log files, sending alarm emails and text messages).

Here I list some exceptions that are often encountered in development and explain under what circumstances they are thrown. In normal coding, we must pay attention to catching these exceptions in the program and handling them well to make the program more robust.

This exception will be thrown when there is an error in executing the SQL statement in Illuminate \ Database \ Queryexception Laravel. It is also the most frequently used exception, which is used to catch the SQL execution error. For example, when executing the Update statement, many people like to judge the number of rows modified after the execution of the SQL to judge whether the UPDATE is successful. However, in some cases, the UPDATE statement executed does not modify the record value. In this case, it is impossible to judge whether the UPDATE is successful by the modified function. In addition, if QueryException is captured in the transaction execution, the transaction can be rolled back in the catch code block.

Illuminate \ Database \ Eloquent \ ModelNotFoundException If a single record is obtained through the findOrFail and firstOrFail methods of the model, this exception will be thrown if it is not found (find and first will return NULL if they cannot find the data).

This exception is thrown when the Illuminate \ Validation \ ValidationException request fails the validation of Laravel's FormValidator.

This exception is thrown when the user request of Illuminate \ auth \ Access \ AuthorizationException fails Laravel's Policy verification.

HTTP Method is incorrect when symfony \ component \ routing \ exception \ methodnotallowedexception requests routing.

This exception is thrown when the processing of HTTP request by Illuminate \ http \ exceptions \ httpResponse Exception Laravel is unsuccessful.

Extend Laravel's exception handler

It is mentioned above that Laravel successfully registered \App\Exceptions\Handler as a global exception handler. Exceptions that are not caught in the code will eventually be caught by \App\Exceptions\Handler, and the handler will report the exception record to the log file, then render the exception response and send the response to the client. However, the method of its own exception handler is not easy to use. Many times, we want to report the exception to the email or error log system. The following example is to report the exception to the Sentry system, which is an error collection service that is very useful:

public function report(Exception $exception) { if (app()->bound('sentry') && $this->shouldReport($exception)) { app('sentry')->captureException($exception); } parent::report($exception); }

There is also a default rendering method. The JSON format of the response generated during form verification is often different from the unified JOSN format in our project, which requires us to customize the behavior of the rendering method.

public function render($request, Exception $exception) { //If the client expects a JSON response, after the API request fails the Validator verification, ValidationException is thrown. //Here you can customize the response returned to the client. if ($exception instanceof ValidationException && $request->expectsJson()) { return $this->error(422, $exception->errors()); } if ($exception instanceof ModelNotFoundException && $request->expectsJson()) { //Catch the NotFoundHttpException thrown after the routing model binding cannot find the model in the database. return $this->error(424, 'resource not found.'); } if ($exception instanceof AuthorizationException) { //Catch the AuthorizationException thrown when the permissions are not met. return $this->error(403, "Permission does not exist."); } return parent::render($request, $exception); }

After customization, a ValidationException will be thrown when the request fails to pass the validation of FormValidator. After the exception handler catches the exception, it will format the error prompt into a unified JSON response format for the project and output it to the client. In this way, the logic of judging whether the form verification passes or not is completely omitted in our controller. Giving this logic to a unified exception handler for execution can make the controller method slim down a lot.

Use custom exceptions

In fact, this part is not about custom exceptions for Laravel framework, and the custom exceptions I mentioned here can be applied in any project.

I have seen many people return different arrays according to different errors in the methods of Repository or Service class, which contains the response error code and error information. This can certainly meet the development needs, but it can't record the runtime context of the application when an exception occurs. It is very unfavorable for developers to locate the problem if they can't record the context information when an error occurs.

The following is a custom exception class.

namespace App\Exceptions\; use RuntimeException; use Throwable; class UserManageException extends RuntimeException { /** * The primitive arguments that triggered this exception * * @var array */ public $primitives; /** * QueueManageException constructor. * @param array $primitives * @param string $message * @param int $code * @param Throwable|null $previous */ public function __construct(array $primitives, $message = "", $code = 0, Throwable $previous = null) { parent::__construct($message, $code, $previous); $this->primitives = $primitives; } /** * get the primitive arguments that triggered this exception */ public function getPrimitives() { return $this->primitives; } }

After defining the exception class, we can throw exception instances in the code logic.

class UserRepository { public function updateUserFavorites(User $user, $favoriteData) { ...... if (! $executionOne) { throw new UserManageException(func_get_args(), 'Update user favorites error', '501'); } ...... if (! $executionTwo) { throw new UserManageException(func_get_args(), 'Another Error', '502'); } return true; } } class UserController extends ... { public function updateFavorites(User $user, Request $request) { ....... $favoriteData = $request->input('favorites'); try { $this->userRepo->updateUserFavorites($user, $favoritesData); } catch (UserManageException $ex) { ....... } } }

In addition to the situations listed in the Repository above, we often throw more detailed exception examples related to the business in the catch code block after capturing the general exceptions listed above, which is convenient for developers to locate the problem. We will modify the updateUserFavorites above according to this strategy.

public function updateUserFavorites(User $user, $favoriteData) { try { // database execution // database execution } catch (QueryException $queryException) { throw new UserManageException(func_get_args(), 'Error Message', '501' , $queryException); } return true; }

When defining the UserMangeException class above, the fourth parameter $previous is an instance that implements the Throwable interface class. In this scenario, we throw an instance of UserManagerException because we have captured the exception instance of QueryException. Then pass the QueryException instance to the PHP exception stack through this parameter, which provides us with the ability to trace back the whole exception to get more context information, not just the context information of the currently thrown exception instance. In the error collection system, we can use the code similar to the following to get all exception information.

while($e instanceof \Exception) { echo $e->getMessage(); $e = $e->getPrevious(); }

Exception handling is a very important function of PHP, but it is easy for developers to ignore it. This article simply explains the mechanism of Laravel's internal exception handling and the ways to extend Laravel's exception handling. More space focuses on sharing some programming practices of exception handling, which are just some programming habits that I hope every reader can understand and practice, including the application of Interface shared before.

Copyright Description:No reproduction without permission。

Knowledge sharing community for developers。

Let more developers benefit from it。

Help developers share knowledge through the Internet。

Follow us