Help us understand the problem. What is going on with this article?

【PHP8.0】PHP8で警告のエラーレベルが軒並み厳しくなる

多くの警告について、PHP8.0でエラーレベルが変更されます。

これはReclassifying engine warningsというRFCで受理されたものです。
提案者はいつものNikita。
影響の大きい未定義変数アクセスについては個別に紹介しましたが、ここではそこで紹介しなかった細かい警告について見ていきます。

これまでE_NOTICEだった警告の一部がE_WARNINGに、これまでE_WARNINGだった警告の一部が例外になります。
E_WARNINGを抑制するような書き方をしている場合、PHP8では動かなくなる可能性が高いので気をつけましょう。
現在E_NOTICE以下であればいきなり動かなくなることはありませんが、そもそも抑制する書き方がよくないので、なるべく修正した方がよいでしょう。

エラーレベルの変更がない警告も並んでいるので、もしかしたら全警告が列挙されてるのか?と思ったのですが、expected to be a %s, %s givenとか色々無いものもあるので、全てを出しているわけではないようです。
どういう基準なんだろうか?

Reclassifying engine warnings

Attempt to increment/decrement property '%s' of non-object

E_WARNING → Error exception

オブジェクトではない変数のプロパティをインクリメント/デクリメントすると発生する。

    $a = 1;
    $a->b++;

Attempt to modify property '%s' of non-object

E_WARNING → Error exception

オブジェクトではない変数のプロパティを変更すると発生する。

    $a = 1;
    $a->b['c'] = 1;

Attempt to assign property '%s' of non-object

E_WARNING → Error exception

オブジェクトではない変数にプロパティを追加すると発生する。

    $a = 1;
    $a->b = 1;

このへん全部同じでいいんじゃないか?

Creating default object from empty value

E_WARNING → Error exception

未定義の変数にプロパティを追加すると発生する。

    $a->b = 1;

PHP7.3では$aが定義されるのだが、PHP8では何も定義されなくなると思われる。

ところで未定義の変数のプロパティを変更しようとすると何の警告もなくオブジェクトが生成されるのだが、こっちは今後もいいのだろうか。

    $a->b['c'] = 1; // エラー出ない
    var_dump($a); // object(stdClass)#1 (1) { ["b"]=> array(1) { ["c"]=> int(1) } }

Trying to get property '%s' of non-object

E_NOTICE → E_WARNING

オブジェクトではない変数のプロパティを参照すると発生する。

    $a = 1;
    $a->b;

Undefined property: %s::$%s

E_NOTICE → E_WARNING

オブジェクトの未定義プロパティを参照すると発生する。

$a = new stdClass();
$a->b;

PHPの場合、入力として外部引数やらAPIやらを使うことが多いため、読み取りの失敗については書き込みより寛容気味。

Cannot add element to the array as the next element is already occupied

E_WARNING → Error exception

配列の自動挿入による整数キーがPHP_INT_MAXを超えたときに発生する。

    $a = [
        PHP_INT_MAX => 1,
    ];
    $a[] = 2;

ちなみに計算値で指定すれば、PHP_INT_MAXを超えていてもいける。

    $a = [
        PHP_INT_MAX => 1,
    ];
    $a[PHP_INT_MAX+1] = 2; // -2147483648とかになる

Cannot unset offset in a non-array variable

E_WARNING → Error exception

エラーの出し方がわからない

Cannot use a scalar value as an array

E_WARNING → Error exception

文字列型ではないスカラー型の変数に配列値を追加すると発生する。

    $a = true;
    $a[] = 1;

文字列型の場合は文字単位アクセスという正しい文法。

ちなみにnullで初期化した場合は問題なく動く。

    $a = null;
    $a[] = 1; // [ 0 => 1]

なぜかfalseでも動く。

    $a = false;
    $a[] = 1; // [ 0 => 1]

Trying to access array offset on value of type %s

E_NOTICE → E_WARNING

文字列型ではないスカラー型の変数を配列形式で読み込もうとすると発生する。

    $a = true;
    $a[1];

このE_NOTICE自体PHP7.4で追加されたもので、それ以前は何も出さずにnullを返していた。

Only arrays and Traversables can be unpacked

E_WARNING → TypeError exception

関数呼び出し時の引数展開にiterableでない値を渡すと発生する。

    var_dump(...1);

unpackとは特に関係ない。

Invalid argument supplied for foreach()

E_WARNING → TypeError exception

iterableでない値をforeachすると発生する。

    $a = 1;
    foreach($a as $loop){}

Illegal offset type

E_WARNING → TypeError exception

配列のキーに配列やオブジェクトを指定すると発生する。

$a = [
    new stdClass() => 1,
    [] => 2,
];

Illegal offset type in isset or empty

