PHP8.2.0以降で動的プロパティは非推奨となり、error_reporting(E_ALL) とすると、存在しないクラスプロパティへの値代入は実行されますが、エラー出力されます。
意図しないプロパティへの値代入はバグの温床になるので、クラスプロパティは明示的に宣言してくださいとうことでしょう。
▼コード例:
<?php
error_reporting(E_ALL);
$class = new class {};
$class->prop1 = "value1";
var_dump($class);
▼実行結果: PHP 8.2以降
PHP Deprecated: Creation of dynamic property class@anonymous::$prop1 is deprecated in /home/macocci7/php/dynamic-property/deprecated.php on line 5
PHP Stack trace:
PHP 1. {main}() /home/macocci7/php/dynamic-property/deprecated.php:0
/home/macocci7/php/dynamic-property/deprecated.php:6:
class class@anonymous#1 (1) {
public $prop1 =>
string(6) "value1"
しかしながら、この動的プロパティを敢えて使いたい場面もあったります。
例えば、ModelBaseで取得結果をDTOで返したい場合などは、当然テーブル毎にカラム定義が異なるので、いちいちテーブル毎のDTOを事前に宣言しておくというのは現実的ではありません(いや、やりたくありません)。
PDOStatementで fetch したレコードのカラム名をプロパティ名として、動的プロパティとして値を代入したいものです。
というわけで、 error_reporting(E_ALL) でもエラーが出ないように動的プロパティを扱っていきます。
前提条件
- PHP8.2以降
- php.ini はいじらない
- ini_set() も使わない
- error_reporting(E_ALL) の状態でエラーが出ないようにする
- アトリビュート
#[\AllowDynamicProperties]は使わない
動的プロパティ専用基底クラス
PHP公式サイトに対応方法が書かれています。
警告
動的なプロパティは、PHP 8.2.0 以降は推奨されなくなりました。 代わりに、プロパティを宣言することを推奨します。 任意のプロパティの名前を扱うには、 クラスがマジックメソッド __get() と __set() を実装すべきです。 動的なプロパティを使うための最終手段として、 アトリビュート
動的なプロパティ | PHPマニュアル#[\AllowDynamicProperties]でクラスをマークすることができます。
動的プロパティを許容する専用の基底クラスを準備します。
▼「DynamicProperty.php」
<?php
namespace MyLib;
class DynamicProperty
{
/**
* @var array<string, mixed> $data = []
*/
private array $data = [];
/**
* @param array<string, mixed> $data = []
*/
public function __construct(array $data = [])
{
$this->setData($data);
}
public function __set(string $key, mixed $value): void
{
$this->data[$key] = $value;
}
public function __get(string $key): mixed
{
return $this->data[$key] ?? null;
}
public function __isset(string $key): bool
{
return isset($this->data[$key]);
}
/**
* @param array<string, mixed> $data = []
*/
private function setData(array $data = []): void
{
foreach ($data as $key => $value) {
if (is_string($key)) {
$this->data[$key] = $value;
}
}
}
}
クラスに存在しないプロパティへの代入があった際には、マジックメソッド __set() がコールされます。
クラスに存在しないプロパティの参照があった際には、マジックメソッド __get() がコールされます。
クラスに存在しないプロパティの isset() がコールされた際には、マジックメソッド __isset() がコールされます。
このクラスのインスタンス生成時に、コンストラクタの引数に渡された配列が、クラスプロパティ$data に格納されます。
__get() でこの $data から取得して返し、 __set() でこの $data へ格納し、 __isset() でこの $data 内に存在するかチェックします。
DBのカラム名で利用することを考慮して、渡す配列キーは文字列のみを許容しています。
使用例
▼「DBRecordDTO.php」:DynamicPropertyクラスを継承
<?php
namespace MyLib;
require_once __DIR__ . "/DynamicProperty.php";
use MyLib\DynamicProperty;
class DBRecordDTO extends DynamicProperty
{
}
▼「UserModel.php」:取得結果をDBRecordDTOで返す
<?php
namespace MyLib;
require_once __DIR__ . "/DBRecordDTO.php";
use MyLib\DBRecordDTO;
class UserModel
{
public function find(int $id): DBRecordDTO|null
{
$data = [
["id" => 1, "name" => "ユーザー1", "email" => "user1@example.com"],
["id" => 2, "name" => "ユーザー2", "email" => "user2@example.com"],
["id" => 3, "name" => "ユーザー3", "email" => "user3@example.com"],
];
$result = array_filter($data, fn($record) => $record["id"] === $id);
return empty($result) ? null : new DBRecordDTO(array_shift($result));
}
}
※DB接続せずにダミーの配列から返すなんちゃってモデルです。
※本来はModelBaseとかを作る必要がありますが、説明を簡単にするためにすっ飛ばしてます。
▼「find_user.php」:呼出処理
<?php
error_reporting(E_ALL);
require_once __DIR__ . "/UserModel.php";
use MyLib\UserModel;
$model = new UserModel;
foreach ([1, 3, 5] as $id) {
$user = $model->find($id);
if (is_null($user)) {
echo "ID[{$id}]:データが見つかりません。" . PHP_EOL;
continue;
}
echo "ID[{$id}]:" . sprintf("%s <%s>", $user->name, $user->email) . PHP_EOL;
}
▼実行結果:

エラーが出ることなく、動的プロパティを扱うことができました。
以上です。
- 2
- 0
- 0
- 0


コメント