前提(予備知識)
まず基本的なことから。
-
Closure
はシリアライズ出来ない。 - スタックトレースは
trace
プロパティに保持される。
trace
プロパティ配列の各要素は以下のようなキーを持つ連想配列からなる。 file
と line
はクロージャ呼び出しに関しては未定義になる。
-
file
(ファイル) -
line
(行) -
function
(関数/メソッド/クロージャ) -
args
(引数)
検証
変数に代入して呼び出す
コード
<?php
session_start();
try {
$func = function () { throw new \Exception; };
$func();
} catch (\Exception $e) {
var_dump($e->getTrace());
$_SESSION['exceptions'][] = $e;
}
結果
array(1) {
[0]=>
array(4) {
["file"]=>
string(12) "test.php"
["line"]=>
int(5)
["function"]=>
string(9) "{closure}"
["args"]=>
array(0) {
}
}
}
call_user_func
関数で呼び出す
コード
session_start();
try {
call_user_func(function () { throw new \Exception; });
} catch (\Exception $e) {
var_dump($e->getTrace());
$_SESSION['exceptions'][] = $e;
}
実行結果
array(2) {
[0]=>
array(2) {
["function"]=>
string(9) "{closure}"
["args"]=>
array(0) {
}
}
[1]=>
array(4) {
["file"]=>
string(38) "test.php"
["line"]=>
int(4)
["function"]=>
string(14) "call_user_func"
["args"]=>
array(1) {
[0]=>
object(Closure)#1 (0) {
}
}
}
}
Fatal error: Uncaught exception 'Exception' with message 'Serialization of 'Closure' is not allowed' in [no active file]:0
Stack trace:
#0 {main}
thrown in [no active file] on line 0
結果
-
function
がクロージャであっても、 "{closure}" という文字列になるので問題なくシリアライズ出来る。 -
args
の要素にクロージャが含まれていると、オブジェクトのままなのでシリアライズ出来なくなる。
対策
安全にセッションに格納したいときはスタックトレースを全部潰してしまえば問題ない。
追記: 2023-07-15 更新
親クラスを全て辿るようにしました
スタックトレースを強制的に空配列にする関数
<?php
function clear_trace_recursive(Throwable $e): void
{
do {
$r = new ReflectionClass($e);
do {
if ($r->hasProperty('trace')) {
$r->getProperty('trace')->setValue($e, []);
}
} while ($r = $r->getParentClass());
} while ($e = $e->getPrevious());
}