ゴール
PHPのpreg_match
に渡す正規表現が正しいかどうかをチェックしたい。
is_valid_regex
という関数に正規表現を渡したら、その正規表現の構文が正しいとか、preg_match
で処理できる正規表現になっているかなどをチェックした上で、結果値を返す関数がほしい。
is_valid_regex('/a/') == new Valid();
is_valid_regex('/(a/') == new Invalid('Compilation failed: nothing to repeat at offset 0');
正規表現が正しいかどうかをチェックする関数はない
PHPの組み込み関数で、上記のis_valid_regex
ような振る舞いをする関数は存在しない(PHP7.2時点)。
したがって、preg_match
で正規表現を実行してみる他ない。
preg_match
の仕様
preg_match
は正規表現が正しくないとき、Warningを出す
preg_match
は第一引数に与えられた正規表現に問題があるとき、Warningを出す仕様になっている。
preg_match('/*invalid-regular-expression/', '');
// Warning: preg_match(): Compilation failed: nothing to repeat at offset 0
Warningを出すときは、戻り値がfalse
になる
Warningが出るとき、preg_match
の戻り値はfalse
になる。なお、下記コードはエラー抑制演算子@
をつけることで、Warningが出力されることを防いでいる。
$result = @preg_match('/*invalid-regular-expression/', '');
assert($result === false);
preg_match
の仕様を踏まえた上で
error_get_last
関数でエラーを取ることができるか?
できる。
@preg_match('/*invalid-regular-expression/', '');
assert(
[
'type' => E_WARNING,
'message' => 'preg_match(): Compilation failed: nothing to repeat at offset 0',
'file' => __FILE__,
'line' => __LINE__ - 6,
] === error_get_last()
);
preg_last_error
関数
正規表現のエラーを取得する関数にpreg_last_error
もあるが、error_get_last
に比べるとずっと情報量が少ない。
@preg_match('/*invalid-regular-expression/', '');
assert(PREG_INTERNAL_ERROR === preg_last_error());
一見error_get_last
でエラーを拾うのが良さそうだが
一見error_get_last
でエラーを拾うのが良さそうだが、エラーハンドラーがセットされていると、Warningの情報はエラーハンドラーに流れてしまい、error_get_last
でWarningの中身を取ることはできない。エラー抑制演算子@
をつけていても変わらない。
$hasErrorHandlerInvoked = false;
// if error handler exists,
set_error_handler(
function () use (&$hasErrorHandlerInvoked) {
restore_error_handler();
$hasErrorHandlerInvoked = true;
}
);
// error_get_last can't get the warning.
@preg_match('/*invalid-regular-expression/', '');
assert($hasErrorHandlerInvoked === true);
assert(error_get_last() === null);
error_reporting
を無効化してもerror_handler
がセットされているとうまくいかない。
$hasErrorHandlerInvoked = false;
set_error_handler(
function () use (&$hasErrorHandlerInvoked) {
$hasErrorHandlerInvoked = true;
}
);
$errorReporting = ini_get('error_reporting');
ini_set('error_reporting', 'Off');
$result = @preg_match('/*invalid-regular-expression/', '');
assert($result === false);
assert($hasErrorHandlerInvoked === true);
ini_set('error_reporting', $errorReporting);
Warningを取るには一時的なエラーハンドラーをセットする
フレームワークなどがエラーハンドラーをセットしてくる環境を想定すると、Warningを取るために一時的なエラーハンドラーをセットする方法が考えられる。set_error_handlerは局所的にも使える - Qiitaを参考にした。
$isValidRegex = function (string $regex): bool {
set_error_handler(
function ($severity, $message) {
throw new \RuntimeException($message);
}
);
try {
preg_match($regex, '');
} catch(\RuntimeException $e) {
return false;
} finally {
restore_error_handler();
}
return true;
};
assert($isValidRegex('/a/'));
assert($isValidRegex('/*a/'));
結論
- Warningを取るには一時的なエラーハンドラーをセットする必要がある
- 思った以上に面倒な実装になる