26
13

More than 3 years have passed since last update.

【PHP8.0】もう`strpos($haystack, $needle)!==false`って書かなくていいんだ

Last updated at Posted at 2020-03-09

ある文字列中に特定の文字列が存在するかを調べる方法としてstrposが存在します。

しかし、そもそもstrposは『ある文字列中で特定の文字列が何文字目に出てくるか』を調べる関数であり、第一に使用目的が異なる関数です。
そしてこちらも有名な話ですが、先頭が一致すると0が返ってくるので、緩やかな比較ではfalseと区別されません。
PHPのよくある落とし穴のひとつです。

if(strpos('放課後アトリエといろ', 'アトリエ')){
    echo '"放課後アトリエといろ"には"アトリエ"が含まれる'; // 表示される
}

if(strpos('放課後アトリエといろ', '放課後')){
    echo '"放課後アトリエといろ"には"放課後"が含まれる'; // 表示されない!!
}

if(strpos('放課後アトリエといろ', '放課後') !== false ){
    echo '"放課後アトリエといろ"には"放課後"が含まれる'; // 表示される
}

マニュアルにも大きく警告が出されています。
01.png

この状況がついにPHP8で変わります

PHP RFC: str_contains

Introduction

str_containsは、ある文字列に特定の文字列が含まれているかをチェックし、見つかったか否かによってtrue/falseいずれかのbooleanを返します。

ある文字列に特定の文字列が含まれているかをチェックするために、一般的にはstrposstrstrが使用されます。
str_containsは凡そあらゆるプロジェクトで非常によく使われるユースケースなので、独自の関数を追加するに値するはずです。
strposstrstrには、いくつかの欠点が存在します。

・あまり直感的ではない
・間違いをしやすい(特に!==の場合)
・新規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を用意する必要はないというわけです。

26
13
4

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
26
13