[Laravel] Clockworkの表示をカスタムする
こんにちは、ナカエです。
最近Laravelのパフォーマンスチューニングの案件があり、以前の記事でも紹介したプロファイル用のツールClockworkに引き続きお世話になっていました。
デフォルトでも十分便利なのですが、ClockworkのDatabaseタブに表示されているクエリがソースコードのどの部分で発行されたのかをより手軽に知りたいと思い、若干手を加えてみました。
理想的にはタブや表示項目を新たに増やしたかったのですが、サーバ側だけでは対応できないようです。
妥協案として、あまり役に立たないEloquent Model名を差し替えて、代わりにファイル名と行番号を表示させることにします。
例) User → /app/Infrastructure/User/UserRepository.php (L51)
EloquentDataSourceを継承して上書き
Cockworkのソースを追うと、ElouquentDataSource::registerQuery()にて、Eloquentのクエリ実行イベントからクエリ情報を取得していることがわかります。このタイミングでバックトレースから必要な情報を追加します。
app/Support/MyEloquentDataSource.php
<?php
namespace App\Support\DevelopmentHelper;
use Clockwork\DataSource\EloquentDataSource;
use Clockwork\Helpers\StackTrace;
use Illuminate\Support\Arr;
class EloquentDomainDataSource extends EloquentDataSource
{
public function registerQuery($event)
{
$caller = StackTrace::get()->firstNonVendor(array('itsgoingd', 'laravel', 'illuminate'));
$targetFrame = null;
$model = $this->nextQueryModel;
// バックトレースから表示したい要素を探す
$backtrace = debug_backtrace(DEBUG_BACKTRACE_IGNORE_ARGS);
foreach($backtrace as $frame) {
if (!$this->_isTargetFrame($frame)) {
continue;
}
$targetFrame = $frame;
break;
}
// 表示したい要素が見つかれば、ファイル名と行番号でModelを差し替え
if (!is_null($targetFrame)) {
$model = $this->getFileAndLine($targetFrame);
}
$this->queries[] = array(
'query' => $event->sql,
'bindings' => $event->bindings,
'time' => $event->time,
'connection' => $event->connectionName,
'file' => $caller->shortPath,
'line' => $caller->line,
'model' => $model,
);
$this->nextQueryModel = null;
}
/**
* ファイル名と行番号を取得して返す
* @param array $frame
* @return string
*/
private function getFileAndLine(array $frame)
{
return str_replace(base_path(), '', Arr::get($frame, 'file')) . ' (L' . Arr::get($frame, 'line') . ')';
}
/**
* 抜きだしたい要素かどうかを判定
* @param array $frame
* @return bool
*/
private function _isTargetFrame(array $frame)
{
if (!array_key_exists('file',$frame) || !array_key_exists('line', $frame) || !array_key_exists('class', $frame)) {
return false;
}
return strpos($frame['file'], base_path().'/app') !== false
&& strpos($frame['class'], 'Illuminate\Database\Eloquent') !== false;
}
}
IoCコンテナに登録されているEloquentDataSourceの入れ替え
あとはClockworkが呼び出すDataSourceを差し替えるのみです。
デフォルトのサービスプロバイダを継承して変更します。
app/Providers/ClockworkServiceProvider.php
<?php
namespace App\Providers;
use App\Support\Clockwork\MyEloquentDataSource;
use Clockwork\DataSource\EloquentDataSource;
class ClockworkServiceProvider extends \Clockwork\Support\Laravel\ClockworkServiceProvider
{
public function register()
{
parent::register();
$this--->app->bind(EloquentDataSource::class, MyEloquentDataSource::class);
$this->app->alias('clockwork.eloquent', MyEloquentDataSource::class);
$this->app->singleton('clockwork.eloquent', function($app)
{
return new MyEloquentDataSource($app['db'], $app['events']);
});
}
}
config/app.php
// Clockworkのデフォルトから変更
'App\Providers\ClockworkServiceProvider',
IoCコンテナを利用したコードはカスタムが楽で助かりますね。