Edited at

【PHP】オブジェクトを連想配列に変換する方法まとめ

More than 1 year has passed since last update.

オブジェクトを連想配列に変換する方法をまとめました。

以前、PHP にマジックメソッドとして __toArray() を追加しようという要望があったのですが、その要望は一度却下されているので、恐らく今後も実装されることは無さそうです。

なので現状これといった変換方法は提供されておらず、ケースに合わせて方法を使い分ける必要があります。


方法1: 型キャストする

最もお手軽な方法です。

プロパティが非公開属性( protectedprivate )の場合、純粋なプロパティ名として取得できない点は注意が必要です。


変換コード

class ChildProperty

{
public $publicProperty = true;
protected $protectedProperty = true;
private $privateProperty = true;
}

class ParentProperty
{
public $publicProperty = true;
protected $protectedProperty = true;
private $privateProperty = true;

public $childProperties = [];

public function __construct()
{
$this->childProperties['key1'] = new ChildProperty();
$this->childProperties['key2'] = new ChildProperty();
}
}

$property = new ParentProperty();
$array = (array)$property;


結果

array(4) {

["publicProperty"]=>
bool(true)
["*protectedProperty"]=>
bool(true)
["App\Controller\Admin\ParentPropertyprivateProperty"]=>
bool(true)
["childProperties"]=>
array(2) {
["key1"]=>
object(App\Controller\Admin\ChildProperty)#167 (3) {
["publicProperty"]=>
bool(true)
["protectedProperty":protected]=>
bool(true)
["privateProperty":"App\Controller\Admin\ChildProperty":private]=>
bool(true)
}
["key2"]=>
object(App\Controller\Admin\ChildProperty)#168 (3) {
["publicProperty"]=>
bool(true)
["protectedProperty":protected]=>
bool(true)
["privateProperty":"App\Controller\Admin\ChildProperty":private]=>
bool(true)
}
}
}


方法2: get_object_vars 関数を使う

get_object_vars() は関数の呼び出し元のスコープから見て公開されているプロパティを連想配列として取得できる関数です。

一階層目は連想配列に変換できていますが、二回層目の $childProperties が変換されていないことから再帰的な変換はしてくれないことが分かりました。


変換コード

class ChildProperty

{
public $publicProperty = true;
protected $protectedProperty = true;
private $privateProperty = true;
}

class ParentProperty
{
public $publicProperty = true;
protected $protectedProperty = true;
private $privateProperty = true;

public $childProperties = [];

public function __construct()
{
$this->childProperties['key1'] = new ChildProperty();
$this->childProperties['key2'] = new ChildProperty();
}

/**
* @return array
*/

public function toArray()
{
return get_object_vars($this);
}
}

$property = new ParentProperty();
$array = $property->toArray();


結果

array(4) {

["publicProperty"]=>
bool(true)
["protectedProperty"]=>
bool(true)
["privateProperty"]=>
bool(true)
["childProperties"]=>
array(2) {
["key1"]=>
object(App\Controller\Admin\ChildProperty)#167 (3) {
["publicProperty"]=>
bool(true)
["protectedProperty":protected]=>
bool(true)
["privateProperty":"App\Controller\Admin\ChildProperty":private]=>
bool(true)
}
["key2"]=>
object(App\Controller\Admin\ChildProperty)#168 (3) {
["publicProperty"]=>
bool(true)
["protectedProperty":protected]=>
bool(true)
["privateProperty":"App\Controller\Admin\ChildProperty":private]=>
bool(true)
}
}
}


方法3: json_encode と json_decode を使う

一度 JSON 文字列化した後にそれをデコードして連想配列に変換する方法ですが、こちらは上手く変換できています。公開属性のプロパティのみが含まれるのが特徴です。

JsonSerializable インターフェース を実装すると柔軟に変換時の挙動をカスタマイズできます。


変換コード

class ChildProperty

{
public $publicProperty = true;
protected $protectedProperty = true;
private $privateProperty = true;
}

