首頁 php框架 Laravel Laravel核心分析之異常處理(程式碼)

Laravel核心分析之異常處理(程式碼)

Feb 11, 2019 am 10:22 AM
laravel php

這篇文章帶給大家的內容是關於Laravel核心分析之異常處理(程式碼),有一定的參考價值,有需要的朋友可以參考一下,希望對你有幫助。

異常處理是程式設計中十分重要但也最容易被忽視的語言特性,它為開發者提供了處理程序運行時錯誤的機制,對於程式設計來說正確的異常處理能夠防止洩露程式本身細節給用戶,提供開發者完整的錯誤回溯堆疊,同時也能提高程式的健全性。

這篇文章我們來簡單梳理一下Laravel中提供的異常處理能力,然後講一些在開發中使用異常處理的實踐,如何使用自訂異常、如何擴展Laravel的異常處理能力。

註冊異常Handler

這裡又要回到我們說過很多次的Kernel處理請求前的bootstrap階段,在bootstrap階段的Illuminate\Foundation\Bootstrap\ HandleExceptions 部分中Laravel設定了系統異常處理行為並註冊了全域的例外處理器:

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'])將HandleExceptions的handleException方法註冊為程式的全域處理器方法:

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);
}

// 渲染CLI请求的异常响应
protected function renderForConsole(Exception $e)
{
    $this->getExceptionHandler()->renderForConsole(new ConsoleOutput, $e);
}

// 渲染HTTP请求的异常响应
protected function renderHttpResponse(Exception $e)
{
    $this->getExceptionHandler()->render($this->app['request'], $e)->send();
}
登入後複製

在處理器裡主要透過ExceptionHandler的report方法上報異常、這裡是記錄異常到storage/laravel.log檔案中,然後根據請求類型渲染異常的回應產生輸出給客戶端。這裡的ExceptionHandler就是\App\Exceptions\Handler類別的實例,它是在專案最開始註冊到服務容器中的:

// 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
);
登入後複製

這裡再順便說一下set_error_handler函數,它的作用是註冊錯誤處理器函數,因為在一些年代久遠的程式碼或類別庫中大多是採用PHP那件函數trigger_error函數來拋出錯誤的,異常處理器只能處理Exception不能處理Error,所以為了能夠相容於老類別庫通常都會使用set_error_handler註冊全域的錯誤處理器方法,在方法中捕獲到錯誤後將錯誤轉換成異常再重新拋出,這樣專案中所有的程式碼沒有被正確執行時都能拋出異常實例了。

