PHP Manual
/
チェーン加工

PHPの正規表現

08. 03. 2020

正規表現は、マスク(パターン)に従って文字列の検索、検証、比較、分割、折りたたみ、置換を簡単に行うことができるツールです。高度な文字列操作のための非常に強力でエレガントなツールです。

マスク

最初に、実際に実行する正規表現を考える必要があります。それはテキスト文字列として入力され、ルールや設定オプションがたくさんあります(これは非常に複雑な技術です)。

まず最初に、正規表現は左から右に順次評価され、文字列の解釈方法が複数ある場合は、常に可能な限り大きなマッチが使われることに注意しましょう(できるだけ多くの文字を処理しようとする、hungryなふるまいをします)。

正規表現の動作や処理方針は、多くのスイッチによって影響を受けることがあります。

文字列が有効な電子メールであるかどうかの簡単な検証

文字列 jan@barasek.com が有効な電子メールアドレスであることを、複雑な部分に分割したり一文字ずつ調べたりすることなく、単純に確認するにはどうしたらよいでしょうか。

正規表現が答えを与えてくれます(上記の表現は例のために非常に単純化されており、実際の電子メールアドレス検証の実装はもう少し複雑なものになるはずです)。

$mail = 'jan@barasek.com';
$regex = '/^.+@.+.(en|ja|com)$/.';
if (preg_match($regex, $mail)) {
echo '電子メールは有効です';
} else {
echo '電子メールが有効でない';
}

もう少し詳しく/^.+@.+@.(en|en|com)$/ という表現について見てみましょう。

まず、式の始まりと終わりを示す / 文字の組(最初と最後)で式全体を囲む必要があります。式の末尾の / の後に修飾語(式の処理モードの設定)が入ります。

式を処理するときは、左側から一文字ずつ進めていきます。それぞれに意味があり、以下の表に示すとおりです。

文字|意味|説明|使用例|など
^
$
.|任意の文字|任意の文字を正確にキャッチします。
d| Number| Detects characters 0-9| Detects of telephone numbers that contains no space and having 9 digits: /^(+420)?\d{9}$/`.
電話番号の3桁の数字の間にスペースを入れることができます: /^(\d{3}d?){3}$/.
+
*
( )`|大括弧|部分式を表す。これは複数の異なるタグを囲んで、例えばその中で繰り返しを要求したり、その内容を変数にトラップしたりするのに使います。
Or
ピリオドで区切られた数字の組をキャッチします(ピリオドをエスケープしなければ、「任意の文字」と理解されます): //d+┣d+/.

念のため、ネット社が実装している電子メールのバリデーションルールの完全な形を挙げておきます。

