laravel 11 如何更优雅的处理异常

想能兼容web和api两种、web返回常规页面错误,api就返回json格式信息

《L05 电商实战》
从零开发一个电商项目,功能包括电商后台、商品 & SKU 管理、购物车、订单管理、支付宝支付、微信支付、订单退款流程、优惠券等
《G01 Go 实战入门》
从零开始带你一步步开发一个 Go 博客项目,让你在最短的时间内学会使用 Go 进行编码。项目结构很大程度上参考了 Laravel。
最佳答案
<?php

use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        //
    })
    ->withExceptions(function (Exceptions $exceptions) {
        if (request()->expectsJson()) {
            $exceptions->render(function (Exception $e) {
                $msg = $e->getMessage().' '.$e->getFile().' '.$e->getLine();
                $state = -2;
                $code = 500;

                switch (true) {
                    case $e instanceof ValidationException:
                        $msg = $e->getMessage();
                        $code = 400;
                        break;
                    case $e instanceof AuthenticationException:
                        $msg = 'Not authenticated (login required)';
                        $state = $code = 401;
                        break;
                    case $e instanceof ModelNotFoundException:
                    case $e instanceof NotFoundHttpException:
                        $msg = '404(NOT FOUND)';
                        $state = $code = 404;
                        break;
                    case $e instanceof MethodNotAllowedHttpException:
                        $msg = '405(Method Not Allowed)';
                        $state = $code = 405;
                        break;
                    case $e instanceof UnauthorizedHttpException:
                        $msg = 'Verification failed';
                        $state = $code = 422;
                        break;
                    case $e instanceof HttpException:
                        $msg = $e->getMessage();
                        switch ($e->getStatusCode()) {
                            case 401:
                                $state = $code = 401;
                                break;
                            default:
                                $code = $e->getStatusCode();
                                break;
                        }
                }

                return responseHelper(false, $msg, $state, $code);
            });
        }
    })->create();
1个月前 评论
Talentisan (作者) (楼主) 1个月前
讨论数量: 34

这就开始投入项目了吗

1个月前 评论
Talentisan (楼主) 1个月前
ShiKi

文档中的错误处理

1个月前 评论
Talentisan (楼主) 1个月前
Mutoulee
1个月前 评论
Talentisan (楼主) 1个月前

基本就是替换默认的异常处理类,判断是api还是web吧。重新定义这个 github.com/deatil/larke-admin/blob...

1个月前 评论
Talentisan (楼主) 1个月前
Talentisan (楼主) 1个月前
  1. 通过 make:exception 命令创建自定义异常

    $ php artisan make:exception InvalidRequestException
  2. 定义 render 方法

    <?php
    namespace App\Exceptions;
    use Exception;
    use Illuminate\Http\Request;
    class InvalidRequestException extends Exception
    {
     // 自定义错误码
     protected $errorCode;
     public function __construct(string $message = "", int $errorCode = 0, int $code = 400)
     {
         $this->errorCode = $errorCode;
         parent::__construct($message, $code);
     }
    
     public function render(Request $request)
     {
         $data = ['message' => $this->message, 'error_code' => $this->errorCode];
         // 返回 json 格式信息
         if ($request->expectsJson()) {
             return response()->json($data, $this->code);
         }
         // 返回错误页面
         return view('pages.error', $data);
     }
    }
1个月前 评论
Talentisan (楼主) 1个月前

自带 ajax 请求识别呢

添加一个请求头就行了

var xhr = new XMLHttpRequest();
xhr.open("GET", "ta", true);
xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');

xhr.onreadystatechange = function() {
  if (xhr.readyState == 4 && xhr.status == 200) {
    // 请求成功
    var response = JSON.parse(xhr.responseText);
    console.log(response);
  }
};

xhr.send();

file

1个月前 评论
Talentisan (楼主) 1个月前
kis龍 (作者) 1个月前
Talentisan (楼主) 1个月前

错误处理《Laravel 10 中文文档》 此处接管异常 实现方法很多,比如通过 $request->is('api/*') 判断是不是api模块,如果是就自定义错误响应

1个月前 评论
Talentisan (楼主) 1个月前

1、创建中间件 ForceJsonResponse

handle() 方法中添加:

$request->headers->set('Accept', 'application/json');

2、在 bootstrap/app.php 中使用中间件

