Rubyistを自称しておきながら社ではPHPを使うダブルスタンダードを起こしている八雲です。
転生したら小学生Rubyプヨグヤマとしておかねゴリゴリ稼ぎたいです。
本題の前に、strposの簡単な説明
PHPの標準ライブラリの中に存在するstrpos()は文字列の検索に用いるメソッドで、第一引数の文字列の中から第二引数の文字列を検索し、見つかった場合は見つかった場所の番号を0から始まるint型の数値として返します。
見つからなかった場合はfalseが返ります。
var_dump(strpos("ruby is great.", "ruby"));
// -> int(0)
上記の場合ruby is great.の中にrubyという文字列が含んでいるか検索し、最初で見つかったのでint型の0を返しています。
var_dump(strpos("ruby is great.", "PHP"));
// -> bool(false)
今度はPHPという文字列が入っているか検索し、存在しなかった為falseが返っている、という感じのメソッドです。簡単ですね。
ここまで踏まえた上で本題に入ります。
本題:比較に使うとき
文字列の検索のメソッドなので、用法的には「文字列を検索させて、見つかった場合に何かしらのアクションをさせる」ということをしたい人が多いでしょう。
その場合、想定するコードは大体こんな感じになります。
if(strpos("ruby is great.", "ruby")){
echo "見つかったよ〜";
}
else {
echo "見つからなかった……";
}
一見特に何も問題ないように思えるコードですが、いざ実行すると見つからなかった……と返されます。
さてどういうことでしょうか
解説
これには「比較にまつわるPHPの言語仕様」が大きく関わっています。
strpos()の仕様について
先程説明した通り、strpos()は見つからなければfalseを返し、見つかった場合は0から始まる数値を返します。
問題なのは"0から"という所にあり、これがどのように解釈されるかで大きく変わります。
他の言語から入ってきた方は「そんなんtrueに決まってんじゃん」って思うじゃん?
違うんだなこれが
ちなみに同じことが公式ドキュメント上で警告されています。
警告
この関数は論理値 FALSE を返す可能性がありますが、FALSE として評価される値を返す可能性もあります。 詳細については 論理値の セクションを参照してください。この関数の返り値を調べるには ===演算子 を 使用してください。
strpos公式ドキュメントより引用
PHPの比較について
根本的にPHPにおいて比較演算子を省略したり、==を比較演算子として使用した場合超がつくユッルユルの結果を返してきます。
PHPの公式ドキュメント上で確認することができますが、
int(0)とstring "1"を突き合わせて**true**が帰ってくるくらい奇妙なことが起こります。
そして先程の見つからなかったと解される問題ですが、この緩い比較が悪さを働いています。
int型の**0という数値は真理値的にはfalse**として扱われます。
では単純に===による厳格な比較をすればよいのか、というも違います。
if(1 === true){
echo 'true扱い';
}
else {
echo 'false扱い';
}
これの吐いてくる出力はfalse扱いになります。
厳格な比較の場合、中身はもちろんこと型の情報まで全部同じでないとfalseとして扱います。
解決策
strpos()の仕様として
- 検索する値を見つけてきた場合は
int型の値である - 見つからなかった場合
bool型のfalseを返してくる
この2つは保証されているので、型の情報で区別させましょう。
そのためにはgettype()を使うのが一番手っ取り早いです。
gettype()は入力された値の型の種類を文字列で返してきます。
なので、これを利用してコードに書き起こす場合はこのようになります。
// 比較の際は可能な限り`===`の厳格な比較をするといいです
if(gettype(strpos("ruby is great.", "ruby")) === 'integer'){
echo "見つかったよ〜";
}
else {
echo "見つからなかった……";
}
これを実行するときちんと見つかったよ〜と返してきます。
文字列の加工は個人的にも頻繁に扱うので、ノウハウとして覚えておくと安全かつ簡単に実装できるかと思います。
追記(2019/07/08)
コメントにてご指摘を受けてから普通にアホだったことに気が付きました。なんということだ
- strposの条件判定
if(strpos("ruby is great.", "ruby") !== false){
echo "見つかったよ〜";
}
else {
echo "見つからなかった……";
}
単純にfalseかどうかを判定させればいいだけでした
-
gettype()の使い方
型を判定するためのものではないので、型で比較する場合は
is_int()を使ってくれ
とのことでした。これは知らなかったので知見。いずれにしてもミリしらで実装するの気をつけようと思います。ありがとうございました😂
余談
2年前くらい、プログラミング初心者だった私はPHPでTwitterのbotを開発していた時期がありました。その時ちょうどこのstrpos()の罠に引っかかってやめちゃったんですよね。
それから時は経ち、ここ最近になって業務でちょうどstrpos()に触るきっかけがあり、比較の解決としてこれにたどり着いたため「これで書こうかな」となった次第です。
この記事が同じ境遇に当たってしまった方々の助けに少しでもなれば幸いです。