PHP8では、配列やクラスオブジェクトの算術演算、ビット演算で型チェックが厳しくなりました。
警告だったものがエラーになります。
PHP8はデータの厳格化がポイントなので演算にも影響します。
『そんな計算ないわー』と思っていた人にとっては当たり前の話。
2020年11月26日、5年ぶりにPHPのメジャーバージョンが上がりました。PHP8です。
メジャーアップデートだけに変更点も大きいです。配列やクラスオブジェクトの算術演算、ビット演算の型チェックを厳しくしたのもそう。
PHP7までも、『そんな計算ないっしょ』だったものはエラーになっていましたが、一部、警告で済んでいたものがありました。
PHP8では、ありえない計算をしたものはUncaught TypeErrorの例外をスローするように統一されています。
言葉で説明するよりも、実際に動かしたほうが『百聞は一見にしかず』なので、サンプルコードで進めていきます。
配列の算術演算
これまでも配列の算術演算はできました。足し算(+)だけは。
配列で足し算をすると配列の結合になります。これはPHP8でも変わりません。
そのほかのありえない算術演算の挙動がPHP8から変わります。
配列の結合(+)
配列の結合の結果は変わりません。
<?php
var_dump( [ 1, 2 ] + [ 10, 20, 30 ] );
array(3) {
[0]=>
int(1)
[1]=>
int(2)
[2]=>
int(30)
}
- , * , / : ErrorからTypeErrorへ変更
引き算(-)、掛け算(*)、割り算(/)はPHP7でもエラーですが、PHP8ではエラーの種類が変わりUncaught TypeErrorになります。
<?php
var_dump( [ 1, 2 ] - [ 10, 20, 30 ] );
PHP Fatal error: Unsupported operand types in /home/vagrant/sample.php on line 3
PHP Fatal error: Uncaught TypeError: Unsupported operand types: array - array in /home/vagrant/sample.php:3
Stack trace:
#0 {main}
thrown in /home/vagrant/sample.php on line 3
サンプルは引き算ですが、掛け算、割り算でも同じ結果になります。
正確には、PHP8でのTypeErrorは例外です。投げられた例外をキャッチして独自のエラーロジックを書くことができる。
『配列とintではどうか?』と思うかもしれませんが、どちらかの計算対象の型(オペランド)が配列だったら同じ結果になります。
% , ** : ややこしかった挙動がエラー例外に
剰余(%, 〇〇を△△で割った余り)、累乗(**, 〇〇の△△乗)はこれまで、ややこしい動きをしていました。
<?php
var_dump( [ 1, 2 ] % [ 10, 20, 30 ] );
int(0)
ありえない計算をしているのに結果は整数の0。
言わんとすることは分かる。曖昧な比較(==)ではfalseを返しているのと一緒だし。
ただ剰余では正しい計算の結果0との区別がつきません。
(余りが0と結果が同じ。)
この不都合な結果がPHP8では修正され、Uncaught TypeErrorになります。
PHP Fatal error: Uncaught TypeError: Unsupported operand types: array % array in /home/vagrant/sample.php:3
Stack trace:
#0 {main}
thrown in /home/vagrant/sample.php on line 3
配列のビット演算
配列のビット演算はbooleanの計算結果が出ます(シフト演算はint)。配列を格納するメモリのアドレスに値が入っていて、それをビット単位にすると0と1の集合体として見るため。
PHP8では、すべてのビット演算でUncaught TypeErrorになります。
<?php
var_dump( [ 1, 2 ] & [ 10, 20, 30 ] );
int(1)
PHP Fatal error: Uncaught TypeError: Unsupported operand types: array & array in /home/vagrant/sample.php:3
Stack trace:
#0 {main}
thrown in /home/vagrant/sample.php on line 3
クラスオブジェクトの算術演算
PHP7まで、クラスオブジェクトで算術演算をすると、警告(Notice)が出るだけで処理をつづけます。
クラスオブジェクトを格納するメモリアドレスの値を無理やりintに変換して。もちろん、その値に意味はありません。
これもPHP8では、すべての演算子でUncaught TypeErrorになります。
<?php
class Class1 {
public $number = 1;
}
class Class2 {
public $number = 2;
}
var_dump( ( new Class1() ) + ( new Class2() ) );
var_dump( ( new Class1() ) - ( new Class2() ) );
var_dump( ( new Class1() ) / ( new Class2() ) );
var_dump( ( new Class1() ) * ( new Class2() ) );
var_dump( ( new Class1() ) % ( new Class2() ) );
var_dump( ( new Class1() ) ** ( new Class2() ) );
PHP Notice: Object of class Class1 could not be converted to number in /home/vagrant/sample.php on line 10
PHP Notice: Object of class Class2 could not be converted to number in /home/vagrant/sample.php on line 10
int(2)
PHP Notice: Object of class Class1 could not be converted to number in /home/vagrant/sample.php on line 11
PHP Notice: Object of class Class2 could not be converted to number in /home/vagrant/sample.php on line 11
int(0)
PHP Notice: Object of class Class1 could not be converted to number in /home/vagrant/sample.php on line 12
PHP Notice: Object of class Class2 could not be converted to number in /home/vagrant/sample.php on line 12
int(1)
PHP Notice: Object of class Class1 could not be converted to number in /home/vagrant/sample.php on line 13
PHP Notice: Object of class Class2 could not be converted to number in /home/vagrant/sample.php on line 13
int(1)
PHP Notice: Object of class Class1 could not be converted to int in /home/vagrant/sample.php on line 14
PHP Notice: Object of class Class2 could not be converted to int in /home/vagrant/sample.php on line 14
int(0)
PHP Notice: Object of class Class1 could not be converted to number in /home/vagrant/sample.php on line 15
PHP Notice: Object of class Class2 could not be converted to number in /home/vagrant/sample.php on line 15
int(1)
PHP Fatal error: Uncaught TypeError: Unsupported operand types: Class1 + Class2 in /home/vagrant/sample.php:10
Stack trace:
#0 {main}
thrown in /home/vagrant/sample.php on line 10
クラスオブジェクトのビット演算
PHP7までのクラスオブジェクトのビット演算は、算術演算と同じで警告を出して処理をつづけます。
オブジェクトの値を無理やりintに変換するのも同じ。
これもPHP8では、すべての演算子でUncaught TypeErrorになります。
<?php
class Class1 {
public $number = 1;
}
class Class2 {
public $number = 2;
}
var_dump( ( new Class1() ) & ( new Class2() ) );
var_dump( ( new Class1() ) | ( new Class2() ) );
var_dump( ( new Class1() ) ^ ( new Class2() ) );
var_dump( ( new Class1() ) << ( new Class2() ) );
var_dump( ( new Class1() ) >> ( new Class2() ) );
PHP Notice: Object of class Class1 could not be converted to int in /home/vagrant/sample.php on line 10
PHP Notice: Object of class Class2 could not be converted to int in /home/vagrant/sample.php on line 10
int(1)
PHP Notice: Object of class Class1 could not be converted to int in /home/vagrant/sample.php on line 11
PHP Notice: Object of class Class2 could not be converted to int in /home/vagrant/sample.php on line 11
int(1)
PHP Notice: Object of class Class1 could not be converted to int in /home/vagrant/sample.php on line 12
PHP Notice: Object of class Class2 could not be converted to int in /home/vagrant/sample.php on line 12
int(0)
PHP Notice: Object of class Class1 could not be converted to int in /home/vagrant/sample.php on line 13
PHP Notice: Object of class Class2 could not be converted to int in /home/vagrant/sample.php on line 13
int(2)
PHP Notice: Object of class Class1 could not be converted to int in /home/vagrant/sample.php on line 14
PHP Notice: Object of class Class2 could not be converted to int in /home/vagrant/sample.php on line 14
int(0)
PHP Fatal error: Uncaught TypeError: Unsupported operand types: Class1 & Class2 in /home/vagrant/sample.php:10
Stack trace:
#0 {main}
thrown in /home/vagrant/sample.php on line 10
なんでこんな変更をしたのか? 答えは簡単です。おかしな計算をさせないため。それなりに結果を出してしまうとバグに気づきません。
というかこれを許しているところがスゴい。昔のC言語でキャストを使ってこんなことしてたような? クラスじゃなくて構造体だったけど。今はどうなってるか分かりませんが。
意味を考えてプログラミングしている人はPHP8にアップグレードしてもエラーにならないでしょう。
しかし、『とりあえず動いているからOK』にしている人は要注意。エラーになるので処理が止まってしまいます。
(修正が必要。)