E_WARNING → TypeError exception

issetおよびemptyでチェックする配列のキーに配列やオブジェクトを指定すると発生する。

    $a = [];
    isset($a[new stdClass()]);

ちなみに$aが未定義やスカラー型の場合は何のエラーも起こらない。

    isset($a[new stdClass()]); // エラー出ない
    $a = 1;
    isset($a[new stdClass()]); // エラー出ない

未定義やint型等であれば配列形式アクセスした時点でfalseだから中身を見る必要もないというのはわかるが、文字列型でもエラーが出ない理由はよくわからない。

    $a = 'a';
    isset($a[1]); // true
    isset($a[new stdClass()]); // false エラー出ない

Illegal offset type in unset

E_WARNING → TypeError exception

unsetする配列のキーに配列やオブジェクトを指定すると発生する。

    $a = [];
    unset($a[new stdClass()]);

$aが未定義の場合Illegal offset typeは発生しないが、かわりにUndefined variableのE_NOTICEが出る。
文字列以外のスカラー型には何のエラーも出さず、文字列型やオブジェクトにはFatal errorが発生する。

    unset($a[new stdClass()]); // E_NOTICE: Undefined variable
    $a = 1;
    unset($a[new stdClass()]); // エラー出ない
    $a = 'a';
    unset($a[new stdClass()]); // Fatal error: Cannot unset string offsets
    $a = new stdClass();
    unset($a[new stdClass()]); // Fatal error: Cannot use object of type stdClass as array

このあたりの法則はさっぱりわからない。

Indirect modification of overloaded element of %s has no effect

E_NOTICEのまま

SplFixedArrayに突っ込んだ配列の値を直接変更すると発生する。

    $a = new SplFixedArray(1);
    $a[0] = [1];
    $a[0][0] = 2;

値を変更しているつもりだが、実際には変更されていないという注意。

SplFixedArrayに限らず、ArrayAccessをimplementsしたクラスに一般的に発生する症状のようだ。

Indirect modification of overloaded property %s::$%s has no effect

E_NOTICEのまま

マジックメソッド__getが配列を返す場合、その返り値を直接変更すると発生する。

    class A{
        private $value = ['a' => 1, 'b' => 2];
        public function __get($k){
            return $this->value;
        }
    }

    $a = new A;
    $a->value['a'] = 3;

こちらも値を変更したつもりだが、実際には変更されていない。

なお配列ではなくオブジェクトであれば、エラーも出ないし値を直接変更できてしまう。

    class A{
        private $obj;
        public function __construct(){
            $this->obj = new stdClass();
        }
        public function __get($k){
            return $this->obj;
        }
    }

    $a = new A;
    $a->obj->b = 1;

    var_dump($a); // { 'obj' => stdClass{ 'b'=>1 } }

Object of class %s could not be converted to int/float/number

E_NOTICEのまま

オブジェクトをスカラー型にキャストすると発生する。

    (int)new stdClass();

緩い比較が内部的にこのキャストを使用しているため、オブジェクトとスカラー型を緩く比較するとE_NOTICEが発生する。

    $a = new stdClass();
    var_dump($a == 1); // E_NOTICE
    var_dump($a === 1); // エラー出ない

比較ではエラーが出るべきではないので、こちらの問題がどうにかなるまでエラーレベルを変更しない。

A non-numeric value encountered

E_WARNINGのまま

次項で一緒に解説する。

A non well formed numeric value encountered

E_NOTICEのまま

非数値文字列を数値演算すると発生する。

    1 + '1';  // エラー出ない
    1 + '1a'; // E_NOTICE: A non well formed numeric value encountered
    1 + 'a';  // E_WARNING: A non-numeric value encountered

完全に数値形式の文字列ではエラーは出ず、一部だけ数値として評価できるときはnon well formed numeric value、完全に数値でない場合はnon-numeric valueになる。
今回はエラーレベルが変わらないが、数値形式文字列の計算は安全のためキャストしておいた方がよいだろう。

    1 + (int)'a'; // エラー出ない

Accessing static property %s::$%s as non static

E_NOTICEのまま

staticプロパティにインスタンスからアクセスすると発生する。

    class A{
        public static $property = 1;
    }

    $a = new A();
    $a->property;

正しくは$a::$property、もしくはA::$property
インスタンス内部からであればself::$propertyもいける。

Array to string conversion

E_NOTICE → E_WARNING

配列を文字列型にキャストすると発生する。

    (string)[];

変換前の配列の中身がどうなっていたとしても変換後の文字列はArrayになるので、実質的に機能していない状態なのでExceptionでもいい気がする。

Resource ID#%d used as offset, casting to integer (%d)

E_NOTICE → E_WARNING

