- はじめに
- php7ccで検出されない問題とは
- ハマった点1: count関数の挙動変更
- ハマった点2: $array[] で値を追加しようとして壊れる
- 苦労した点: mysql関数の置き換え
- 苦労した点2: PHPUnitのバージョンも上げないといけない
- 逆に嬉しかった点: 型アノテーションの強化
- まとめ
はじめに
この記事は PHPアドベントカレンダー 6日目の記事です。
皆さん、PHP 5.6のセキュリティサポートは2018年12月いっぱいで終了です。つまり、このアドベントカレンダーのカウントダウンがPHP 5.6終了へのカウントダウンなわけです。
PHP5.6からPHP7系に移行しようとしている皆さんはきっとphp7ccを使ってコードのチェックをしているかと思いますが、php7ccでは検出されず、実際に動かしてみないと気づかない問題もあります。
今回はそういったハマった点や苦労した点を紹介しようかと思います。*1
php7ccで検出されない問題とは
php7ccで検出されない問題って何?と思う方もいるかもしれません。私も最初は「php7cc通ったらPHP7で動くやろ」と思っていました、しかし、全然そんな事はありませんでした。
「メソッドが削除されたので置き換える必要がある」この場合はphp7ccで容易に検出が可能です。
一方で「メソッドの挙動が微妙に変わったため、特定のパターンで正常に動作しなくなっていた」こういう場合はphp7ccで検出されません。
ハマった点1: count関数の挙動変更
これは、PHP 7.1 -> 7.2 の間の変更なので、php7ccでも検出できない類のものなのですが、count関数にcountableインタフェースを実装していないオブジェクトを渡すとWarningを出すようになりました。
$values = null; count($values);
php7.1までは int(0)
が返っていたのですが、php7.2からは PHP Warning: count(): Parameter must be an array or an object that implements Countable
Warning が出るようになりました。戻り値としては int(0)
が返ってくるので、error_reporting関数でWarningを抑制していれば問題なく動作するのですが、僕の場合はWarningが出力されてしまっており、APIが壊れました。
nullableな値をcountしている場合は注意してください。今後fatalに変更される可能性もあるので、修正することをおすすめします。また、ライブラリでもこのバグを踏んでいるケースがありました。僕の場合は事前にたまたま気づけて、ライブラリをバージョンアップすることで解消されましたが、注意が必要です。
ハマった点2: $array[] で値を追加しようとして壊れる
かなり古いコードで以下のようなコードがありました。
$tag = ""; $tag[0] = "flag"; var_dump($tag);
これは、PHP5.6だとこうなります。
array(1) { [0]=> string(4) "flag" }
配列の添字アクセスだと判断されて、要素が追加されるわけです。では、PHP7.2だとどうなるでしょうか。
string(1) "f"
f!$tag[0]
が文字列の1文字目だと解釈され、そこに"flag"の"f"が突っ込まれてるのだと思います。ハマるのはレアケースですが気をつけましょう。
ちなみに
$tag = ""; $tag[] = "php5only"; var_dump($tag);
上記のコードはPHP5.6では
array(1) { [0]=> string(8) "php5only" }
こうなりますが、PHP7.2では
Fatal error: Uncaught Error: [] operator not supported for strings
Fatalになってしまいます。
$tag
が null や []
などで初期化された場合はどちらも通常の配列の動きをしますので、配列の初期化には気をつけましょう。
苦労した点: mysql関数の置き換え
やっぱりいちばん苦労した点はmysql関数の置き換えですね。すべてPDOで置き換えました。置き換え対応表は以下のとおりです。
置き換え前 | 置き換え後(PDO) | 備考 |
---|---|---|
mysql_real_escape_string($sql, $db) | $pdo->quote($sql) | PDOではエスケープしたうえで" でくくられるので注意 |
mysql_insert_id($db) | $pdo->lastInsertId() | http://php.net/manual/ja/pdo.lastinsertid.php |
mysql_fetch_assoc($result) | $statement->fetch(PDO::FETCH_ASSOC) | http://php.net/manual/ja/function.mysql-fetch-assoc.php |
mysql_query($sql, $db) | $pdo->query($sql) | http://php.net/manual/ja/function.mysql-query.php |
mysql_affected_rows($db) | $statement->rowCount() | http://php.net/manual/ja/function.mysql-affected-rows.php |
mysql_close($db) | $pdo = NULLまたは何もしない | http://php.net/manual/ja/function.mysql-close.php |
mysql_select_db | dbnameをconstructorに渡す必要がある | http://php.net/manual/ja/function.mysql-select-db.php |
mysql_connect(....) | new PDO(...) | http://php.net/manual/ja/function.mysql-connect.php |
mysql_free_result | $statement->closeCursor() | http://php.net/manual/ja/function.mysql-free-result.php |
mysql_unbuffered_query | 対応するPDOのメソッドは無い | http://php.net/manual/ja/function.mysql-unbuffered-query.php |
mysql_errno($db) | $pdo->errorInfo()[0]($pdo->errocode()とおなじ) または$pdo->errorInfo()[1] | 完全に対応するものは無い |
mysql_error($db) | $pdo->errorInfo()[2] | http://php.net/manual/ja/function.mysql-error.php |
mysql_num_rows($result) | $statement->rowCount() | http://php.net/manual/ja/function.mysql-num-rows.php |
mysql_result($result, int) | $statement->fetchColumn(int) | http://php.net/manual/ja/function.mysql-result.php |
mysql_fetch_array($result, $db) | $statement->fetch(PDO::FETCH_BOTH) |
特に、完全対応するメソッドが無いquoteや、mysql_unbuffered_queryの変更は苦労しました。また、mysql_queryは、クエリの実行に失敗すると return false
していましたが、PDOでは throw PDOException
になるので注意が必要です。PDOのconfigでこの挙動は変えられるようですが、あまり推奨はできないです。
苦労した点2: PHPUnitのバージョンも上げないといけない
PHP7.0と7.1はPHPUnit5以上、PHP7.2はPHPUnit6以上にする必要があります。PHPUnit5やPHPUnit6では重要な部分に破壊的変更が入っているので注意が必要です。詳細はまた別の機会がありましたら書こうかと思います。僕の環境はPHPUnit4で止まっていたため、バージョンアップにめっちゃ苦労しました。
逆に嬉しかった点: 型アノテーションの強化
型アノテーションが強化されたのが一番嬉しい点です。PHP7.1でプリミティブ型のアノテーションが追加され、PHP7.2で戻り値のアノテーションが追加されました。
function postData(int $id, array $data): ?string { ... }
PHP5.6でも array
はできていましたが、今回から int などのアノテーションができるようになりました。また上記のように戻り値のアノテーションもできます。さらには、型の前に?をつけることで nullable も表現できるようになりました。
ただし一点注意が必要で、intやstringのプリミティブなヒントは、それ以外の型が入った時にエラーになる わけではなく、型キャストを試みます。
まとめ
PHP7.2にするのは苦労も耐えないが型アノテーションの恩恵はでかい。
*1:まだPHP7移行完了してなくて、年末に向けてせっせと修正作業をしている方々がいたら絶望してしまうかもしれませんが、私も絶望しております...