
在laravel应用开发中,我们经常会创建一些通用的辅助函数(helper functions),例如用于统一记录数据库操作异常的logdatabaseerror($exception)。这个辅助函数可能被多个控制器中的多个方法调用。在记录日志时,我们通常希望包含更多上下文信息,例如哪个控制器和方法触发了异常。然而,每次调用时都手动传递控制器和方法名作为参数,会增加代码冗余和维护成本。
例如,一个典型的数据库错误日志辅助函数可能如下:
// helpers.php
function logDatabaseError ($exception) {
// 如何在此处动态获取 $controller 和 $function?
$controller = '???';
$function = '???';
$log_string = "TIME: ".now().PHP_EOL;
$log_string.= "User ID: ".(Auth::user() ? Auth::user()->id : 'Guest').PHP_EOL;
$log_string.= "Controller->Action:".$controller."->".$function.PHP_EOL;
$log_string.= $exception->getMessage().PHP_EOL; // 获取异常消息
$log_string.= $exception->getTraceAsString().PHP_EOL; // 完整堆栈追踪
Storage::disk('logs')->append('database.log', $log_string);
}而控制器中的调用方式:
// BestControllerEverController.php
class BestControllerEver extends Controller
{
function writeStuffToDatabase (Request $request) {
try {
DB::table('some_table')->insert(['data' => 'value']);
}
catch(\Illuminate\Database\QueryException $exception) {
logDatabaseError($exception); // 希望在此处自动识别 BestControllerEver 和 writeStuffToDatabase
}
}
}虽然异常对象本身包含堆栈追踪信息,但直接解析其字符串表示既不优雅也不可靠。因此,我们需要一种更结构化的方法来获取这些信息。
PHP内置的debug_backtrace()函数可以获取程序执行的堆栈信息。然而,直接使用它需要手动解析数组,较为繁琐。spatie/backtrace是一个优秀的PHP包,它封装了debug_backtrace(),提供了更面向对象和易于使用的API来处理堆栈追踪。
首先,通过Composer将spatie/backtrace安装到您的Laravel项目中:
composer require spatie/backtrace
在logDatabaseError辅助函数中,我们可以使用Spatie\Backtrace\Backtrace::create()来生成一个堆栈追踪实例,然后遍历其帧(frames)来找到负责的控制器和方法。
// helpers.php
use Spatie\Backtrace\Backtrace;
use Spatie\Backtrace\Frame as SpatieBacktraceFrame; // 避免与 Laravel 内部 Frame 冲突
function logDatabaseError ($exception) {
$backtrace = Backtrace::create();
// 过滤堆栈帧,找到第一个继承自 App\Http\Controllers\Controller 的类
$controllerResponsible = collect($backtrace->frames())
->filter(function (SpatieBacktraceFrame $frame) {
return (bool)$frame->class; // 确保帧有类名
})
->filter(function (SpatieBacktraceFrame $frame) {
// 检查该类是否是 App\Http\Controllers\Controller 的子类
// 注意:您的控制器必须继承 App\Http\Controllers\Controller
return is_subclass_of($frame->class, \App\Http\Controllers\Controller::class);
})
->first(); // 获取第一个匹配的控制器帧
$log_string = "TIME: " . now()->format('Y-m-d H:i:s') . PHP_EOL;
$log_string .= "User ID: " . (auth()->check() ? auth()->id() : 'Guest') . PHP_EOL;
if ($controllerResponsible) {
$log_string .= "Controller->Action:" . $controllerResponsible->class . "->" . $controllerResponsible->method . PHP_EOL;
} else {
$log_string .= "Controller->Action: N/A (Could not determine from backtrace)" . 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; // 包含完整堆栈追踪
\Illuminate\Support\Facades\Storage::disk('logs')->append('database.log', $log_string);
// 如果需要使用 Laravel 8.66.0+ 的按需日志功能,可以取消注释以下代码
/*
\Illuminate\Support\Facades\Log::build([
'driver' => 'single',
'path' => storage_path('logs/database.log'),
])->info($log_string);
*/
}注意事项:
对于更大型的应用,将错误处理逻辑分散在各个try-catch块中并不理想。Laravel提供了强大的全局异常处理机制,通过修改app/Exceptions/Handler.php文件,我们可以实现更优雅、集中化的错误上下文日志记录。这种方法不仅移除了控制器中的try-catch样板代码,还能确保所有报告的异常都包含调用上下文。
首先,从您的控制器方法中移除所有的try-catch块,让异常自然地冒泡到全局异常处理器。
// BestControllerEverController.php
class BestControllerEver extends Controller
{
function writeStuffToDatabase (Request $request) {
// 移除 try-catch,让异常被全局处理器捕获
DB::table('myunavialbetable')->get(); // 假设这会抛出 QueryException
}
}在app/Exceptions/Handler.php中,我们将利用reportable方法在异常被报告时捕获堆栈信息,并通过context方法将这些信息添加到日志上下文中。
<?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
{
/**
* 用于在 reportable 回调中存储控制器负责信息。
*
* @var SpatieBacktraceFrame|null
*/
public $controllerResponsible = null;
/**
* A list of the exception types that are not reported.
*
* @var array<int, class-string<Throwable>>
*/
protected $dontReport = [
//
];
/**
* A list of the inputs that are never flashed for validation exceptions.
*
* @var array<int, string>
*/
protected $dontFlash = [
'current_password',
'password',
'password_confirmation',
];
/**
* Register the exception handling callbacks for the application.
*
* @return void
*/
public function register(): void
{
$this->reportable(function (Throwable $e) {
// 在异常被报告时,创建回溯实例并查找负责的控制器
$backtraceInstance = SpatieBacktrace::createForThrowable($e);
$controllerResponsible = collect($backtraceInstance->frames())
->filter(function (SpatieBacktraceFrame $frame) {
return (bool)$frame->class;
})
->filter(function (SpatieBacktraceFrame $frame) {
return is_subclass_of($frame->class, \App\Http\Controllers\Controller::class);
})
->first();
// 将找到的控制器帧存储起来,以便在 context 方法中使用
$this->controllerResponsible = $controllerResponsible;
});
}
/**
* Get the default context variables for logging.
*
* @return array<string, mixed>
*/
protected function context(): array
{
$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的日志系统会自动包含触发该异常的控制器和方法信息,无需在业务逻辑中手动处理。
本文介绍了两种在Laravel中动态获取调用辅助函数或触发异常的控制器和方法的方法:
无论选择哪种方法,关键在于利用PHP的堆栈追踪机制(通过spatie/backtrace进行优化),并确保您的控制器都遵循了继承App\Http\Controllers\Controller的约定。第二种方案尤其适合构建健壮、易于调试的生产级Laravel应用。
以上就是在Laravel应用中获取调用辅助函数的控制器和方法的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号