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


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

これは[Reclassifying engine warnings](https://wiki.php.net/rfc/engine_warnings)というRFCで受理されたものです。
提案者はいつものNikita。
影響の大きい未定義変数アクセスについては[個別に紹介](https://qiita.com/rana_kualu/items/4815d52f9885917af1a8)しましたが、ここではそこで紹介しなかった細かい警告について見ていきます。

これまで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

<font color="Red">E_WARNING → Error exception</font>

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

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

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

<font color="Red">E_WARNING → Error exception</font>

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

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

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

<font color="Red">E_WARNING → Error exception</font>

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

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

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


## Creating default object from empty value

<font color="Red">E_WARNING → Error exception</font>

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

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

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

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

```php
	$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

<font color="Blue">E_NOTICE → E_WARNING</font>

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

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


## Undefined property: %s::$%s

<font color="Blue">E_NOTICE → E_WARNING</font>

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

```php

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

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


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

<font color="Red">E_WARNING → Error exception</font>

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

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

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

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


## Cannot unset offset in a non-array variable

<font color="Red">E_WARNING → Error exception</font>

エラーの[出し方がわからない](https://github.com/php/php-src/blob/PHP-7.3.11/Zend/zend_execute.c#L1909)。


## Cannot use a scalar value as an array

<font color="Red">E_WARNING → Error exception</font>

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

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

文字列型の場合は[文字単位アクセス](https://www.php.net/manual/ja/language.types.string.php#language.types.string.substr)という正しい文法。

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

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

なぜかfalseでも動く。

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


## Trying to access array offset on value of type %s

<font color="Blue">E_NOTICE → E_WARNING</font>

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

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

このE_NOTICE自体[PHP7.4で追加された](https://twitter.com/nikita_ppv/status/1148931277216800769)もので、それ以前は何も出さずにnullを返していた。


## Only arrays and Traversables can be unpacked

<font color="Red">E_WARNING → TypeError exception</font>

関数呼び出し時の[引数展開](https://www.php.net/manual/ja/functions.arguments.php#functions.variable-arg-list)にiterableでない値を渡すと発生する。

```php
	var_dump(...1);
```

[unpack](https://www.php.net/manual/ja/function.unpack.php)とは特に関係ない。


## Invalid argument supplied for foreach()

<font color="Red">E_WARNING → TypeError exception</font>

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

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


## Illegal offset type

<font color="Red">E_WARNING → TypeError exception</font>

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

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


## Illegal offset type in isset or empty

<font color="Red">E_WARNING → TypeError exception</font>

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

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

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

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

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

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


## Illegal offset type in unset

<font color="Red">E_WARNING → TypeError exception</font>

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

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

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

```php
	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

<font color="ForestGreen">E_NOTICEのまま</font>

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

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

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

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


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

<font color="ForestGreen">E_NOTICEのまま</font>

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

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

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

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

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

```php
	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

<font color="ForestGreen">E_NOTICEのまま</font>

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

```php
	(int)new stdClass();
```

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

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

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


## A non-numeric value encountered

<font color="Blue">E_WARNINGのまま</font>

次項で一緒に解説する。

## A non well formed numeric value encountered

<font color="ForestGreen">E_NOTICEのまま</font>

[非数値文字列を数値演算](https://qiita.com/rana_kualu/items/476385b674d27339f911#warn-about-invalid-strings-in-arithmetic)すると発生する。

```php
    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`になる。
今回はエラーレベルが変わらないが、数値形式文字列の計算は安全のためキャストしておいた方がよいだろう。

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


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

<font color="ForestGreen">E_NOTICEのまま</font>

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

```php
	class A{
	    public static $property = 1;
	}

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

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


## Array to string conversion

<font color="Blue">E_NOTICE → E_WARNING</font>

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

```php
	(string)[];
```

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


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

<font color="Blue">E_NOTICE → E_WARNING</font>

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

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

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

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

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


## String offset cast occurred

<font color="Blue">E_NOTICE → E_WARNING</font>

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

```php
	'string'[1.5];
	'string'[true];
```

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


## Illegal string offset '%s'

<font color="Blue">E_WARNINGのまま</font>

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

```php
	'string'['a'];
```


## Uninitialized string offset: %d

<font color="Blue">E_NOTICE → E_WARNING</font>

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

```php
	'string'[10];
```


## Illegal string offset: %d

<font color="Blue">E_WARNINGのまま</font>

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

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

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

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


## Cannot assign an empty string to a string offset

<font color="Red">E_WARNING → Error exception</font>

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

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

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

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


## Only variables should be passed by reference

<font color="ForestGreen">E_NOTICEのまま</font>

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

```php
	sort([2, 1]);
```


## Only variable references should be returned by reference

<font color="ForestGreen">E_NOTICEのまま</font>

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

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


## Only variable references should be yielded by reference

<font color="ForestGreen">E_NOTICEのまま</font>

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

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

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


## Only variables should be assigned by reference

<font color="ForestGreen">E_NOTICEのまま</font>

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

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


## Attempting to set reference to non referenceable value

<font color="ForestGreen">E_NOTICEのまま</font>

[出し方がわからない](https://github.com/php/php-src/blob/PHP-7.3.11/Zend/zend_vm_def.h#L2074)どころか、[事例すら一切出てこない](https://www.google.com/search?q=%22Attempting+to+set+reference+to+non+referenceable+value%22)謎の警告。


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

<font color="Blue">E_WARNINGのまま</font>

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

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

いみがわからない。


## Division by zero

<font color="Red">E_WARNING → DivisionByZeroError exception</font>

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

```php
	1 / 0;
```

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


## Undefined variable

<font color="Blue">E_NOTICE → E_WARNING</font>

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

```php
	echo $a;
```

詳細は[個別記事](https://qiita.com/rana_kualu/items/4815d52f9885917af1a8)を参照のこと。


## Undefined array index

<font color="ForestGreen">E_NOTICEのまま</font>

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

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

詳細は[個別記事](https://qiita.com/rana_kualu/items/4815d52f9885917af1a8)を参照のこと。



# 感想

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

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

それ以外でも、ゆるふわぺちぱーに対する締め付けは[年々厳しくなる一方](https://qiita.com/rana_kualu/items/c1b5397f9232d4c38f91#%E6%84%9F%E6%83%B3)で、彼らの肩身はどんどん狭まりつつあります。
かつてはPHP以上にアバウトで[破壊と慈悲の混沌](https://qiita.com/rana_kualu/items/793f0cbdde6a88f86394)だったJavaScript界も、最近は型に嵌まっていないゆるふわJavaScripterを完全排除する流れができあがっています。
やがて彼らの居場所が完全に失われてしまったとき、難民たちはいったいどこに行くのでしょうね。