PHPでオブジェクトの配列からプロパティ値を収集する

symfonyのようにORマッパーのある状況でコードを書いていると「モデルが格納された配列から、各モデルのIDだけを抽出したい」というような局面がたまにあって、ベタに書くと、

$book_id = array();
foreach ($books as $book) {
    $book_id[] = $book->getId();
}

こんな感じになってとてもダサい。

こういうとき、RubyのEnumerableにはフィルタ的に使えるメソッドが多く定義されていて「いいなー」なんて思うんですが、PHPで似たようなアプローチをしようとすると、

$book_id = array_map(create_function('$e', 'return $e->getId();'), $books);

こうなります。これはこれで、違う意味でダサい。あちこち危険なにおいがします。create_functionの期待外れ感は異常。

呼び出し側をすっきりさせつつ実装側もある程度robustにしておくには、あらかじめ次のようにユーティリティメソッドを定義しておくのがベスト。ただ、Javaくさいというか、あまりスクリプト言語っぽくないアプローチだと思うので好き嫌いは分かれそうです。

class ModelUtils
{
    /**
     * 指定された文字列をCamelCaseに変換したものを返します。
     *
     * @param string $property 変換する文字列
     * @return string
     */
    public static function camelize($property)
    {
        return implode('', array_map('ucfirst', explode('_', $property)));
    }
 
    /**
     * 配列に含まれる各オブジェクトのプロパティの値を集めた配列を返します。
     * $modelsに含まれるオブジェクトは異なるクラスのものでも構いません。
     * $propertyには、CamelCaseまたはアンダースコア区切りのプロパティ名を
     * 指定します。
     *
     * オブジェクトに、指定されたプロパティ名をCamelCaseにしたものに対応する
     * getterメソッドが定義されている場合に値が取得されます。
     *
     * 使用例:
     * <pre>
     * // $booksの各要素についてgetId()が呼び出される
     * $book_id = ModelUtils::selectProperty($books, 'id');
     * </pre>
     *
     * @param array $models モデルの配列
     * @param string $property 値を取得するプロパティ名
     * @return array
     */
    public static function selectProperty($models, $property)
    {
        $values = array();
 
        foreach ($models as $model) {
            $callback = array($model, 'get' . self::camelize($property));
            if (!is_object($model) || !is_callable($callback)) {
                continue;
            }
            $values[] = call_user_func($callback);
        }
 
        return $values;
    }
}

呼び出し側は以下のようになります。

$book_id = ModelUtils::selectProperty($books, 'id');

使用可能なタグ <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>