
在开发web应用程序时,我们经常会遇到需要记录错误或特定事件的场景。例如,当数据库操作失败时,我们可能希望将 queryexception 记录到一个特殊的日志文件中。为了更好地诊断问题,除了异常信息本身,我们通常还需要知道是哪个控制器中的哪个方法触发了这个异常。
考虑以下辅助函数 logDatabaseError:
// helpers.php
function logDatabaseError ($exception) {
$controller = ????; // 如何获取?
$function = ????; // 如何获取?
$log_string = "TIME: ".now().PHP_EOL;
$log_string.= "User ID: ".Auth::user()->id.PHP_EOL;
$log_string.= "Controller->Action:".$controller."->".$function.PHP_EOL;
$log_string.= $exception.PHP_EOL;
Storage::disk('logs')->append('database.log', $log_string);
}这个函数被多个控制器中的多个方法调用。我们希望在不修改 logDatabaseError 函数签名(即不传入 $controller 和 $function 参数)的情况下,自动捕获这些信息。
PHP提供了 debug_backtrace() 函数来获取程序执行的回溯信息。然而,直接处理原始的回溯数组可能比较繁琐。spatie/backtrace 是一个优秀的第三方库,它封装了 debug_backtrace(),提供了更易用、更面向对象的API来处理回溯栈。
首先,通过 Composer 将 spatie/backtrace 库安装到您的项目中:
composer require spatie/backtrace
安装完成后,我们可以在 logDatabaseError 辅助函数中利用 spatie/backtrace 来获取调用栈信息,并从中筛选出控制器和方法。
// helpers.php
use Spatie\Backtrace\Backtrace;
use Spatie\Backtrace\Frame;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Facades\Auth;
function logDatabaseError (\Illuminate\Database\QueryException $exception) {
// 创建一个回溯实例
$backtrace = Backtrace::create();
// 过滤回溯帧,找到第一个属于控制器类的帧
$controllerResponsible = collect($backtrace->frames())
->filter(function (Frame $frame) {
// 确保帧有类名
return ($frame->class);
})
->filter(function (Frame $frame) {
// 检查该类是否继承自 Laravel 的基控制器
// 注意:您的控制器必须继承 App\Http\Controllers\Controller
return is_subclass_of($frame->class, \App\Http\Controllers\Controller::class);
})
->first(); // 获取第一个匹配的控制器帧
$log_string = "TIME: " . now()->toDateTimeString() . PHP_EOL;
$log_string .= "User ID: " . (Auth::check() ? Auth::user()->id : 'Guest') . PHP_EOL;
if ($controllerResponsible) {
$log_string .= "Controller->Action:" . $controllerResponsible->class . "->" . $controllerResponsible->method . PHP_EOL;
} else {
$log_string .= "Controller->Action:Unknown->Unknown" . PHP_EOL;
}
$log_string .= "Exception: " . $exception->getMessage() . PHP_EOL;
$log_string .= "File: " . $exception->getFile() . " Line: " . $exception->getLine() . PHP_EOL;
$log_string .= "Trace: " . $exception->getTraceAsString() . PHP_EOL; // 包含完整的堆栈信息
Storage::disk('logs')->append('database.log', $log_string);
}注意事项:
现在,您的控制器可以像以前一样调用 logDatabaseError,无需传递额外的参数:
// BestControllerEverController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
use Illuminate\Database\QueryException;
class BestControllerEver extends Controller
{
public function writeStuffToDatabase (Request $request) {
try {
// 模拟一个数据库操作,可能会失败
DB::table('non_existent_table')->get();
}
catch(QueryException $exception) {
// 直接调用辅助函数,无需传递控制器/方法名
logDatabaseError($exception);
// 可以选择抛出异常或返回错误响应
return response()->json(['error' => 'Database operation failed.'], 500);
}
}
}更优雅和系统化的方法是将这种回溯逻辑集成到 Laravel 的异常处理器 (app/Exceptions/Handler.php) 中。这样,您无需在每个 try/catch 块中手动调用辅助函数,所有未捕获的或被报告的异常都将自动包含控制器和方法信息。
composer require spatie/backtrace
<?php
namespace App\Exceptions;
use Illuminate\Foundation\Exceptions\Handler as ExceptionHandler;
use Throwable;
use Spatie\Backtrace\Backtrace as SpatieBacktrace;
use Spatie\Backtrace\Frame as SpatieBacktraceFrame;
class Handler extends ExceptionHandler
{
/**
* 用于存储负责触发异常的控制器帧。
* @var SpatieBacktraceFrame|null
*/
public $controllerResponsible = null;
/**
* 不报告的异常类型列表。
*
* @var array
*/
protected $dontReport = [
//
];
/**
* 不闪存的验证异常输入列表。
*
* @var array
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* 注册应用程序的异常处理回调。
*
* @return void
*/
public function register()
{
// 当异常被报告时,执行此回调
$this->reportable(function (Throwable $e) {
// 为异常创建回溯实例
$backtraceInstance = SpatieBacktrace::createForThrowable($e);
// 过滤回溯帧,找到第一个属于控制器类的帧
$controllerResponsible = collect($backtraceInstance->frames())
->filter(function (SpatieBacktraceFrame $frame) {
return ($frame->class);
})
->filter(function (SpatieBacktraceFrame $frame) {
// 检查该类是否继承自 Laravel 的基控制器
return is_subclass_of($frame->class, \App\Http\Controllers\Controller::class);
})
->first();
// 将找到的控制器帧存储到类属性中,以便在 context() 方法中使用
$this->controllerResponsible = $controllerResponsible;
});
}
/**
* 获取用于日志记录的默认上下文变量。
* 此方法在 Laravel 记录异常时被调用。
*
* @return array
*/
protected function context()
{
$extraContext = [];
// 如果找到了负责的控制器帧,则将其信息添加到日志上下文中
if ($this->controllerResponsible instanceof SpatieBacktraceFrame) {
$extraContext['controller'] = $this->controllerResponsible->class;
$extraContext['method'] = $this->controllerResponsible->method;
$extraContext['controller@method'] = $this->controllerResponsible->class . '@' . $this->controllerResponsible->method;
}
// 合并父类的上下文和我们自定义的额外上下文
return array_merge(parent::context(), $extraContext);
}
}使用这种高级解决方案后,对于那些您希望由 Laravel 统一处理并记录的异常(例如 QueryException),您可以从控制器中移除 try/catch 块。
// BestControllerEverController.php
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\DB;
class BestControllerEver extends Controller
{
public function writeStuffToDatabase (Request $request) {
// 直接执行数据库操作,如果发生 QueryException,它将被 Laravel 的异常处理器捕获和处理
DB::table('non_existent_table')->get();
return response()->json(['message' => 'Operation successful.']);
}
}当 DB::table('non_existent_table')->get() 抛出 QueryException 时,Laravel 的异常处理器会捕获它。在 Handler.php 的 reportable 回调中,spatie/backtrace 会识别出 BestControllerEver->writeStuffToDatabase 是触发异常的控制器和方法,并将这些信息存储起来。随后,在 context() 方法中,这些信息会被添加到日志的上下文数据中,最终被 Laravel 的日志系统记录下来。
通过上述两种方法,我们都能在不修改辅助函数签名的前提下,自动获取调用辅助函数的控制器和方法信息。
以上就是从帮助函数中获取调用控制器和方法的教程的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号