10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

PHP:正規表現が正しいかどうかチェックしたかったが、思った以上に大変だった

Last updated at Posted at 2018-11-13

ゴール

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を取るには一時的なエラーハンドラーをセットする必要がある
  • 思った以上に面倒な実装になる
10
5
3

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
10
5

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?