->withMiddleware(function (Middleware $middleware) {
    $middleware->prependToGroup('api', [
        ForceJsonResponse::class
    ]);
})
1个月前 评论
Talentisan (楼主) 1个月前
sanders

我是在 app/Exceptions/Handler.php 里面定义, $request->wantsJson() 用来获取是否要返回 JSON 格式数据。

<?php

namespace App\Exceptions;

use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Illuminate\Http\Request;
use Illuminate\Validation\ValidationException;
use InvalidArgumentException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Throwable;

class Handler extends ExceptionHandler
{
    /**
     * The list of the inputs that are never flashed to the session on validation exceptions.
     *
     * @var array<int, string>
     */
    protected $dontFlash = [
        'current_password',
        'password',
        'password_confirmation',
    ];

    /**
     * Register the exception handling callbacks for the application.
     */
    public function register(): void
    {
        // 数据不存在异常
        $this->renderable(function (ModelNotFoundException $e, ?Request $request = null) {
            if ($request && $request->wantsJson()) {
                $message = __(
                    'messages.model_not_found',
                    [
                        'model' => __('messages.model.'.$e->getModel()),
                    ]
                );

                return response()->json([
                    'code' => $e->getCode(),
                    'message' => $message,
                    'model' => $e->getModel(),
                    'ids' => $e->getIds(),
                ], 404);
            }

            return response()->view('errors.404', [], 404);
        });
// ...
1个月前 评论
Talentisan (楼主) 1个月前

file

if ($request->is('api/*')) {

}
1个月前 评论
Talentisan (楼主) 1个月前
WHOAMI_ (作者) 1个月前

封装个异常处理类,这样就不用频繁修改app.php了,例如

<?php

use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        commands: __DIR__.'/../routes/console.php',
    )
    ->withMiddleware(function (Middleware $middleware) {
        //
    })
    ->withExceptions(function (Exceptions $exceptions) {
        (new \App\Exceptions\Handler($exceptions))->handle();
    })->create();

然后在Hanler类里面处理,可以参考下:

<?php

namespace App\Exceptions;

use App\Helpers\ApiHelper;
use Illuminate\Auth\AuthenticationException;
use Illuminate\Contracts\Http\Kernel as HttpKernel;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Http\Request;
use Illuminate\Support\Arr;
use Illuminate\Support\Traits\ForwardsCalls;
use Illuminate\Support\Facades\App;
use Illuminate\Support\Facades\Auth;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpKernel\Exception\HttpExceptionInterface;
use Throwable;

class Handler
{
    use ForwardsCalls;

    protected $dontReport = [
        XXXX::class,
    ];

    public function __construct(public Exceptions $exceptions) {}

    public function handle() {

        // 不需要报告的异常
        $this->dontReport($this->dontReport);

        // 异常附带context
        $this->context(fn (Throwable $e, array $context) => $this->withContext($e, $context));

        // 是否需要响应Json
        $this->shouldRenderJsonWhen(fn(Request $request, Throwable $e) => $this->shouldReturnJson($request, $e));

        // 处理异常响应
        $this->respond(function(Response $response, Throwable $e, Request $request) {
            if ( !$this->shouldReturnJson($request, $e) ) {
                return $response;
            }

            $data = ['xxxxxx'];

            $apiResponse = match(true) {
                $e instanceof AuthenticationException => ApiHelper::failed(message: $e->getMessage(), code: 401, extends: $data),
                $e instanceof HttpExceptionInterface => ApiHelper::failed(message: $e->getMessage(), code: $e->getStatusCode(), extends: $data),
                $e instanceof ValidationException => ApiHelper::validation(messages: $e->errors(), message: $e->getMessage(), extends: $data),
                // APP定义异常
                $e instanceof AppException => ApiHelper::failed($e->getMessage(), code: $e->getCode(), extends: $data),
                default => ApiHelper::error(message: 'Server Error', e: $e, code: 500),
            };

            return $apiResponse;
        });
    }

    protected function shouldReturnJson(Request $request, Throwable $e)
    {
        if ($request->is('api/*')) {
            return true;
        }
        return $request->expectsJson();
    }

