PHP8では、それまで警告で済んでいたものがエラーになるものが多くあります。
(エラー例外をスローする。)
個々の変更点では説明されてたんですが、まとまったものがありませんでした。やっと見つけたので、エラー、警告、通知の変更点のレジュメをご紹介します。
2020年11月26日、5年ぶりにPHPのメジャーバージョンが上がりました。PHP8です。
メジャーアップデートだけに変更点も大きいです。ログ出力されるエラー、警告、通知も見直され変更されました。
PHPの公式ドキュメントでは、個々の変更点で『警告からエラーに変わる』『エラー例外をスローする』と説明されますが、エラー、警告、通知の変更の全体像が見えません。
これらのエラーまわりの概要(レジュメ)をWordPressのPHP8対応ドキュメントで見つけました。
今回はそれを和訳してサンプルコードを付けてご紹介します。
PHP公式のRFCもありますが、マニアックなものとよく出るものが一緒になっているので、分かりづらい。
WordPressのほうは、よく出るものをピックアップしているので、まずはこっちから見たほうがいいです。
エラーへの変更が多い
PHP8では、これまで警告(Warning)で済んでいたものがエラーになっているものが多いです。
また、通知(Notice)から警告へ変更になっているものも。
PHP8にアップグレードしても、ログを頻繁にチェックし修正して、ログ内容ををきれいにしていた人はあまり影響ないですが、放置している人は動かなくなると思っていい。
PHP8では『それなりに(なんとなく)動く』ものを厳しく取り締まっています。
ちゃんとしてた人は楽で、そうじゃない人は痛い目にあう、子どもの夏休みの宿題みたい。
今回は、一般的に遭遇するであろう部分をピックアップしています。これが全てではないことに注意して下さい。
警告(Warning)からエラー例外に変わったもの
PHP8に上げると動かなくなる原因はここに集約されます。通知(Notice)から警告(Warning)に変わったものは一応動くので。
動かなくなったときの主な原因として参考にしてください。
Non-objectのプロパティの変更
PHP7までは、クラスオブジェクトじゃない変数をクラスオブジェクトと見立ててもエラーではありません。
<?php
$data = '';
var_dump('before:');
var_dump($data->sample);
$data->sample = 1;
var_dump('after:');
var_dump($data->sample);
php sample.php
string(7) "before:"
PHP Notice: Trying to get property 'sample' of non-object in /home/vagrant/sample.php on line 6
NULL
PHP Warning: Creating default object from empty value in /home/vagrant/sample.php on line 8
string(6) "after:"
int(1)
PHP7では処理がつづきます。空文字列をクラスオブジェクトとして参照したら、stdClassオブジェクトに変換してくれるありがた迷惑も。
クラスオブジェクトじゃない変数でオブジェクトのプロパティを参照するとnullやfalseになります。
php80 sample.php
string(7) "before:"
PHP Warning: Attempt to read property "sample" on string in /home/vagrant/sample.php on line 6
NULL
PHP Fatal error: Uncaught Error: Attempt to assign property "sample" on string in /home/vagrant/sample.php:8
Stack trace:
#0 {main}
thrown in /home/vagrant/sample.php on line 8
PHP8では、クラスオブジェクトじゃない変数をクラスオブジェクトと見立て、さらにあるはずのないプロパティを変更しようとすると、Error例外をスローします。
データの型を常に意識している人からすると、今までエラーじゃなかったほうが怖い。
ややこしいのはプロパティ値の変更はエラーだけど、見るだけなら警告で済むこと。(後述)
これ、どうにかしてほしい。
PHP_INT_MAXを使った配列に要素を追加
PHP_INT_MAXはint型が表現できる最大値の定数です。配列の要素数の限界値にも使われます。
PHP7では、限界の要素数をもつ配列に要素を追加すると警告が出ていました。
(もちろん内容は追加できないことが出る。)
PHP8ではError例外をスローします。
<?php
$data[PHP_INT_MAX] = 'sample';
array_push( $data, 'sample2' );
php sample.php
PHP Warning: array_push(): Cannot add element to the array as the next element is already occupied in /home/vagrant/sample.php on line 5
php80 sample.php
PHP Fatal error: Uncaught Error: Cannot add element to the array as the next element is already occupied in /home/vagrant/sample.php:5
Stack trace:
#0 /home/vagrant/sample.php(5): array_push()
#1 {main}
thrown in /home/vagrant/sample.php on line 5
配列のキーに配列やクラスオブジェクトを使う
配列のキーに不適切なタイプのキー値(配列とクラスオブジェクト)を使うと、エラーに変わります。
PHP8では、TypeError例外をスローします。
<?php
$key = ['array'];
$data = [ $key => 'sample' ];
php sample.php
PHP Warning: Illegal offset type in /home/vagrant/sample.php on line 4
php80 sample.php
PHP Fatal error: Uncaught TypeError: Illegal offset type in /home/vagrant/sample.php:4
Stack trace:
#0 {main}
thrown in /home/vagrant/sample.php on line 4
スカラー値の配列インデックスへの書き込み
ちょっと意味がわからない。
参考にしたドキュメントを直訳したんですが、こんなことする?
スカラー値とはint, float, double, bool, stringの型の値のことなんですが、そもそも配列のキーを直接変更したことがないので。
(キーを変えるとき、元データから作り直して別の変数に突っ込んだりはするが。)
『オレはやってるぞ!』という人には、PHP8ではエラーになりますよ? とだけ伝えておきます。
で、参考元では、くわしくはPHPのRFCを見てくれっていうので見ると、
Cannot use a scalar value as an array
についての記述が。スカラー関連はこれだけ。多分これのことでしょう。
<?php
$data = 1;
$data[2] = 'sample';
php sample.php
PHP Warning: Cannot use a scalar value as an array in /home/vagrant/sample.php on line 4
php80 sample.php
PHP Fatal error: Uncaught Error: Cannot use a scalar value as an array in /home/vagrant/sample.php:4
Stack trace:
#0 {main}
thrown in /home/vagrant/sample.php on line 4
スカラー型の値を入れた変数をむりやり配列変数にしちゃったらダメってこと。ちなみに、スカラー型でもstringはエラーになりません。
(PHP7は警告・通知なし。PHP8は別の警告が出る。)
PHP Warning: Only the first byte will be assigned to the string offset
参考元の英文はこれ。
Attempting to write to an array index of a scalar value.
で和訳がこれ。
スカラー値の配列インデックスに書き込もうとしています。
訳がまちがってるのか、英文を書いた人の表現がまちがってるのか分からない。
これ以上突っ込むのはやめよう。
stringがエラーにならないのは、文字列オフセットがあるから。
文字列オフセットは、文字列の中の一文字の位置(オフセット)という意味で、文字列変数を配列のように[]を付けて任意の一文字を参照できる。
さっきの警告は、『一文字しか代入できないのに文字列を入れようとしているよ?』というもの。
配列やTraversable以外で展開しようとする
Traversableは、イテレータ機能のインタフェースの親玉で、Iteratorインターフェイスなどの大元。
ようは、グルグル回せるものを期待しているのに、回せないものを渡されてもどうしようもないよ? ってこと。
このサンプルをどうしようか結構探しました。for文では別のエラーが出るので。
そこで見つけたのがこれ。
<?php
function iterator( ...$args ) {
foreach( $args as $val ) {
var_dump( $val );
}
}
$aaa = [ 2, 3, 4 ];
iterator( 1, ...$aaa );
iterator( $aaa, ...1 );
php sample.php
int(1)
int(2)
int(3)
int(4)
PHP Warning: Only arrays and Traversables can be unpacked in /home/vagrant/sample.php on line 12
array(3) {
[0]=>
int(2)
[1]=>
int(3)
[2]=>
int(4)
}
最初のiterator()では、配列に "..." を付けているので、$aaaは複数パラメータとして展開(アンパック)します。
2番めのiterator()では、配列じゃないもの(1)に "..." が付いてるので警告が出ます。
php80 sample.php
int(1)
int(2)
int(3)
int(4)
PHP Fatal error: Uncaught TypeError: Only arrays and Traversables can be unpacked in /home/vagrant/sample.php:12
Stack trace:
#0 {main}
thrown in /home/vagrant/sample.php on line 12
PHP8では、アンパックできないものはTypeError例外をスローします。
ちなみに、unpack() は関係ありません。パラメータ(引数)の分割・展開のこともアンパックと言います。
(これがややこしくて意味が分かるのに時間がかかった。)
空文字を文字列オフセットに代入
文字列オフセットと聞くと難しく思えますが、ようは文字列の一文字の位置(オフセット)に代入すること。
<?php
$tmp = '-----';
$tmp[ 0 ] = 1;
$tmp[ 1 ] = 2.345;
var_dump( $tmp );
$tmp[ 2 ] = '';
$tmp[ 3 ] = false;
$tmp[ 4 ] = null;
var_dump( $tmp );
php sample.php
string(5) "12---"
PHP Warning: Cannot assign an empty string to a string offset in /home/vagrant/sample.php on line 7
PHP Warning: Cannot assign an empty string to a string offset in /home/vagrant/sample.php on line 8
PHP Warning: Cannot assign an empty string to a string offset in /home/vagrant/sample.php on line 9
string(5) "12---"
php80 sample.php
PHP Warning: Only the first byte will be assigned to the string offset in /home/vagrant/sample.php on line 5
string(5) "12---"
PHP Fatal error: Uncaught Error: Cannot assign an empty string to a string offset in /home/vagrant/sample.php:7
Stack trace:
#0 {main}
thrown in /home/vagrant/sample.php on line 7
PHP8では空文字を代入するとError例外をスローします。false, nullを代入しても同じ。
trueを代入すると1が入り、floatは小数点が切り捨てられた整数が入ります。
通知(Notice)から警告(Warning)への変更
通知と警告ではログに出力されるレベルがちがうだけで気にしない人もいます。
が警告への変更は、絶対に放置しないほうがいい。
警告は将来、エラーになる予定のものです。(いつなるかは分からないが。)
エラーになって動かなくなる前に潰せるものは潰しておきましょう。
今回の変更は、C言語やJavaをしている人からすると、『なんでエラーにならないの?』と戸惑うものばかりです。
警告に昇格するということはそっちへ寄っていくのでしょう。
未定義変数の読み取り
これはかんたんですね?
変数を宣言していない初期値を入れていない変数をいきなり使おうとすると出ます。
<?php
//$aaa = 'sample';
$bbb = $aaa;
php sample.php
PHP Notice: Undefined variable: aaa in /home/vagrant/sample.php on line 4
php80 sample.php
PHP Warning: Undefined variable $aaa in /home/vagrant/sample.php on line 4
コメント化した変数の初期値代入を復活させればOK。
また、変数に初期値を入れないのは、C言語からスタートしたロートルからするとありえません。
(C言語では、変数に割り当てられたメモリ領域にデータの残骸があって、意味不明の初期値が入ることがよくあった。)
今はどうなんでしょう?
(昔はプログラミングの初歩の初歩として叩き込まれた。)
知らなかった人は、ここまで気にして書いてる人がいますよ? ということを知っておいてください。
(C言語やJavaではそもそも、変数宣言をしないとエラーになる。)
未定義のプロパティの読み取り
今度は、クラスオブジェクトの変数、プロパティの未定義の読み取りです。同じように警告が出るようになりました。
<?php
class Test {
public $aaa = 1;
}
$test = new Test();
$aaa = $test->aaa;
$bbb = $test->bbb;
php sample.php
PHP Notice: Undefined property: Test::$bbb in /home/vagrant/sample.php on line 9
php80 sample.php
PHP Warning: Undefined property: Test::$bbb in /home/vagrant/sample.php on line 9
JavaなどからPHPへ入ってきた人はびっくりしないでください。このユルユル加減が、PHPのストロングポイントでありウィークポイントです。
未定義の配列キーの読み取り
次は、配列の未定義領域を読んだときの警告です。
<?php
$list = [ 1, 2, 3 ];
$aaa = $list[5];
$list = [
'first' => 'sample1',
'second' => 'sample2',
'third' => 'sample3',
];
$bbb = $list[ 'last' ];
php sample.php
PHP Notice: Undefined offset: 5 in /home/vagrant/sample.php on line 4
PHP Notice: Undefined index: last in /home/vagrant/sample.php on line 11
php80 sample.php
PHP Warning: Undefined array key 5 in /home/vagrant/sample.php on line 4
PHP Warning: Undefined array key "last" in /home/vagrant/sample.php on line 11
非オブジェクトのプロパティ読み取り
クラスオブジェクトの変数じゃないのに、オブジェクト変数だと思ってプロパティを読んじゃった場合です。
<?php
$list = [ 1, 2, 3 ];
$aaa = $list->aaa;
$list = 1;
$bbb = $list->bbb;
php sample.php
PHP Notice: Trying to get property 'aaa' of non-object in /home/vagrant/sample.php on line 4
PHP Notice: Trying to get property 'bbb' of non-object in /home/vagrant/sample.php on line 7
php80 sample.php
PHP Warning: Attempt to read property "aaa" on array in /home/vagrant/sample.php on line 4
PHP Warning: Attempt to read property "bbb" on int in /home/vagrant/sample.php on line 7
PHP8ではメッセージの内容も変わり、分かりやすくなっています。
ロートルの口うるさい戯言として聞いてください。かつては、変数名に型を入れて一目瞭然にする方法を使ったりしていました。
int_id
short_id
bool_id
str_name
array_names
今はそんなことしなくていいです。今は、PHPDocのコメントをきちんと書くようにしてください。
プログラミングのエディタでは、変数の型、関数のパラメータ・戻り値の型がヒントとして表示されるので確認できます。
非配列の配列インデックスへのアクセス
配列じゃない変数なのに、配列としてアクセスした場合です。
<?php
$aaa = 1;
$tmp = $aaa[ 0 ];
$tmp = $aaa[ 3 ];
$bbb = 'sample';
$tmp = $bbb[ 0 ];
var_dump( $tmp );
$tmp = $bbb[ 2 ];
var_dump( $tmp );
php sample.php
PHP Notice: Trying to access array offset on value of type int in /home/vagrant/sample.php on line 4
PHP Notice: Trying to access array offset on value of type int in /home/vagrant/sample.php on line 5
string(1) "s"
string(1) "m"
php80 sample.php
PHP Warning: Trying to access array offset on value of type int in /home/vagrant/sample.php on line 4
PHP Warning: Trying to access array offset on value of type int in /home/vagrant/sample.php on line 5
string(1) "s"
string(1) "m"
ひとつ注意点が。stringはchar型の配列かのように認識されます。
(配列の要素には1文字ずつ入る。)
警告は出ません。
配列の[]とstringの[]は同じ書き方でも意味が違います。
(stringの[]を文字列オフセットという。)
string型はchar型の配列かのように見えますが、まったくちがうもの。
ただ書き方が同じだけ。
配列を文字列に変換
さっき『stringはchar型の配列かのように認識される』と言いましたが、配列を文字列に強制型変換(キャスト)はできません。
これがややこしい。
<?php
$str = [ 's', 'a', 'm', 'p', 'l', 'e' ];
$tmp = (string)$str;
var_dump($tmp);
$tmp = implode( $str );
var_dump($tmp);
php sample.php
PHP Notice: Array to string conversion in /home/vagrant/sample.php on line 4
string(5) "Array"
string(6) "sample"
php80 sample.php
PHP Warning: Array to string conversion in /home/vagrant/sample.php on line 4
string(5) "Array"
string(6) "sample"
恐ろしいのは、キャストすると結果が "Array" という文字列になってしまうこと。
値が変わってんじゃん!
PHP8でも同じ。
せめて値は変えないで欲しい。変わるならエラーになってほしい。
配列からstringへの変換は implode() を使います。
リソースを配列のキーとして使ってしまう
リソースとは、リソースIDのことで、外部とのコネクティング関数の戻り値として使われる特殊な型です。
たとえば、ファイル操作やDB操作の関数など。
値はintegerなんですが、型はそれぞれ特殊な型として用意されています。
<?php
$handle = fopen( './test.text', 'r+' );
$handles = [ $handle => './test.text' ];
fclose( $handle );
php sample.php
PHP Notice: Resource ID#5 used as offset, casting to integer (5) in /home/vagrant/sample.php on line 5
php80 sample.php
PHP Warning: Resource ID#5 used as offset, casting to integer (5) in /home/vagrant/sample.php on line 5
メッセージの内容を見ると、配列のキーに使うとダメというより、特殊な型をintegerに型変換するのがダメらしい。
(配列のキーは数値、数字はintegerに強制型変換する。)
ということはキャストもダメ。
fopen() はファイル操作でよく使われる関数です。これの戻り値の型がintegerじゃなかったというのを知らない人もいるんじゃないでしょうか?
(ボクは知らなかった。C言語のクセが抜けなくて。)
null, boolean, float を文字列オフセットで使おうとした
文字列オフセットというと難しく思えますが、ようは文字列の中の一文字の位置(オフセット)で整数値以外を使うな!ってこと。
<?php
$tmp = 'abc';
var_dump( $tmp[ 1 ] );
var_dump( $tmp[ null ] );
var_dump( $tmp[ false ] );
var_dump( $tmp[ true ] );
var_dump( $tmp[ 1.234 ] );
var_dump( $tmp[ 1.567 ] );
php sample.php
string(1) "b"
PHP Notice: String offset cast occurred in /home/vagrant/sample.php on line 5
string(1) "a"
PHP Notice: String offset cast occurred in /home/vagrant/sample.php on line 6
string(1) "a"
PHP Notice: String offset cast occurred in /home/vagrant/sample.php on line 7
string(1) "b"
PHP Notice: String offset cast occurred in /home/vagrant/sample.php on line 8
string(1) "b"
PHP Notice: String offset cast occurred in /home/vagrant/sample.php on line 9
string(1) "b"
php80 sample.php
string(1) "b"
PHP Warning: String offset cast occurred in /home/vagrant/sample.php on line 5
string(1) "a"
PHP Warning: String offset cast occurred in /home/vagrant/sample.php on line 6
string(1) "a"
PHP Warning: String offset cast occurred in /home/vagrant/sample.php on line 7
string(1) "b"
PHP Warning: String offset cast occurred in /home/vagrant/sample.php on line 8
string(1) "b"
PHP Warning: String offset cast occurred in /home/vagrant/sample.php on line 9
string(1) "b"
ややこしいのは、結果がそれなりに出るということ。
オフセット値 | |
---|---|
null | 0 |
boolean | true -> 1 false -> 0 |
float | 小数点を切り捨て |
内部で、integerに強制型変換、キャストしてるのでしょう。
個人的にはエラーにして欲しい。
範囲外の文字列オフセットを読もうとした
これは一目瞭然、百聞は一見にしかずです。
<?php
$tmp = 'abc';
var_dump( $tmp[ 0 ] );
var_dump( $tmp[ 1 ] );
var_dump( $tmp[ 2 ] );
var_dump( $tmp[ 3 ] );
var_dump( $tmp[ -1 ] );
php sample.php
string(1) "a"
string(1) "b"
string(1) "c"
PHP Notice: Uninitialized string offset: 3 in /home/vagrant/sample.php on line 7
string(0) ""
string(1) "c"
php80 sample.php
string(1) "a"
string(1) "b"
string(1) "c"
PHP Warning: Uninitialized string offset 3 in /home/vagrant/sample.php on line 7
string(0) ""
string(1) "c"
当然です。文字列長が超えてるところを見てるんだから。
結果は空文字になります。ちなみに、オフセットをマイナス指定すると符号を+に変換します。
ぜんぶを網羅したドキュメントはない
WordPressのドキュメントはすべてのエラー・警告・通知についてまとめられていません。
PHP公式のRFCも同じ。
そして、PHP8の個々の変更点のところにしか書いてないものもあります。
すべてを網羅したドキュメントはありません。エラーや警告の内容でググってしかるべきドキュメントを見るしかないでしょう。
(PHP公式ドキュメントがベスト)
今回書いたものはその手助けになれば幸いです。