PHPメモ: foreachでオブジェクトの配列を回したい時


シンプルなオブジェクト配列

// 動物クラス
class Animal
{
    private string $name; // 名前

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName() : string
    {
        return $this->name;
    }
}

// インスタンス化し、配列に格納
$animals[] = new Animal('cat');
$animals[] = new Animal('dog');
$animals[] = new Animal('tanuki');

// 全ての動物の名前を出力する関数
function echoAnimalNames(array $animals)
{
    foreach ($animals as $animal) {
        echo $animal->getName(), PHP_EOL;
    }

    var_dump($animals);
}

// 実行
echoAnimalNames($animals);
// cat
// dog
// tanuki
// array(3) {
//   [0]=>
//   object(Animal)#1 (1) {
//     ["name":"Animal":private]=>
//     string(3) "cat"
//   }
//   [1]=>
//   object(Animal)#2 (1) {
//     ["name":"Animal":private]=>
//     string(3) "dog"
//   }
//   [2]=>
//   object(Animal)#3 (1) {
//     ["name":"Animal":private]=>
//     string(6) "tanuki"
//   }
// }

シンプルなオブジェクト配列の問題点

  • 型の保証がない
  • 配列にインスタンスを追加する時にコードの方で型を保証しても、関数の引数には単なる配列として渡すことになるので型宣言できない
  • IDEも判別してくれないので自動補完が使えない

オブジェクト(疑似)配列クラス

// 動物オブジェクト疑似配列クラス ⭐
class AnimalArray
{
    // 動的にプロパティを追加するマジックメソッド
    public function __set(string $name, Animal $animal)
    {
        $this->$name = $animal;
    }
}

class Animal
{
    private string $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName() : string
    {
        return $this->name;
    }
}

// インスタンス化し、疑似配列オブジェクトに格納
$animals = new AnimalArray(); // ⭐

$animals->cat = new Animal('cat'); // ⭐
$animals->dog = new Animal('dog'); // ⭐
$animals->tanuki = new Animal('tanuki'); // ⭐

function echoAnimalNames(AnimalArray $animals)
{
    foreach ($animals as $animal) {
        echo $animal->getName(), PHP_EOL;
    }

    var_dump($animals);
}

echoAnimalNames($animals);
// cat
// dog
// tanuki
// object(AnimalArray)#1 (3) {
//   ["cat"]=>
//   object(Animal)#2 (1) {
//     ["name":"Animal":private]=>
//     string(3) "cat"
//   }
//   ["dog"]=>
//   object(Animal)#3 (1) {
//     ["name":"Animal":private]=>
//     string(3) "dog"
//   }
//   ["tanuki"]=>
//   object(Animal)#4 (1) {
//     ["name":"Animal":private]=>
//     string(6) "tanuki"
//   }
// }

良い点

  • 型が(疑似)配列の内部で保証されている
  • 関数の引数にも(疑似)配列クラスを型宣言して渡せる

微妙な点

  • 全ての配列要素にプロパティ名を設定する必要があるのが面倒
    • プロパティ名に数字は使えないので、for文では回せないと思われ
  • foreachの中で使われるvalueの型はIDEには相変わらず判別できないので、自動補完は使えないまま

どうにか自動補完させたかった

class AnimalArray
{
    public function __set(string $name, Animal $animal)
    {
        $this->$name = $animal;
    }
}

class Animal
{
    private string $name;

    public function __construct($name)
    {
        $this->name = $name;
    }

    public function getName() : string
    {
        return $this->name;
    }
}

$animals = new AnimalArray();

$animals->cat = new Animal('cat');
$animals->dog = new Animal('dog');
$animals->tanuki = new Animal('tanuki');

function echoAnimalNames(AnimalArray $animals)
{
    // Animal型を引数とする無名関数を宣言 ⭐
    $name = function (Animal $animal) {
        return $animal->getName();
    };

    foreach ($animals as $animal) {
        echo $name($animal), PHP_EOL;
    }

    var_dump($animals);
}

echoAnimalNames($animals);

  • ちょっと無理やりですよね‥‥‥良い方法があったら知りたいです