ホームページ  >  記事  >  PHPフレームワーク  >  Laravel コア分析の例外処理 (コード)

Laravel コア分析の例外処理 (コード)

不言
不言転載
2019-02-11 10:22:422905ブラウズ

この記事の内容は、Laravel コア解析の例外処理 (コード) に関するものです。必要な方は参考にしていただければ幸いです。

例外処理は、プログラムの実行時エラーを処理するメカニズムを開発者に提供する非常に重要ですが、プログラム自体の詳細は次のとおりです。ユーザーに提供され、開発者に完全なエラー トレースバック スタックを提供すると同時に、プログラムの堅牢性も向上します。

この記事では、Laravel で提供される例外処理機能を簡単に確認し、その後、開発での例外処理の使用方法、カスタム例外の使用方法、Laravel の例外処理機能を拡張する方法について説明します。

例外ハンドラーの登録

ここでは、ブートストラップ ステージで何度も述べたように、カーネルがリクエストを処理する前にブートストラップ ステージに戻る必要があります。 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 のレポート メソッドを通じて報告されます。ここでは、例外は 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 関数を使用して登録します。エラー ハンドラー。一部の古いコードやクラス ライブラリでは、例外ハンドラーは例外のみを処理でき、エラーは処理できないため、通常は 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 ステートメントはレコード値を変更しません。また、トランザクションの実行中に QueryException がキャッチされた場合、トランザクションはキャッチ コード ブロックでロールバックされる可能性があります。

Illuminate\Database\Eloquent\ModelNotFoundException この例外は、モデルの findOrFail メソッドと firstOrFail メソッドで 1 つのレコードが見つからない場合にスローされます (データが見つからない場合、find と first は NULL を返します)。

Illuminate\Validation\ValidationException この例外は、リクエストが Laravel の FormValidator 検証に合格しない場合にスローされます。

Illuminate\Auth\Access\AuthorizationException この例外は、ユーザーリクエストが Laravel のポリシー (ポリシー) 検証に合格しない場合にスローされます。

Symfony\Component\Routing\Exception\MethodNotAllowedException ルーティングをリクエストするとき、 HTTP メソッドが正しくありません

Illuminate\Http\Exceptions\HttpResponseException Laravel は、HTTP リクエストの処理が失敗したときにこの例外をスローします

Laravel の例外ハンドラーを拡張します

# 前述したように、Laravel は \App\Exceptions\Handler をグローバル例外ハンドラーとして正常に登録しました。コード内でキャッチされなかった例外は、最終的には \App\Exceptions\Handler によってキャッチされます。次に、例外応答をレンダリングし、クライアントに応答を送信します。ただし、組み込みの例外ハンドラー メソッドは使いにくいことがよくあります。次の例では、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 中国語 Web サイトの他の関連記事を参照してください。

声明:
この記事はsegmentfault.comで複製されています。侵害がある場合は、admin@php.cn までご連絡ください。