    /**
     * 附带日志信息
     * @param  Throwable $e       异常
     * @param  array     $context 异常附带的信息
     * @return [type]             [description]
     */
    protected function withContext(Throwable $e, array $context): array
    {
        $request = app('request');

        try {
            return !App::runningInConsole() || isset($_SERVER['LARAVEL_OCTANE']) ? [
                'userId' => Auth::check() ? Auth::id() : null,
                'ip' => $request->ip(),
                'requestId' => $request->attributes->get('request_id'),
                'requestUrl' => $request->fullUrl(),
                'requestMethod' => $request->method(),
            ] : [];
        } catch (\Throwable $e) {
            return [];
        }
    }

    public function __call(string $method, array $parameters)
    {
        return $this->forwardCallTo($this->exceptions, $method, $parameters);
    }
}
1个月前 评论
Talentisan (楼主) 1个月前

我这安装11报错了,是要更新compsoer的源么

composer create-project laravel/laravel:^11.0 lara11

Creating a "laravel/laravel:^11.0" project at "./lara11" Installing laravel/laravel (v11.0.3)

  • Installing laravel/laravel (v11.0.3): Extracting archive Created project in /var/www/laravels/lara11

    @php -r "file_exists('.env') || copy('.env.example', '.env');" Loading composer repositories with package information Updating dependencies Your requirements could not be resolved to an installable set of packages.

    Problem 1

    • laravel/framework[v11.0.0, ..., v11.0.8] require fruitcake/php-cors ^1.3 -> found fruitcake/php-cors[dev-feat-setOptions, dev-master, dev-main, dev-test-8.2, v0.1.0, v0.1.1, v0.1.2, v1.0-alpha1, ..., 1.2.x-dev (alias of dev-master)] but it does not match the constraint.
    • Root composer.json requires laravel/framework ^11.0 -> satisfiable by laravel/framework[v11.0.0, ..., v11.0.8].
1个月前 评论
ononl 1个月前
Talentisan (楼主) 1个月前
aliongkk (作者) 1个月前
aliongkk (作者) 1个月前

请求头不是有类似 accept: application/json 这种么?Laravel 会自动判断响应格式呀? 另,为什么要把异常丢给浏览器?浏览器该接收的应该是数据不是异常呀。

1个月前 评论
<?php

use Illuminate\Auth\AuthenticationException;
use Illuminate\Database\Eloquent\ModelNotFoundException;
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Symfony\Component\HttpKernel\Exception\MethodNotAllowedHttpException;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;
use Symfony\Component\HttpKernel\Exception\UnauthorizedHttpException;

return Application::configure(basePath: dirname(__DIR__))
    ->withRouting(
        web: __DIR__.'/../routes/web.php',
        api: __DIR__.'/../routes/api.php',
        commands: __DIR__.'/../routes/console.php',
        health: '/up',
    )
    ->withMiddleware(function (Middleware $middleware) {
        //
    })
    ->withExceptions(function (Exceptions $exceptions) {
        if (request()->expectsJson()) {
            $exceptions->render(function (Exception $e) {
                $msg = $e->getMessage().' '.$e->getFile().' '.$e->getLine();
                $state = -2;
                $code = 500;

                switch (true) {
                    case $e instanceof ValidationException:
                        $msg = $e->getMessage();
                        $code = 400;
                        break;
                    case $e instanceof AuthenticationException:
                        $msg = 'Not authenticated (login required)';
                        $state = $code = 401;
                        break;
                    case $e instanceof ModelNotFoundException:
                    case $e instanceof NotFoundHttpException:
                        $msg = '404(NOT FOUND)';
                        $state = $code = 404;
                        break;
                    case $e instanceof MethodNotAllowedHttpException:
                        $msg = '405(Method Not Allowed)';
                        $state = $code = 405;
                        break;
                    case $e instanceof UnauthorizedHttpException:
                        $msg = 'Verification failed';
                        $state = $code = 422;
                        break;
                    case $e instanceof HttpException:
                        $msg = $e->getMessage();
                        switch ($e->getStatusCode()) {
                            case 401:
                                $state = $code = 401;
                                break;
                            default:
                                $code = $e->getStatusCode();
                                break;
                        }
                }

                return responseHelper(false, $msg, $state, $code);
            });
        }
    })->create();
1个月前 评论
Talentisan (作者) (楼主) 1个月前

讨论应以学习和精进为目的。请勿发布不友善或者负能量的内容,与人为善,比聪明更重要!