ある文字列中に特定の文字列が存在するかを調べる方法としてstrposが存在します。
しかし、そもそもstrposは『ある文字列中で特定の文字列が何文字目に出てくるか』を調べる関数であり、第一に使用目的が異なる関数です。
そしてこちらも有名な話ですが、先頭が一致すると0が返ってくるので、緩やかな比較ではfalseと区別されません。
PHPのよくある落とし穴のひとつです。
if(strpos('放課後アトリエといろ', 'アトリエ')){
echo '"放課後アトリエといろ"には"アトリエ"が含まれる'; // 表示される
}
if(strpos('放課後アトリエといろ', '放課後')){
echo '"放課後アトリエといろ"には"放課後"が含まれる'; // 表示されない!!
}
if(strpos('放課後アトリエといろ', '放課後') !== false ){
echo '"放課後アトリエといろ"には"放課後"が含まれる'; // 表示される
}
この状況がついにPHP8で変わります。
PHP RFC: str_contains
Introduction
str_contains
は、ある文字列に特定の文字列が含まれているかをチェックし、見つかったか否かによってtrue/falseいずれかのbooleanを返します。
ある文字列に特定の文字列が含まれているかをチェックするために、一般的にはstrposやstrstrが使用されます。
str_contains
は凡そあらゆるプロジェクトで非常によく使われるユースケースなので、独自の関数を追加するに値するはずです。
strpos
やstrstr
には、いくつかの欠点が存在します。
・あまり直感的ではない
・間違いをしやすい(特に!==の場合)
・新規PHP開発者が覚えにくい
このため、多くのPHPフレームワークは独自のヘルパ関数を提供しています。
このことこそがstr_contains
の重要性や必要性をよく示しています。
Proposal
このRFCは、新しい関数str_contains
を提唱します。
str_contains ( string $haystack , string $needle ) : bool
引数$haystack
と$needle
を取り、$haystack
の中に$needle
が存在するか否かをbooleanで返します。
str_contains("abc", "a"); // true
str_contains("abc", "d"); // false
// 空文字列はtrueになる
str_contains("abc", ""); // true
str_contains("", ""); // true
As of PHP 8, behavior of '' in string search functions is well defined, and we consider '' to occur at every position in the string, including one past the end. As such, both of these will (or at least should) return true. The empty string is contained in every string.
PHP8において、文字列検索関数において''の動作は明確に定義されており、終端を含む文字列全ての位置にマッチします。そのため、これらは両方ともtrueを返すべきです。空文字はあらゆる文字列に含まれています。 - Nikita Popov
Case-insensitivity and multibyte strings
internalメーリングリストなどの議論において、この関数のマルチバイト対応版(mb_str_contains)は必要ないという結論に達しました。
理由として、この関数のマルチバイト版は非マルチバイト版と全く同じ動作になります。
文字列の見付かった位置によって動作が異なる場合は、マルチバイト版は異なる動作になります。
この関数は見付かった位置によって動作は変わらないので、マルチバイト版の必要はありません。
大文字と小文字を区別しない版については、需要が大文字と小文字を区別する版よりはるかに低いため、このRFCには含まれません。
区別しない版を取り込むと、有効なバリアントはstr_contains
/mb_str_icontains
だけになります。
このように中途半端なバリアントをいきなり提供するとPHP開発者が混乱する可能性があるので、最初は小さく始めた方がよいでしょう。
Backward Incompatible Changes
PHP自体に後方互換性のない変更はありません。
ユーザランドに同じ関数が実装されている場合、非互換の可能性があります。
しかし、そのようなグローバル関数はアプリケーションの起動プロセスのかなり早い段階で追加されるので、開発者はこの問題にすぐ気付くでしょう。
Proposed PHP Version(s)
PHP 8
Implementation
str_contains
関数は、このプルリクで実装されています。
https://github.com/php/php-src/pull/5179
投票
投票は2020/03/16まで、受理には2/3の賛成が必要です。
2020/03/09時点では賛成43反対5で、まず間違いなく受理されます。
感想
よく考えたらずっと前からあってもおかしくない関数なのに、これまで存在してなかったのは不思議ですね。
実際のところ、文字数としてはわずか2字の差にすぎません。
if(strpos($heystack, $needle)!==false){
echo '$heystackに$needleが含まれている';
}
// 同じ
if(str_contains($heystack, $needle)){
echo '$heystackに$needleが含まれている';
}
しかし、読みやすさ、理解のしやすさという点では圧倒的にstr_contains
に分がありますね。
以下はマルチバイト対応関数が用意されない点の補足です。
マルチバイト対応strpos
としてmb_strposがありますが、文字が存在するか否かをチェックするだけであれば実はstrpos
でも問題ありません。
echo strpos('あい', 'い'); // 3
echo mb_strpos('あい', 'い'); // 1
echo strpos('あい', 'あ'); // 0
echo mb_strpos('あい', 'あ'); // 0
echo strpos('あい', 'う'); // false
echo mb_strpos('あい', 'う'); // false
値が変わるのは『存在したときの文字数の数え方』だけです。
文字列の有無を確認するだけであれば、存在しない場合は常にfalse、存在する場合は常にint型となって差が出ないわけですね。
同様にstr_contains
も、存在しない場合は常にfalse、存在する場合は常にtrueなるため、あえてmb_str_contains
を用意する必要はないというわけです。