CodeIgniter4のコアクラスの拡張はわかりにくい
CodeIgniter3は簡単にログを拡張できたが、CodeIgniter4のコアクラスの拡張はちょっとわかりにくいので、Slack通知を例に作成してみた。
勉強中のLaravelでやってみようかと思ったが、すでにあったので、CodeIgniter4で記述する。
CodeIgniter4のコアクラスの拡張(基本編+愚痴)
マニュアルはこれ
https://codeigniter4.github.io/userguide/extending/core_classes.html
ルーターの拡張を行っているが、このままでは動かない。なんでやねん。
四苦八苦した結果、以下のように書くと動作した
public static function routes(bool $getShared = true) {
if ($getShared) {
return static::getSharedInstance('routes');
}
$locator = Services::locator();
$moduleConfig = config('Modules');
return new \App\Libraries\RouteCollection($locator, $moduleConfig);
}
つまり、コンストラクタに設定を渡さなければならない ということ。
もちろん、利用する(拡張する)クラスごとにコンストラクタの内容が変わるので、systemディレクトリを徘徊することになる。
これがマニュアルから非常にわかりづらい。
名前空間を使い慣れていればすぐに気づくのかもしれないが、CodeIgniter3から上がってくるユーザーには厳しいのではないか。
ログクラスを拡張してみる
- ログは
Service::logger
を拡張すれば良い。 - 拡張ログクラス
Logger.php
はLibraries
に置いた。 - POST先の
POST_URL_TO_SLACK
の作り方はこちらが詳しいので割愛
slackのIncoming webhookが新しくなっていたのでまとめてみた
public static function logger($getShared = true) {
if ($getShared) {
return static::getSharedInstance('logger');
}
$config = config('Logger');
return new \App\Libraries\Logger($config);
}
<?php
namespace App\Libraries;
use CodeIgniter\Log\Logger AS BaseLogger;
class Logger extends BaseLogger {
public function log($level, $message, array $context = []): bool {
// 継承元クラスでログの書き込み(成功するとtrueが返る)
$ret = parent::log($level, $message, $context);
if ($ret) {
// Slackが落ちていたりした場合のためにTryにしている。
try {
// Slackに通知するためのメッセージ作成
$request = \Config\Services::request();
$router = \Config\Services::router();
$data = [
"text" => "New Paid Time Off request from Fred Enriquez",
'blocks' => [
[
'type' => 'header',
'text' => [
'type' => 'plain_text',
'text' => 'New Error'
]
],
[
'type' => 'section',
'fields' => [
[
"type" => "mrkdwn",
"text" => "*Class:*" . PHP_EOL . $level
],
[
"type" => "mrkdwn",
"text" => "*Date:*" . PHP_EOL . date('Y-m-d H:i:s')
]
]
],
[
'type' => 'section',
'fields' => [
[
"type" => "mrkdwn",
"text" => "*Router:*" . PHP_EOL . $router->controllerName()
],
[
"type" => "mrkdwn",
"text" => "*URL:*" . PHP_EOL . current_url()
]
]
],
[
'type' => 'section',
'text' => [
"type" => "mrkdwn",
"text" => "*IP / UA:*" . PHP_EOL . $request->getIPAddress() . PHP_EOL . $request->getUserAgent()
]
],
[
'type' => 'section',
'text' => [
"type" => "mrkdwn",
"text" => '```' . PHP_EOL . $message . PHP_EOL . '```'
]
],
],
];
$header = [
'Content-Type: application/json',
];
// SlackにPOSTする
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, POST_URL_TO_SLACK);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST'); // post
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data)); // jsonデータを送信
curl_setopt($curl, CURLOPT_HTTPHEADER, $header); // リクエストにヘッダーを含める
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, true);
$response = curl_exec($curl);
$header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
$header = substr($response, 0, $header_size);
$body = substr($response, $header_size);
$result = json_decode($body, true);
curl_close($curl);
} catch (Exception $ex) {
}
}
return $ret;
}
}
結果
trace内容がうまく取れていない(恐らくcontextに入っている)が、メッセージが長くなること請け合いなので、ここまでわかれば解析はかなり楽になるとして妥協。
↓↓ trace取れました ↓↓
trace対応版
やっぱりcontextにいたので対応してみた。
ただし、予想通り、メッセージが長くなってしまうので、対応しなくてもいいかもしれない。
これ以上のことをやろうと思うと、エラーメッセージをテキストファイルにしてアップロード・添付するしかない。
<?php
namespace App\Libraries;
use CodeIgniter\Log\Logger AS BaseLogger;
class Logger extends BaseLogger {
public function log($level, $message, array $context = []): bool {
// 継承元クラスでログの書き込み(成功するとtrueが返る)
$ret = parent::log($level, $message, $context);
if ($ret) {
// Slackが落ちていたりした場合のためにTryにしている。
try {
// Slackに通知するためのメッセージ作成
$request = \Config\Services::request();
$router = \Config\Services::router();
$traceMessage = $message;
if (count($context) > 0) {
foreach ($context as $key => $data) {
$slackMessage = str_replace('{' . $key . '}', $data, $traceMessage);
}
}
$data = [
"text" => "New Paid Time Off request from Fred Enriquez",
'blocks' => [
[
'type' => 'header',
'text' => [
'type' => 'plain_text',
'text' => 'New Error'
]
],
[
'type' => 'section',
'fields' => [
[
"type" => "mrkdwn",
"text" => "*Class:*" . PHP_EOL . $level
],
[
"type" => "mrkdwn",
"text" => "*Date:*" . PHP_EOL . date('Y-m-d H:i:s')
]
]
],
[
'type' => 'section',
'fields' => [
[
"type" => "mrkdwn",
"text" => "*Router:*" . PHP_EOL . $router->controllerName()
],
[
"type" => "mrkdwn",
"text" => "*URL:*" . PHP_EOL . current_url()
]
]
],
[
'type' => 'section',
'text' => [
"type" => "mrkdwn",
"text" => "*IP / UA:*" . PHP_EOL . $request->getIPAddress() . PHP_EOL . $request->getUserAgent()
]
],
[
'type' => 'section',
'text' => [
"type" => "mrkdwn",
"text" => '```' . PHP_EOL . $traceMessage . PHP_EOL . '```'
]
],
],
];
$header = [
'Content-Type: application/json',
];
// SlackにPOSTする
$curl = curl_init();
curl_setopt($curl, CURLOPT_URL, POST_URL_TO_SLACK);
curl_setopt($curl, CURLOPT_CUSTOMREQUEST, 'POST'); // post
curl_setopt($curl, CURLOPT_POSTFIELDS, json_encode($data)); // jsonデータを送信
curl_setopt($curl, CURLOPT_HTTPHEADER, $header); // リクエストにヘッダーを含める
curl_setopt($curl, CURLOPT_SSL_VERIFYPEER, false);
curl_setopt($curl, CURLOPT_RETURNTRANSFER, true);
curl_setopt($curl, CURLOPT_HEADER, true);
$response = curl_exec($curl);
$header_size = curl_getinfo($curl, CURLINFO_HEADER_SIZE);
$header = substr($response, 0, $header_size);
$body = substr($response, $header_size);
$result = json_decode($body, true);
curl_close($curl);
} catch (Exception $ex) {
}
}
return $ret;
}
}
備考
- 案件によってはログイン情報やDebugbarの内容なんかを入れてもいいかもしれない。
- Slackへの通知部分はモデルかライブラリ化をしたほうが良い(エラー通知以外にも使えるので)
(まぁ、手で書かずにComposer使えばいいんだけど)