class ParentProperty
{
public $publicProperty = true;
protected $protectedProperty = true;
private $privateProperty = true;

public $childProperties = [];

public function __construct()
{
$this->childProperties['key1'] = new ChildProperty();
$this->childProperties['key2'] = new ChildProperty();
}
}

$property = new ParentProperty();
$array = json_decode(json_encode($property), true);


結果

array(2) {

["publicProperty"]=>
bool(true)
["childProperties"]=>
array(2) {
["key1"]=>
array(1) {
["publicProperty"]=>
bool(true)
}
["key2"]=>
array(1) {
["publicProperty"]=>
bool(true)
}
}
}


方法4: 再帰的に配列変換メソッドを呼び出す実装を書いて頑張る

自分で変換を実装すると挙動を調整したくなったときに融通が効きます。

以下、少し長いですが、非公開属性のものを含めて全てのプロパティを再帰的に取得するサンプルを書いてみました。(書いてる途中一体何をやってるんだ俺はという感情に襲われました)


変換コード

interface Arrayable

{
public function toArray(): array;
}

class ChildProperty implements Arrayable
{
public $publicProperty = true;
protected $protectedProperty = true;
private $privateProperty = true;

public function toArray(): array
{
return get_object_vars($this) ?? [];
}
}

class ParentProperty implements Arrayable
{
public $publicProperty = true;
protected $protectedProperty = true;
private $privateProperty = true;

public $childProperties = [];

public function __construct()
{
$this->childProperties['key1'] = new ChildProperty();
$this->childProperties['key2'] = new ChildProperty();
}

public function toArray(): array
{
$properties = get_object_vars($this);
return $this->toArrayRecursive($properties);
}

private function toArrayRecursive($property)
{
// 配列の場合は中身を再帰的に配列に変換
if (is_array($property)) {
$array = [];
foreach ($property as $key => $value) {
$array[$key] = $this->toArrayRecursive($value);
}
return $array;
}
// 配列変換が実装されているオブジェクトの場合は変換
if (is_object($property)) {
if (!($property instanceof Arrayable)) {
throw new \LogicException('配列に変換できないオブジェクトが含まれています');
}
return $property->toArray();
}
// プリミティブ型の場合
return $property;
}
}

$property = new ParentProperty();
$array = $property->toArray();


結果

array(4) {

["publicProperty"]=>
bool(true)
["protectedProperty"]=>
bool(true)
["privateProperty"]=>
bool(true)
["childProperties"]=>
array(2) {
["key1"]=>
array(3) {
["publicProperty"]=>
bool(true)
["protectedProperty"]=>
bool(true)
["privateProperty"]=>
bool(true)
}
["key2"]=>
array(3) {
["publicProperty"]=>
bool(true)
["protectedProperty"]=>
bool(true)
["privateProperty"]=>
bool(true)
}
}
}


各方法の速度比較

参考までに PHP 7.3 環境でそれぞれの方法を2万回繰り返したときの実行速度を比較した結果を貼っておきます。型キャストの方法が最も高速という結果になりました。(相当数繰り返すようなケースでもなければ意識する必要は殆ど無いと思います。)

速度比較.png


結果の早見表

方法
実行速度
トップ階層
二回層目以降

方法1: 型キャスト
0.0016秒
配列に変換される
配列に変換されない

方法2: get_object_vars
0.0077秒
配列に変換される
配列に変換されない

方法3: json_encode
0.0374秒
配列に変換される
配列に変換される

方法4: 自前変換
0.0387秒
配列に変換される
配列に変換される


まとめ

今回の方法の中だと「方法3: json_encode と json_decode を使う」が最も柔軟で簡単な感じがしました。

JsonSerializable を実装すれば変換の動作をハンドリングできる点も大きいと思います。CakePHP ではデータソースの個々のレコードを表す Entity クラスも標準で JsonSerializable が実装されていたりします。

他にも色々方法はあると思いますが、単一責任の原則の観点でいうとオブジェクト側の責任で変換する方法の方が好ましいと思います。

他にいい方法があればご教示ください。