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

  • 3
    いいね
  • 0
    コメント

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

以前、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
{
    /**
     * @return array
     */
    public function toArray();
}

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

    /**
     * {@inheritdoc}
     */
    public function toArray()
    {
        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();
    }

    /**
     * {@inheritdoc}
     */
    public function toArray()
    {
        $properties = get_object_vars($this);
        return $this->toArrayRecursive($properties);
    }

    /**
     * @param mixed $property
     * @return array
     */
    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)
        }
    }
}

まとめ

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

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

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