

通过
make:exception
命令创建自定义异常$ php artisan make:exception InvalidRequestException
定义
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); } }
自带 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();
我是在 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);
});
// ...
封装个异常处理类,这样就不用频繁修改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);
}
}
我这安装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].
<?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();
我是这么写的,没有使用系统自定义的422状态,而是全部返回200状态码。
<?php
use Illuminate\Support\Arr;
use Illuminate\Foundation\Application;
use Illuminate\Validation\ValidationException;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
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) {
$middleware->validateCsrfTokens([
'*'
]);
})
->withExceptions(function (Exceptions $exceptions) {
// 返回自定义错误
$exceptions->renderable(function (ValidationException $exception) {
return response_error([], Arr::first($exception->errors())[0]);
});
})->create();
主要还是看你对api的定义,比如get请求就是web页面。非get就是api 那就简单了,直接验证类型返回就行,如果是混合的只能把get请求做请求方式验证,如果ajax或其它特别的定义则响应为api的json,否则就认为是web 你想要的主要目的就是自动识别是 web与api 至于响应处理与规范那都是简单的
验证是否json响应laravel自带有在集合你有啥自定义的规则
官方的参考json响应验证:
Laravel 11x的将中间件和异常放在了bootstrap/app.php下,这使得更加方便,而不必像以前一样要到kernel和providers文件里搞东搞西,关于全局异常处理其实跟以前有点类似,下面是bootstrap/app.php:
<?php
use Illuminate\Foundation\Application;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Foundation\Configuration\Middleware;
use Illuminate\Support\Facades\Route;
return Application::configure(basePath: dirname(__DIR__))
->withRouting(
web: __DIR__.'/../routes/web.php',
commands: __DIR__.'/../routes/console.php',
health: '/up',
then: function () {
Route::middleware('admin')
->prefix(config('admin.admin_base_path','admin'))
->group(base_path('routes/admin.php'));
}
)
->withMiddleware(function (Middleware $middleware) {
//中间件
$middleware->appendToGroup('admin', [
\Illuminate\Session\Middleware\StartSession::class,
\Illuminate\Cookie\Middleware\EncryptCookies::class,
\Illuminate\Foundation\Http\Middleware\VerifyCsrfToken::class,
\App\Http\Middleware\PermissionAuthMiddleware::class,
]);
})
->withExceptions(function (Exceptions $exceptions) {
//全局异常统一处理
(new \App\Exceptions\Handler($exceptions))->handle();
})->create();
app/Exceptions/Handler.php
代码:
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Configuration\Exceptions;
use Illuminate\Support\Facades\Log;
use Illuminate\Validation\ValidationException;
use Symfony\Component\HttpKernel\Exception\HttpException;
use Throwable;
class Handler
{
/**
* A list of the exception types that are not reported.
*不做日志记录的异常错误
* @var array
*/
protected $dontReport = [
ValidationException::class,
ApiException::class
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*认证异常时不被flashed的数据
* @var array
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
public function __construct(public Exceptions $excepHandler)
{
}
public function handle()
{
$this->excepHandler->dontReport($this->dontReport);
$this->excepHandler->dontFlash($this->dontFlash);
// 异常处理
$this->excepHandler->render(function (Throwable $e) {
//验证器异常统一处理
$msg = '';
$statusCode = 200;
switch (true) {
case $e instanceof ValidationException: //验证器类型异常
$msg = array_values($e->errors())[0][0];
break;
case $e instanceof HttpException:
// 给客户端返回自定义的错误信息,同时将具体错误记录日志
// 具体报错信息开发人员可到laravel.log中查看
Log::error($e->getMessage());
$statusCode = $e->getStatusCode() ? (string)$e->getStatusCode() : '0';
$msg = Status::getReasonPhrase($statusCode);
break;
}
//api返回json,web返回页面
if (request()->ajax()) {
$return = [
'msg' => $msg
];
return response()->json($return,$statusCode);
}else{
return response()->view('errors.error',['errCode'=>$statusCode,'msg'=>$msg]);
}
});
}
}
接上面的内容,
由于HttpException返回的信息都是英文,我做了一个http状态码对应信息的对应文件app/Exceptions/Status.php
:
<?php
namespace App\Exceptions;
class Status
{
// Informational 1xx
const CODE_CONTINUE = 100;
const CODE_SWITCHING_PROTOCOLS = 101;
// Success 2xx
const CODE_OK = 200;
const CODE_CREATED = 210;
const CODE_ACCEPTED = 202;
const CODE_NON_AUTHORITATIVE_INFORMATION = 203;
const CODE_NO_CONTENT = 204;
const CODE_RESET_CONTENT = 205;
const CODE_PARTIAL_CONTENT = 206;
// Redirection 3xx
const CODE_MULTIPLE_CHOICES = 300;
const CODE_MOVED_PERMANENTLY = 301;
const CODE_MOVED_TEMPORARILY = 302;
const CODE_SEE_OTHER = 303;
const CODE_NOT_MODIFIED = 304;
const CODE_USE_PROXY = 305;
const CODE_TEMPORARY_REDIRECT = 307;
// Client Error 4xx
const CODE_BAD_REQUEST = 400;
const CODE_UNAUTHORIZED = 401;
const CODE_PAYMENT_REQUIRED = 402;
const CODE_FORBIDDEN = 403;
const CODE_NOT_FOUND = 404;
const CODE_METHOD_NOT_ALLOWED = 405;
const CODE_NOT_ACCEPTABLE = 406;
const CODE_PROXY_AUTHENTICATION_REQUIRED = 407;
const CODE_REQUEST_TIMEOUT = 408;
const CODE_CONFLICT = 409;
const CODE_GONE = 410;
const CODE_LENGTH_REQUIRED = 411;
const CODE_PRECONDITION_FAILED = 412;
const CODE_REQUIRED_ENTITY_TOO_LARGE = 413;
const CODE_REQUEST_URI_TOO_LONG = 414;
const CODE_UNSUPPORTED_MEDIA_TYPE = 415;
const CODE_REQUESTED_RANGE_NOT_SATISFIABLE = 416;
const CODE_EXPECTATION_FAILED = 415;
const CODE_UNPROCESSABLE_ENTITY = 422;
// Server Error 5xx
const CODE_INTERNAL_SERVER_ERROR = 500;
const CODE_NOT_IMPLEMENTED = 501;
const CODE_BAD_GATEWAY = 502;
const CODE_SERVICE_UNAVAILABLE = 503;
const CODE_GATEWAY_TIMEOUT = 504;
const CODE_HTTP_VERSION_NOT_SUPPORTED = 505;
const CODE_BANDWIDTH_LIMIT_EXCEEDED = 509;
private static $phrases = [
'0' => '未知错误',
'100' => '继续请求',
'101' => '切换协议',
'102' => '处理中',
'200' => '请求成功',
'201' => '已创建',
'202' => '已接受',
'203' => '非权威信息',
'204' => '无内容',
'205' => '重置内容',
'206' => '部分内容',
'207' => '多状态',
'208' => '已上报',
'226' => 'IM已使用',
'300' => '多种选择',
'301' => '已永久移动',
'302' => '临时移动',
'303' => '见其他',
'304' => '未修改',
'305' => '使用代理',
'307' => '临时重定向',
'308' => '永久重定向',
'400' => '请求错误',
'401' => '未授权',
'402' => '需要付款',
'403' => '禁止',
'404' => 'URL地址错误',
'405' => '请求方法不允许',
'406' => '无法接受',
'407' => '需要代理验证',
'408' => '请求超时',
'409' => '冲突',
'410' => '不可用',
'411' => '长度要求',
'412' => '前提条件未满足',
'413' => '请求实体过大',
'414' => 'URI太长了',
'415' => '不支持的媒体类型',
'416' => '请求范围不符合',
'417' => '期望不满足',
'418' => '我是一个茶壶',
'419' => '认证已过期',
'421' => '错误的请求',
'422' => '不可处理的实体',
'423' => '锁定',
'424' => '失败的依赖',
'425' => '太早了',
'426' => '需要升级',
'428' => '前提要求',
'429' => '请求太多',
'431' => '请求标头字段太大',
'444' => '连接关闭无响应',
'449' => '重试',
'451' => '法律原因不可用',
'499' => '客户端关闭请求',
'500' => '服务器内部错误',
'501' => '未实现',
'502' => '网关错误',
'503' => '服务不可用',
'504' => '网关超时',
'505' => 'HTTP版本不支持',
'506' => '变体协商',
'507' => '存储空间不足',
'508' => '检测到环路',
'509' => '超出带宽限制',
'510' => '未延期',
'511' => '需要网络验证',
'520' => '未知错误',
'521' => 'Web服务器已关闭',
'522' => '连接超时',
'523' => '原点无法到达',
'524' => '发生超时',
'525' => 'SSL握手失败',
'526' => '无效的SSL证书',
'527' => '轨道炮错误',
'598' => '网络读取超时',
'599' => '网络连接超时',
'unknownError' => '未知错误',
];
static function getReasonPhrase($statusCode): string
{
if (isset(self::$phrases[$statusCode])) {
return self::$phrases[$statusCode];
} else {
return '未知错误';
}
}
}