HHeLiBeXの日記 正道編

日々の記憶の記録とメモ‥

オブジェクトに対するarray_key_exists関数呼び出しの代替策について

array_key_exists関数の第二パラメータにオブジェクトを渡すとエラーになるようになってから久しいが、PHP 5.4.16で動くシステムを最低でもPHP 7.3以降に上げなければならなくなったためにぶつかった壁の調査。

以下のようなコードをPHP 5.6.40/7.4.29/8.1.6で実行してみる。

<?php

printf("%s\n", PHP_VERSION);

ini_set('display_errors', '1');
ini_set('error_reporting', E_ALL);

class A {
}

$obj = new A();
$ary = array();

$k = 'noExistence';
printf("%-12s: %d %d\n",
    $k,
    array_key_exists($k, $ary),
    array_key_exists($k, $obj));
$ php56 Main.php
5.6.40
noExistence : 0 0
$ 
$ php74 Main.php
7.4.29
PHP Deprecated:  array_key_exists(): Using array_key_exists() on objects is deprecated. Use isset() or property_exists() instead in /home/hhelibex/blog/2022-0518-01/Main.php on line 18

Deprecated: array_key_exists(): Using array_key_exists() on objects is deprecated. Use isset() or property_exists() instead in /home/hhelibex/blog/2022-0518-01/Main.php on line 18
noExistence : 0 0
$ 
$ php81 Main.php
8.1.6
PHP Fatal error:  Uncaught TypeError: array_key_exists(): Argument #2 ($array) must be of type array, A given in /home/hhelibex/blog/2022-0518-01/Main.php:18
Stack trace:
#0 {main}
  thrown in /home/hhelibex/blog/2022-0518-01/Main.php on line 18

Fatal error: Uncaught TypeError: array_key_exists(): Argument #2 ($array) must be of type array, A given in /home/hhelibex/blog/2022-0518-01/Main.php:18
Stack trace:
#0 {main}
  thrown in /home/hhelibex/blog/2022-0518-01/Main.php on line 18
$

確かにエラーになる。 さて、PHP 7.4.29での実行結果に「Use isset() or property_exists() instead」とあるので、その辺を検証してみる。

<?php

printf("%s\n", PHP_VERSION);

ini_set('display_errors', '1');
ini_set('error_reporting', E_ALL);

class A {
}

$base = array(
    'isNull' => null,
    'isZero' => 0,
    'isOne' => 1,
    'isStr0' => '0',
    'isEmptyStr' => '',
    'isStrHoge' => 'hoge',
);

$obj = new A();
foreach ($base as $k => $v) {
    $obj->$k = $v;
}
$ary = $base;

var_dump($obj, $ary);

foreach ($base as $k => $v) {
    printf("%-12s: %d %d %d %d\n",
        $k,
        array_key_exists($k, $ary),
        property_exists($obj, $k),
        isset($ary[$k]),
        isset($obj->$k));
}
$k = 'noExistence';
printf("%-12s: %d %d %d %d\n",
    $k,
    array_key_exists($k, $ary),
    property_exists($obj, $k),
    isset($ary[$k]),
    isset($obj->$k));

これを同様に実行してみると、以下のようになる。

$ php56 Test.php
5.6.40
object(A)#1 (6) {
  ["isNull"]=>
  NULL
  ["isZero"]=>
  int(0)
  ["isOne"]=>
  int(1)
  ["isStr0"]=>
  string(1) "0"
  ["isEmptyStr"]=>
  string(0) ""
  ["isStrHoge"]=>
  string(4) "hoge"
}
array(6) {
  ["isNull"]=>
  NULL
  ["isZero"]=>
  int(0)
  ["isOne"]=>
  int(1)
  ["isStr0"]=>
  string(1) "0"
  ["isEmptyStr"]=>
  string(0) ""
  ["isStrHoge"]=>
  string(4) "hoge"
}
isNull      : 1 1 0 0
isZero      : 1 1 1 1
isOne       : 1 1 1 1
isStr0      : 1 1 1 1
isEmptyStr  : 1 1 1 1
isStrHoge   : 1 1 1 1
noExistence : 0 0 0 0
$ 
$ php74 Test.php
7.4.29
object(A)#1 (6) {
  ["isNull"]=>
  NULL
  ["isZero"]=>
  int(0)
  ["isOne"]=>
  int(1)
  ["isStr0"]=>
  string(1) "0"
  ["isEmptyStr"]=>
  string(0) ""
  ["isStrHoge"]=>
  string(4) "hoge"
}
array(6) {
  ["isNull"]=>
  NULL
  ["isZero"]=>
  int(0)
  ["isOne"]=>
  int(1)
  ["isStr0"]=>
  string(1) "0"
  ["isEmptyStr"]=>
  string(0) ""
  ["isStrHoge"]=>
  string(4) "hoge"
}
isNull      : 1 1 0 0
isZero      : 1 1 1 1
isOne       : 1 1 1 1
isStr0      : 1 1 1 1
isEmptyStr  : 1 1 1 1
isStrHoge   : 1 1 1 1
noExistence : 0 0 0 0
$ 
$ php81 Test.php
8.1.6
object(A)#1 (6) {
  ["isNull"]=>
  NULL
  ["isZero"]=>
  int(0)
  ["isOne"]=>
  int(1)
  ["isStr0"]=>
  string(1) "0"
  ["isEmptyStr"]=>
  string(0) ""
  ["isStrHoge"]=>
  string(4) "hoge"
}
array(6) {
  ["isNull"]=>
  NULL
  ["isZero"]=>
  int(0)
  ["isOne"]=>
  int(1)
  ["isStr0"]=>
  string(1) "0"
  ["isEmptyStr"]=>
  string(0) ""
  ["isStrHoge"]=>
  string(4) "hoge"
}
isNull      : 1 1 0 0
isZero      : 1 1 1 1
isOne       : 1 1 1 1
isStr0      : 1 1 1 1
isEmptyStr  : 1 1 1 1
isStrHoge   : 1 1 1 1
noExistence : 0 0 0 0
$ 

いずれも「nullが代入されたpropertyが存在する」時にisset()とproperty_exists()の挙動が異なる。

すなわち、厳密には、オブジェクトに対するarray_key_exists関数の呼び出しはproperty_exists関数でしか代用できない。