WebエンジニアのLoL日記

LoLをプレイしたりLJLの試合を見たりするのが好きなエンジニア。LoLのイベントやパッチノートなど気になった点を記事にしたり、LJLについの記事をかいたりしています。某社でWeb系のエンジニアとして働いているので、技術系の記事もたまに書きます。コンタクトを取りたい場合はtwitterまで。

PHPを5.6から7.2にする際に苦労した点やハマった点、嬉しかった点

f:id:yoshiki_utakata:20181004212834j:plain

はじめに

この記事は 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移行完了してなくて、年末に向けてせっせと修正作業をしている方々がいたら絶望してしまうかもしれませんが、私も絶望しております...