2011年10月28日金曜日

CakePHPのモデルからSQLを外出しする

CakePHPを使って開発をしていると、自分でSQLを書く機会が劇的に減ると思います。簡単なSQLであればモデルのメソッドでほとんど記述できる。でも、やっぱり難しいSQLを実行したいとなると自分でSQLを書いてそれを実行することが多いですよね。そんな時どうしてますか?

SQLというのは通常、結構行数をくいます。たとえば

$query = <<<EOD
    SELECT
        Post.id,
        Post.subject,
        Post.created
    FROM
        posts Post
    WHERE
        Post.created <= 'YYYY-MM-DD'
EOD;
$results = $this-<query($query);

こんな感じの簡単なSQLでもそれなりに行数が。。さて、簡単なSQLならまだいいけど、こういう風に直接SQLを書いて実行したい場合は、もっと複雑なSQLになることが多いです。たとえばUNION句を使って結合していたり、条件部分にEXIST句を利用していたり、サブクエリを利用していたりする場合。そうすると、これとは比べ物にならないくらい大きな行数になって、時にはテキストエディタの1画面内に入り切らないことも。。。

モデル内に直接SQLを書くとモデルが肥大化してメンテナンスしにくくなってしまうので、SQLを外部に書きだして、それを読み込むことによってモデルの中身を軽量化を目指します。

まず、AppModelにこんな感じで関数を定義。

/**
 * SQLファイルを読み込む
 *
 * @param string $fileName SQLファイル名
 * @param array  $params   パラメータ
 * @param array  $escape   シングルクォーテーションをエスケープしないパラメータのキー
 * @return string SQL
 */
function sql($fileName, $params = array(), $escape = array())
{
 // ファイル名に拡張子まで指定されていれば取り除く
 if (substr($fileName, -4) == ".sql") {
  $fileName = substr($fileName, strlen($fileName) - 4);
 }
 
 $query = "";
 
 // ファイルが存在することを確認
 $paths = array(
  MODELS . "sql" . DS . Inflector::underscore($this->name) . DS . $fileName . ".sql", // 自モデルのディレクトリ
  MODELS . "sql" . DS . $fileName . ".sql"                                            // 共通ディレクトリ
 );
 $sqlFilePath = "";
 foreach ($paths as $value) {
  if (file_exists($value)) {
   $sqlFilePath = $value;
   break;
  }
 }
 
 if (!empty($sqlFilePath)) {
  if (empty($escape)) {
   // エスケープ除外対象がなければすべての変数についてシングルクォーテーションをエスケープする
   array_walk($params, array($this, "escapeQuote"));
  }
  else {
   // エスケープ除外対象があれば、配列から除外する
   $tmp = $params;
   foreach ($params as $key => $value) {
    if (in_array($key, $escape)) {
     unset($tmp[$key]);
    }
   }
   
   // シングルクォーテーションをエスケープする
   array_walk($tmp, array($this, "escapeQuote"));
   
   // エスケープしたものとそれ以外のものをマージする
   $params = array_merge($params, $tmp);
  }
  
  // パラメータを展開
  extract($params, EXTR_OVERWRITE);
  
  // ファイルからSQLを読み込む
  ob_start();
  include $sqlFilePath;
  $query = ob_get_clean();
 }
 else {
  $this->log("sqlファイルが見つかりません。" . $sqlFilePath);
 }
 
 return $query;
}

/**
 * シングルクォーテーションをエスケープする
 */
function escapeQuote(&$value, $key)
{
    if (is_string($value)) {
        $value = str_replace("'", "''", $value);
    }
    else if (is_array($value)) {
        array_walk($value, array($this, __FUNCTION__));
    }
}

で、直書きSQLを呼び出したいときは、app/models/sql/モデル名/以下にSQLファイルを作る。たとえば、app/models/sql/post/list.sqlというファイルにさっきのSQLを書いたとすると

$query = $this->sql("list.sql");
$results = $this->query($query);
と、たったこれだけに!

パラメータを渡したい場合は、2つ目の引数にキーと値の連想配列を渡してあげる。ちょうどビューのエレメントを呼ぶ時と同じようなやり方です。

モデルがSQLで埋まらないように、整理しましょう!

2011年10月17日月曜日

CakePHP 2.0 Stable Release!

CakePHP 2.0 Stableがリリースされた!つい、こないだRC3がリリースされたと思ったらもうStableだよ。素晴らしい。さっそくダウンロードして試してみよう。ダウンロードして解凍すると、こんな感じのディレクトリ構造
  • app
  • cake
  • lib
  • plugins
  • vendors
  • .gitignore
  • .htaccess
  • index.php
  • README
ん?cakeってディレクトリなんだろう?たどっていっても、奥深くのフォルダにtest_element.xmlっていうファイルがあるだけ。よくわからないなぁ。
とりあえず、app/Config/以下のdatabase.php.defaultをdatabase.phpにリネームして設定を変更。それからapp/tmp以下を書き込み可能に設定して、いざアクセス。


おぉきたー。このスクリーンショットはChrome14ですが、見慣れた緑背景にグラデーションがかかってる。かっこいい。

さて、いろいろいじってみよう。

2011年10月7日金曜日

働くということ

僕は大学卒業後に県内のIT企業に就職、それから丸4年半が経ちました。入社前は「県内一の規模のIT企業」ということで、さぞかし凄い人達がたくさん働いているのだろう、と。期待をふくらませて入社したわけです。

しかし、いざ入社して仕事をしてみるとちょっと違和感。1~2年目はまぁこんなものなのかな、と思いつつも任せられた仕事をコツコツとこなしていました。3年目くらいからかな、その違和感がだんだん確信に変わってきたのは。

周りのレベルが低すぎる、とだんだん思うようになってきました。

もちろん自分なんて世の中全体で見ればまだまだ知らないことがいっぱいあって、もっともっと勉強せないかんこともある。そんな僕も入社して4年半の間、色々な人達と一緒に仕事をしてきた。でも、誰一人として技術力で僕を驚かせてくれる人は出てこず。


向上心というのは常に持ち続けたほうがいいもの。常に上を目指したい。
  • オープンソースコミュニティへの参加
  • 新技術、旬な話題へのアンテナ
  • 創ることの喜び
  • 英語の記事を読んで、英語の日記をつける
他にもいっぱいあると思うけど、いつもこういうことを意識して技術者としての向上を目指してさ。働く上でも必要よね。



働くということ。

お金を稼ぐため。やりがいを見つけるため。自己を高めていくため。人それぞれ目標があることだろう。

生活に必要なお金を稼ぐために安定した職業に就く。これはわかる。難しい課題に挑戦して技術者として向上した。これもわかる。才能ある人達と一緒に仕事をしてお互いに刺激し合いたい。いいね。


僕には家族がいる。妻と娘。養っていかんといけん。こういう場合、周りは必然的に安定を求める。技術者としての価値の向上を目指す必要なんてない。安定したお給料が一番大事だ、ってね。


でも、僕は・・・。