リソースIDを配列のキーとして使用すると発生する。

    $fp = fopen('hoge', 'w+');
    $array = [$fp => $fp];
    var_dump($array); // []

リソースIDは整数っぽい値であり、かつプログラム中ではユニークなので、このような使い方ができそうではあるが実際は動いていない。
明示的にキャストするとint型になるため警告は発生せず、正しく動作する。

    $fp = fopen('./hoge', 'w+');
    $array = [(int) $fp => $fp];
    var_dump($array); // [1=>resource]

そもそも動いてないので、これもいきなりExceptionでいい気がしないでもない。

String offset cast occurred

E_NOTICE → E_WARNING

文字列への角括弧オフセットアクセスのキーに整数ではない数値を使ったときに発生する。

    'string'[1.5];
    'string'[true];

下の項目と同じような内容なのでエラーレベルを揃えたという話のようだ。

Illegal string offset '%s'

E_WARNINGのまま

文字列への角括弧オフセットアクセスのキーに数値ではない値を使ったときに発生する。

    'string'['a'];

Uninitialized string offset: %d

E_NOTICE → E_WARNING

文字列への角括弧オフセットアクセスで範囲外の値を読み込もうとしたときに発生する。

    'string'[10];

Illegal string offset: %d

E_WARNINGのまま

文字列への角括弧オフセットアクセスでマイナスの範囲外の値を変更したときに発生する。

    $str = 'string';
    $str[-10] = 'a';

正の範囲外を変更したときは単に文字列が伸びるだけでエラーは発生しない。

    $str = 'string';
    $str[10] = 'a'; // 'string    a'

Cannot assign an empty string to a string offset

E_WARNING → Error exception

文字列への角括弧オフセットアクセスで値を空文字に変更しようとしたときに発生する。

    $str = 'string';
    $str[1] = '';

2文字以上与えた場合は2文字目以降が無視されるだけでエラーは発生しない。

    $str = 'string';
    $str[1] = 'abcde'; // 'saring'

Only variables should be passed by reference

E_NOTICEのまま

リファレンス関数に値を直接渡すと発生する。

    sort([2, 1]);

Only variable references should be returned by reference

E_NOTICEのまま

リファレンス返しで値を直接返すと発生する。

    function &ref(){
        return 1;
    }
    ref();

Only variable references should be yielded by reference

E_NOTICEのまま

リファレンス返しで値を直接yieldすると発生する。

    function &ref(){
        yield 1;
    }
    foreach(ref() as $v);

リファレンス返しは百害しかないので使用してはならない。

Only variables should be assigned by reference

E_NOTICEのまま

リファレンスではない関数をリファレンスで受け取ろうとすると発生する。

    function ref(){
        return 1;
    }
    $x = &ref();

Attempting to set reference to non referenceable value

E_NOTICEのまま

出し方がわからないどころか、事例すら一切出てこない謎の警告。

Cannot pass by-reference argument %d of %s%s%s() by unpacking a Traversable, passing by-value instead

E_WARNINGのまま

参照渡し関数にTraversableな値を引数アンパックして渡すと発生する。

    function ref(&$var){}
    ref(...new ArrayIterator([1]));

いみがわからない。

Division by zero

E_WARNING → DivisionByZeroError exception

数値を0で割ると発生する。

    1 / 0;

PHP7.4までは計算結果がfloat(INF)になる。

Undefined variable

E_NOTICE → E_WARNING

未定義変数を参照すると発生する。

    echo $a;

詳細は個別記事を参照のこと。

Undefined array index

E_NOTICEのまま

配列の未定義キーを参照すると発生する。

    $a = [];
    echo $a[1];

詳細は個別記事を参照のこと。

感想

そもそもどうすれば出せるのかすらわからないエラーがあった。

警告に寛容なプログラミングをしている場合、Invalid argument supplied for foreachCreating default object from empty valueあたりはよく見かけるのではないかと思います。
これらはPHP8では例外になって完全に動かなくなるので注意しましょう。

それ以外でも、ゆるふわぺちぱーに対する締め付けは年々厳しくなる一方で、彼らの肩身はどんどん狭まりつつあります。
かつてはPHP以上にアバウトで破壊と慈悲の混沌だったJavaScript界も、最近は型に嵌まっていないゆるふわJavaScripterを完全排除する流れができあがっています。
やがて彼らの居場所が完全に失われてしまったとき、難民たちはいったいどこに行くのでしょうね。

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
Comments
No comments
Sign up for free and join this conversation.
If you already have a Qiita account
Why do not you register as a user and use Qiita more conveniently?
You need to log in to use this function. Qiita can be used more conveniently after logging in.
You seem to be reading articles frequently this month. Qiita can be used more conveniently after logging in.
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
ユーザーは見つかりませんでした