PHP
CakePHP
mvc
QueryBuilder

findByを作ってみた

概要

MVCでSELECTしたい箇所の呼び出しを書いていたら例のごとく、以下のようなたくさんのメソッドが出来上がった。

  • getById
  • getNameByEmail
  • getEmailById

etc...

このような状況を打破するために便利なメソッドを作れるらしい。

findBy

どうやらこのメソッド、「findById」みたいな形で呼ぶとidをWHERE句に使ってSELECTをやってくれるらしい。ではどうやったら実装できるのか。

findBy実装概要

MVCであることを前提とする。
また、今回はfindByの内容を把握したいだけなので、指定するカラムは一つとする。

  1. findByを使いたいモデルの親モデルを作成する(大体はBaseModelとか)
  2. __call()メソッドを利用してメソッド名をゴニョゴニョするように実装する
  3. findByメソッドを実装する

大体こんな感じ。

実装して見ると

findBy
class BaseModel
{ 
    public function __call($method_name, $method_argument) {
        $column = '';

        if (strpos($method_name, 'findBy') === 0) {
            // findBy以降のcolumnを切取して最初を小文字に。
            $column = lcfirst(substr($method_name, 6)); 
        }

        // ここでreturnしないと返る値がNULLとなる。
        return $this->findBy($column, $method_argument[0]);
    }

    /**
     * マジックメソッド
     * 基本的にはfindByIdのような形で呼ばれることを前提とする。
     *
     * @param  string $where_column WHEREの条件に用いるカラム。今回は一つのカラムのみとする。
     * @param  string $where_value WHEREの条件に用いる値。今回は一つのカラムのみとする。
     * @return array|bool SELECTに成功したら結果の連想配列, 失敗したらfalse
     */
    public static function findBy($where_column, $where_value) {

        $trace = debug_backtrace();
                // 呼び出し元のクラスを指定してそのオブジェクトのclass名を取得
        $table = lcfirst(get_class($trace[1]['object'])).'s'; 

        $sql = "SELECT * FROM `{$table}` WHERE `{$where_column}`='{$where_value}'";

        $pdo = DbConnector::getPdo();

        $stmt = $pdo->prepare($sql);

        if ($stmt === false) {
            // FIXME:? Exceptionの方が良い?
            return false;
        }

        $stmt->execute();
        $result = $stmt->fetch(PDO::FETCH_ASSOC);

        return $result;
    }
}

上記のメソッドをUserControllerなどで例えば以下のように呼ぶ。

呼び出し側
class UserController
{
     public function sample() {
         $this->users->findByEmail('sample@gmail.com');
     }
}

肝は__call()

これは存在しないメソッド名が呼ばれた時に呼ばれるメソッドのようで、staticのバージョン(__callStatic())もある。__callにfindByのようなものを入れておけば大体よしなにできるようになる。