お疲れ様です。
PHPで配列操作時に、標準のarray_key_exists関数を久しぶりに使う機会がありました。久しぶりすぎて詳細を忘れていたため、改めてマニュアルを調べたところ、PHP7系から8系のバージョンに上がった時に、少し修正が入っていたみたいですね。
注意点として記載されていますが、パッと見てよくわからなかったので、少し検証してみたので記事にしてみます。
注意の内容
今回論点とする、array_key_existsのマニュアルに記載されている注意点の内容は以下のものです。
過去との互換性を保つため、
key
が仮にarray
で指定したオブジェクトのプロパティであっても array_key_exists() はtrue
を返します。 この挙動は PHP 7.4.0 で非推奨となり、PHP 8.0.0 以降で削除されています。オブジェクトのプロパティが存在するかどうかを調べるには、 property_exists() を使いましょう。
https://www.php.net/manual/ja/function.array-key-exists.php
この注意点は何を言っているのか
array_key_exixtsは、第二引数に指定された配列のデータに、第一引数に指定されたキー、または添え字が含まれているかどうかを検証します。以下のように配列に対して使うのが正しい使い方です。(今回は、var_dumpの出力結果は、行の右側にコメントで記載しています。)
$array = ["zero", "one", "two"];
var_dump(array_key_exists(2, $array)); // bool(true)
var_dump(array_key_exists(3, $array)); // bool(false)
$array = ["zero" => "rei", "one" => "ichi", "two" => "ni"];
var_dump(array_key_exists("two", $array)); // bool(true)
var_dump(array_key_exists("two", $array)); // bool(false)
今回のこの注意はPHPのバージョン7系以前では、array_key_exixts関数の第二引数にnewキーワードを使って作成されたクラスのインスタンスを指定することができました。しかし、バージョン8系からはこれを禁止するということのようです。
要するに、以下のようなコードは、PHPのバージョン7系では利用可能でしたが、PHPのバージョン8系以降では使えなくなるということですね。
class OneClass
{
public $key;
public $value;
}
$item = new OneClass();
var_dump(array_key_exists("key", $item)); //PHP8以降はこの書き方ができない
var_dump(array_key_exists("undefined", $item));
実際に検証してみた
次の2つのバージョンの環境があったので、実際に動作を確認してみました。
- 環境1:PHP 7.4.33
- 環境2:PHP 8.2.9
実際の検証に使用するコードは上記でも記述しましたが、以下のコードとします。
class OneClass
{
public $key;
public $value;
}
$item = new OneClass();
var_dump(array_key_exists("key", $item)); //ここ以降がどう動くかが問題
var_dump(array_key_exists("undefined", $item));
環境1 PHP7.4.33での結果
実際に実行してみた結果、以下の結果を得ました。どうやらClassのインスタンスを受け入れてくれて、クラスのプロパティをキーとして認識して動作している模様です。
bool(true)
bool(false)
環境2 PHP8.2.9での結果
では、同じコードをバージョン8系である8.2.9で実行するとどうなるか。実際に実行してみると。以下のようにFatal error(致命的なエラー)が出て動作が途中で停止しました。
PHP Fatal error: Uncaught TypeError: array_key_exists(): Argument #2 ($array) must be of type array, OneClass given in /Users/***/test_ArrayKeyExsixts.php:11
Stack trace:
#0 {main}
thrown in /Users/***/test_ArrayKeyExsixts.php on line 11
Fatal error: Uncaught TypeError: array_key_exists(): Argument #2 ($array) must be of type array, OneClass given in /Users/***l/test_ArrayKeyExsixts.php:11
Stack trace:
#0 {main}
thrown in /Users/***/test_ArrayKeyExsixts.php on line 11
エラー結果を見てみると、「array_key_existsの第二引数には配列しか指定できないけど、OneClassのオブジェクトがやってきたよ」と言っていますね。
クラスのプロパティの存在を確認するにはどうすればいいのか
注意文の末尾にもありますが、「property_exists()」の関数を利用しましょう。
クラスと配列の両方が指定される処理をすでに書いてしまっている場合
では、すでに実装済みのコードにarray_key_existsを利用していて、第二引数には基本的に配列が来るが、稀にクラスが来てしまうことを許容しているような、共通関数がある場合はどうすればいいのか。
これに対する単純な回答は、以下のようにデータ型をチェックして、処理を振り分ける関数に置き換える方法でしょう。
function variable_key_exists(int|string $key, mixed $variable) : bool
{
if(is_array($variable))
{
return array_key_exists($key, $variable);
}
if(is_object($variable))
{
return property_exists($variable, $key);
}
throw new RuntimeException("関数の使い方が間違っています。第二引数には配列かオブジェクト型を指定してください。");
}
この関数を共通関数として定義して、必要に応じてarray_key_exists関数と置き換えれば動作すると思います。
このコードを使えば以下のように動作すると思います。
$one_array = ["key" => "鍵", "value" => "値"];
class OneClass
{
public $key;
public $value;
}
$item = new OneClass();
var_dump(variable_key_exists("key", $one_array)); //bool(true)
var_dump(variable_key_exists("undefined", $one_array)); //bool(false)
var_dump(variable_key_exists("key", $item)); //bool(true)
var_dump(variable_key_exists("undefined", $item)); //bool(false)
var_dump(variable_key_exists("undefined", "string")); //PHP Fatal error: Uncaught RuntimeException
まとめ
今回は、PHP8で変更となったarray_key_existsの動作について解説と検証を行いました。
array_key_existsの関数の名前からしても、オブジェクトのキーまでチェックできてしまうのは、機能の範囲を超えてしまっていますね。でも、古いコードでは動くからとついつい実装をしてしまっているかもしれません。
PHPのオブジェクトって、昔は配列の拡張として作られた経緯があったと記憶しているので、その名残でこのような実装になっているのだと思います。
この中途半端な状態が、今回のバージョン8へのアップデートでスッキリしたというところでしょうか。