Qiita Teams that are logged in
You are not logged in to any team

Log in to Qiita Team
Community
OrganizationEventAdvent CalendarQiitadon (β)
Service
Qiita JobsQiita ZineQiita Blog
62
Help us understand the problem. What are the problem?
@ymm1x

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

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

以前、PHP にマジックメソッドとして __toArray() を追加しようという要望があったのですが、その要望は実現すること無くクローズされておりので、現状 PHP としてこれといった変換方法は提供されていません。

そのためケースに合わせて変換の方法を使い分ける必要があります。

方法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;

結果

以下は var_dump($array) をした結果です。

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 文字列化した後に配列としてデコードし直して変換する方法です。一階層目以降も上手く変換できています。public のプロパティのみが含まれるのが特徴です。

JsonSerializable インターフェースjsonSerialize() を実装することで、変換時に含めたいプロパティを指定することも可能です。

変換コード

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 では DB の個々の行データを表す Entity クラスも標準で JsonSerializable が実装されていたりします。

ただ、初見で json_decode(json_encode($var), true) のようなコードを見ても意図が伝わりにくいので、意味付けのためにもヘルパー関数を用意しておくと良いかもしれません。

function toArray($var): array
{
    return json_decode(json_encode($var), true);
}

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

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

Why not register and get more from Qiita?
  1. We will deliver articles that match you
    By following users and tags, you can catch up information on technical fields that you are interested in as a whole
  2. you can read useful information later efficiently
    By "stocking" the articles you like, you can search right away
62
Help us understand the problem. What are the problem?