多くの警告について、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 foreach
やCreating default object from empty value
あたりはよく見かけるのではないかと思います。
これらはPHP8では例外になって完全に動かなくなるので注意しましょう。
それ以外でも、ゆるふわぺちぱーに対する締め付けは年々厳しくなる一方で、彼らの肩身はどんどん狭まりつつあります。
かつてはPHP以上にアバウトで破壊と慈悲の混沌だったJavaScript界も、最近は型に嵌まっていないゆるふわJavaScripterを完全排除する流れができあがっています。
やがて彼らの居場所が完全に失われてしまったとき、難民たちはいったいどこに行くのでしょうね。