読者になるhnwの日記この広告は、90日以上更新していないブログに表示しています。5.5からdeprecatedになっています。PHP7からは廃止されます。PHPのpreg_replace関数では、e修飾子が利用できます。これはPerlから輸入された機能で、置換後パターンの文字列をPHP文法に従って評価する(evalする)というものです。Perlを知らないとあまり使わない機能かもしれませんが、Perlを知っているとPHPでも使いたくなるのではないでしょうか。本記事では、preg_replace関数でe修飾子を使う場合の注意点を指摘し、代替案を示します。preg_replace関数のe修飾子preg_replace関数のe修飾子は次のような機能です。コードであるとして取り扱います。コードを記述してください。ある行でパースエラーが発生してしまいます。Manualつまり、マッチした場所を置換後パターンに置き換える代わりに、置換後パターンをPHPとして解釈した結果に置き換えます。これを利用すれば正規表現マッチの範囲では不可能な置換を実現できるというわけです。例題として、「空白文字以外が連続する部分文字列の後ろに、その部分文字列の長さを括弧付きの数字で追記する」ことを考えてみます。12345(5)」などと置換したいものとします。通常の正規表現では文字列の長さを取り出すことはできませんので、e修飾子とstrlen関数で実現してみましょう。12345(5)このコードで期待通りに実現できているように見えますが、実は置換対象文字列にダブルクォートが含まれる場合に正しく動きません。e修飾子を指定すると後方参照がエスケープされるpreg_replace関数のマニュアルには次のように書いてあります。'strlen(\'$1\')+strlen("$2")')に構文エラーが発生しないようにするためのものです。PHPの文字列構文を意識し、文字列がどのように解釈されるのかを正確に知っておくようにしましょう。Manualつまり、e修飾子を利用した場合のみ、置換パターン中の後方参照($1や\1など)を文字列展開する際に、勝手にaddslashesされるというわけです。先ほどのコードで試してみましょう。#\",;(5)置換前は4文字だったものが5文字に置換されてしまいました。いうことかというと、$1の中の"が文字列展開される際に\"に展開されてしまったのです。これはマニュアルの記載通りの挙動ではありますが、多くの人が予想しない挙動なのではないでしょうか。このようにe修飾子を使っていて後方参照をPHP文字列として評価したい場合、キャプチャ*1した文字列中にダブルクォートおよびヌル文字が含まれないかどうかに注意する必要があります。これらが含まれる場合、期待と異なる置換結果になる可能性があります。後方参照をダブルクォートで囲むと別の問題がある先ほどの例では$1の周りをシングルクォートで囲んでいました。ダブルクォートで囲めば問題は解決するでしょうか?確かに、後方参照の文字列展開後に「"\""」となっていれば同じ問題は起きません。ダブルクォートで囲むとevalするときに変数展開が起こるので、キャプチャした文字列中にドル記号が含まれていると不思議な結果になってしまいます。bar(3)例題の仕様としては「$foo」は「$foo(4)」と置換されるべきなのですが、「"$foo(".strlen("$foo").")"」をevalしたせいで「bar(3)」になってしまうのです。eval実行時にプログラムがユーザーに見られたくない変数値を持っていたとしたら、情報流出などの事故の原因になりかねません。ダブルクォートで囲む状況が無いとは言いませんが、シングルクォートで囲むよりも注意が必要なのではないでしょうか。e修飾子の代わりにpreg_replace_callback関数を使うpreg_replaceとe修飾子を用いて置換後パターン中で後方参照をPHP文字列として使いたい場合、シングルクォートで囲んでもダブルクォートで囲んでも事故の原因になりかねないことを指摘しました。このような状況に対して、preg_replace_callback関数を利用することができます。次のようなコードです。$foo(4)このようにすれば余計なエスケープはされません。上記コード例ではPHP5.3.x以降の無名関数の機能を利用していますが、置換専用の関数やメソッドを作成したり、create_function関数を利用すればPHP5.2.x以前でも同じことができます。参考までにPerlで同じことをしたい場合のコードを紹介します。$foo(4)見ての通り、十分シンプルなコードです。Perlでは/eを指定した場合に置換後パターンとして直接コードを書けることに加え、$1なども通常の変数としてセットされるので、他の場所と同じようにコードを書くことができます。PHPには正規表現リテラルが無いので、e修飾子の対象となるPHPコードは文字列として実現されており、エスケープ地獄*2に悩まされたり、変なコードを実行しないようにaddslashesされたりしてカオスなわけです。逆に言えば、preg_replace_callback関数と無名関数の組み合わせを使うのは、エスケープ地獄から抜け出せるというメリットもあるわけです。Perlのノリでe修飾子を使うより、preg_replace_callbackを使う方がPHPっぽいということなのかもしれません。まとめpreg_replaceのe修飾子の挙動がキモい置換後パターン中の後方参照は勝手にaddslashesされるマッチするパターンによっては利用可能だが、エスケープ地獄の恐れもPHP5.3.0以降ならpreg_replace_callback+無名関数なら比較的読みやすいPHP5.2.x以前ならプライベートメソッドを作ってpreg_replace_callback?create_functionは文字列としてPHPコードを書くので個人的には嫌い*1:正規表現を括弧で囲むことで、マッチした文字列を捕まえておくこと。2015の3日目です。和訳してみた先週の土日にPHPMatsuriに行ってきました。12はてなブログをはじめよう!hnwさんは、はてなブログを使っています。ブログを報告する引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。