/**
 * 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);
    }
}
登入後複製

常用的Laravel例外實例

Laravel中針對常見的程式例外情況拋出了對應的例外實例,這讓開發者能夠捕捉這些執行時期異常並根據自己的需求來做後續處理(例如:在catch中呼叫另外一個補救方法、記錄異常到日誌檔案、發送警報郵件、簡訊)

在這裡我列一些開發中常遇到異常,並說明他們是在什麼情況下被拋出的,平時編碼中一定要注意在程式裡捕捉這些異常做好異常處理才能讓程式更健壯。

Illuminate\Database\QueryException Laravel中執行SQL語句發生錯誤時會拋出此異常,它也是使用率最高的異常,用來捕獲SQL執行錯誤,比方執行Update語句時很多人喜歡判斷SQL執行後判斷被修改的行數來判斷UPDATE是否成功,但有的情景裡執行的UPDATE語句並沒有修改記錄值,這種情況就沒法通過被修改函數來判斷UPDATE是否成功了,另外在事務執行中如果捕獲到QueryException 可以在catch程式碼區塊中回滾事務。

Illuminate\Database\Eloquent\ModelNotFoundException 透過模型的findOrFail和firstOrFail方法取得單一記錄時如果沒有找到會拋出這個例外(find和first找不到資料時會傳回NULL)。

Illuminate\Validation\ValidationException 請求未通過Laravel的FormValidator驗證時會拋出此例外。

Illuminate\Auth\Access\AuthorizationException  使用者要求未透過Laravel的政策(Policy)驗證時拋出此例外

Symfony\Component\Routing\Exception\MethodNotAllowedException 正確

Illuminate\Http\Exceptions\HttpResponseException  Laravel的處理HTTP請求不成功時拋出此異常

#擴展Laravel的異常處理器

#上面說了Laravel把\App\Exceptions\Handler 註冊成功了全域的異常處理器,程式碼中沒有被catch到的異常,最後都會被\App\Exceptions\Handler捕獲到,處理器先上報異常記錄到日誌文件裡然後渲染異常響應再發送回應給客戶端。但是自帶的異常處理器的方法並不好用,很多時候我們想把異常上報到郵件或者是錯誤日誌系統中,下面的例子是將異常上報到Sentry系統中,Sentry是一個錯誤收集服務非常好用:

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

    parent::report($exception);
}
登入後複製

還有預設的渲染方法在表單驗證時產生回應的JSON格式往往跟我們專案裡統一的JOSN格式不一樣這就需要我們自訂渲染方法的行為。

public function render($request, Exception $exception)
{
    //如果客户端预期的是JSON响应,  在API请求未通过Validator验证抛出ValidationException后
    //这里来定制返回给客户端的响应.
    if ($exception instanceof ValidationException && $request->expectsJson()) {
        return $this->error(422, $exception->errors());
    }

    if ($exception instanceof ModelNotFoundException && $request->expectsJson()) {
        //捕获路由模型绑定在数据库中找不到模型后抛出的NotFoundHttpException
        return $this->error(424, 'resource not found.');
    }


    if ($exception instanceof AuthorizationException) {
        //捕获不符合权限时抛出的 AuthorizationException
        return $this->error(403, "Permission does not exist.");
    }

    return parent::render($request, $exception);
}
登入後複製

自定义后,在请求未通过FormValidator验证时会抛出ValidationException, 之后异常处理器捕获到异常后会把错误提示格式化为项目统一的JSON响应格式并输出给客户端。这样在我们的控制器中就完全省略了判断表单验证是否通过如果不通过再输出错误响应给客户端的逻辑了,将这部分逻辑交给了统一的异常处理器来执行能让控制器方法瘦身不少。

使用自定义异常

这部分内容其实不是针对Laravel框架自定义异常,在任何项目中都可以应用我这里说的自定义异常。

我见过很多人在Repository或者Service类的方法中会根据不同错误返回不同的数组,里面包含着响应的错误码和错误信息,这么做当然是可以满足开发需求的,但是并不能记录发生异常时的应用的运行时上下文,发生错误时没办法记录到上下文信息就非常不利于开发者进行问题定位。

下面的是一个自定义的异常类

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;
    }
}
登入後複製

定义完异常类我们就能在代码逻辑中抛出异常实例了

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) {
            .......
        }
    }
}
登入後複製

除了上面Repository列出的情况更多的时候我们是在捕获到上面列举的通用异常后在catch代码块中抛出与业务相关的更细化的异常实例方便开发者定位问题,我们将上面的updateUserFavorites 按照这种策略修改一下

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;
}
登入後複製

在上面定义UserMangeException类的时候第四个参数$previous是一个实现了Throwable接口类实例,在这种情景下我们因为捕获到了QueryException的异常实例而抛出了UserManagerException的实例,然后通过这个参数将QueryException实例传递给PHP异常的堆栈,这提供给我们回溯整个异常的能力来获取更多上下文信息,而不是仅仅只是当前抛出的异常实例的上下文信息, 在错误收集系统可以使用类似下面的代码来获取所有异常的信息。

while($e instanceof \Exception) {
    echo $e->getMessage();
    $e = $e->getPrevious();
}
登入後複製

异常处理是PHP非常重要但又容易让开发者忽略的功能,这篇文章简单解释了Laravel内部异常处理的机制以及扩展Laravel异常处理的方式方法。更多的篇幅着重分享了一些异常处理的编程实践,这些正是我希望每个读者都能看明白并实践下去的一些编程习惯,包括之前分享的Interface的应用也是一样。

以上是Laravel核心分析之異常處理(程式碼)的詳細內容。更多資訊請關注PHP中文網其他相關文章!

本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1673
14
CakePHP 教程
1429
52
Laravel 教程
1333
25
PHP教程
1278
29
C# 教程
1257
24
如果session_start()被多次調用會發生什麼? 如果session_start()被多次調用會發生什麼? Apr 25, 2025 am 12:06 AM

多次調用session_start()會導致警告信息和可能的數據覆蓋。 1)PHP會發出警告,提示session已啟動。 2)可能導致session數據意外覆蓋。 3)使用session_status()檢查session狀態,避免重複調用。

作曲家:通過AI的幫助開發PHP 作曲家:通過AI的幫助開發PHP Apr 29, 2025 am 12:27 AM

AI可以幫助優化Composer的使用,具體方法包括:1.依賴管理優化:AI分析依賴關係,建議最佳版本組合,減少衝突。 2.自動化代碼生成:AI生成符合最佳實踐的composer.json文件。 3.代碼質量提升:AI檢測潛在問題,提供優化建議,提高代碼質量。這些方法通過機器學習和自然語言處理技術實現,幫助開發者提高效率和代碼質量。

session_start()函數的意義是什麼? session_start()函數的意義是什麼? May 03, 2025 am 12:18 AM

session_start()iscucialinphpformanagingusersessions.1)ItInitiateSanewsessionifnoneexists,2)resumesanexistingsessions,and3)setsasesessionCookieforContinuityActinuityAccontinuityAcconActInityAcconActInityAcconAccRequests,EnablingApplicationsApplicationsLikeUseAppericationLikeUseAthenticationalticationaltication and PersersonalizedContentent。

php框架laravel和yii區別是什麼 php框架laravel和yii區別是什麼 Apr 30, 2025 pm 02:24 PM

Laravel和Yii的主要區別在於設計理念、功能特性和使用場景。 1.Laravel注重開發的簡潔和愉悅,提供豐富的功能如EloquentORM和Artisan工具,適合快速開發和初學者。 2.Yii強調性能和效率,適用於高負載應用,提供高效的ActiveRecord和緩存系統,但學習曲線較陡。

哪些數據庫版本與最新的Laravel兼容? 哪些數據庫版本與最新的Laravel兼容? Apr 25, 2025 am 12:25 AM

最新版本的Laravel10與MySQL5.7及以上、PostgreSQL9.6及以上、SQLite3.8.8及以上、SQLServer2017及以上兼容。這些版本選擇是因為它們支持Laravel的ORM功能,如MySQL5.7的JSON數據類型,提升了查詢和存儲效率。

H5:HTML5的關鍵改進 H5:HTML5的關鍵改進 Apr 28, 2025 am 12:26 AM

HTML5帶來了五個關鍵改進:1.語義化標籤提升了代碼清晰度和SEO效果;2.多媒體支持簡化了視頻和音頻嵌入;3.表單增強簡化了驗證;4.離線與本地存儲提高了用戶體驗;5.畫布與圖形功能增強了網頁的可視化效果。

如何使用MySQL的函數進行數據處理和計算 如何使用MySQL的函數進行數據處理和計算 Apr 29, 2025 pm 04:21 PM

MySQL函數可用於數據處理和計算。 1.基本用法包括字符串處理、日期計算和數學運算。 2.高級用法涉及結合多個函數實現複雜操作。 3.性能優化需避免在WHERE子句中使用函數,並使用GROUPBY和臨時表。

Laravel 最佳擴展包推薦:2024 年必備工具 Laravel 最佳擴展包推薦:2024 年必備工具 Apr 30, 2025 pm 02:18 PM

2024年必備的Laravel擴展包包括:1.LaravelDebugbar,用於監控和調試代碼;2.LaravelTelescope,提供詳細的應用監控;3.LaravelHorizon,管理Redis隊列任務。這些擴展包能提升開發效率和應用性能。

See all articles