/**
* 文字列が有効な電子メールアドレスであるかどうかを検索します。
*/
public static function isEmail(string $value): bool
{
$atom = "[-a-z0-9!#$%&'*+/=?^_`{|}~]"; // RFC 5322 の引用符で囲まれていないローカル部分
$alpha = "a-zx80-xFF"; // IDNのスーパーセット
return (bool) preg_match("(^
(\"([ !#-[\\]-~]*|\\\\[ -~])+\"|$atom+(\\.$atom+)*) # quoted or unquoted
@
([0-9$alpha]([-0-9$alpha]{0,61}[0-9$alpha])?\\.)+ # domain - RFC 1034
[$alpha]([-0-9$alpha]{0,17}[$alpha])? # top domain
\\z)ix", $value);
}

preg_match() - パターンによる検証

フォーマットの検証と解析を行う基本的な関数は preg_match() で、2つの必須パラメータを持ち、3つ目のパラメータで出力フィールドを指定することができる。

$psc = '272 01'; // クラドノ
if (preg_match('/^(\d{3})\s*(\d{2})$/', $psc, $parser)) {
echo '郵便番号は有効です。' . $parser[1] . ','. $parser[2] . ']';
} else {
echo '郵便番号が無効です';
}

コードは、Code is valid [272, 01]と返されます。

1つの括弧に注目してください。これは、式をいくつかの小さな部分に分割するために使用します。これにより、個々の部分式を配列の項目として取得することが可能になる。そして、関数全体は、文字列がうまくキャプチャされたかどうかによって true または false を返します。

しかし、括弧の数が変わったり、単に数が多すぎたりと、数値の順番をナビゲートするのは非常に困難な場合があります。この場合、ブラケットに個別に名前を付けて、その名前を使ってキーにアクセスすることが可能です。

例えば、こんな感じです。

$phone = '777 123 456';
preg_match('/^(?<operator>d{3})\s*(?<number>[0-9 ]+)$/.', $phone, $parser);
echo $parser['オペレータ']; // 777を返しました。

preg_replace() - パターンによる置換

また、正規表現による文字列の置換も可能であり、特にユーザー後の様々なフォーマット修正に有効です。

例えば、ユーザーが入力した電話番号を整数値で保存したいとします。これは、サードパーティのライブラリで要求されているからですが、ユーザーはかなり乱暴なフォーマットで電話番号を入力することができます。

その場合、私はディクタムにこだわります。

「受け取るものには寛大に、送るものには厳格に」。

そのため、フォーマットを自動的に調整するのです。まず構文解析で文字列を各パーツに分解し、ブラケット番号に従って折り返す。

function formatPhoneNumber(string $phoneNumber): int
{
return (int) preg_replace(
'/^(\+\d{3})\s*(\d{3})\s*(\d{3})\s*(\d{3})$/',
'$2$3$4',
$phoneNumber
);
}
echo formatPhoneNumber('+420 777 123 456');

正規表現に従った文字列の構築

また、複雑なパターンに従って新しい文字列を生成する場合にも、正規表現は非常に有効である。

Pure PHPはこれをサポートしていませんが、サードパーティのReverseRegexライブラリをダウンロードすることで、これを実現することが可能です。

例えば、正規表現 [a-z]{10} に基づいてパスワードのセットを生成したいと思うかもしれませんが、それは誰にも止められません。

jmceohykoa
aclohnotga
jqegzuklcv
ixdbpbgpkl
kcyrxqqfyw
jcxsjrtrqb
kvaczmawlz
itwrowxfxh
auinmymonl
dujyzuhoag
vaygybwkfm

用途は以下の通りです。

use ReverseRegex\Lexer;
use ReverseRegex\Random\SimpleRandom;
use ReverseRegex\Parser;
use ReverseRegex\Generator\Scope;
require 'ベンダ/autoload.php';
$lexer = new Lexer('[a-z]{10}');
$gen = new SimpleRandom(10007);
$result = '';
$parser = new Parser($lexer, new Scope(), new Scope());
$parser->parse()->getResult()->generate($result, $gen);
echo $result;

私はこの方法で、例えばPresenterでネッテの計算例を生成していますが、実に簡単に可能なのです。

public function actionRegex(): void
{
$lexer = new Lexer('\d{1,3}[\+\-\*\/]');
$parser = new Parser($lexer, new Scope(), new Scope());
for ($i = 0; $i <= 10; $i++) {
$result = '';
$gen = new SimpleRandom($i);
$parser->parse()->getResult()->generate($result, $gen);
dump($result);
}
$this->terminate();
}

このライブラリにとって重要なことは、同じ入力に対して同じ出力を生成することです(たとえ、それぞれの正規表現に対してマッチする文字列が多数存在するように見えても)。もし、生成される式をランダムに変更したい場合は、出力文字列が生成される seed も変更する必要がある。このためには、種区間をトラバースするか、あるいは rand(1, 1e6) 関数が有効であろう。

エラーの捕捉と処理

PHPでは、正規表現でエラーをキャッチするのはかなり地獄ですが、それでも解決策はあります。

これについては、David Grudelによる記事Treachable regular expressions in PHPで詳しく説明されています。

Jan Barášek   Více o autorovi

Autor článku pracuje jako seniorní vývojář a software architekt v Praze. Navrhuje a spravuje velké webové aplikace, které znáte a používáte. Od roku 2009 nabral bohaté zkušenosti, které tímto webem předává dál.

Rád vám pomůžu:

Související články

1.
5.
Status:
All systems normal.
2024