A Django site.
7月 3, 2008
» 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');

5月 24, 2008
» MacPortsのphp5でgdサポートがデフォルトで有効に

MacPortsのphp5パッケージは5.2.6_1から、–with-gd が常に付いた状態でビルドされるようになりました(#13988 (RFE: php5 +macports_gd2) - MacPorts - Trac)。ビルド時にはPHPのソースに同梱のgd2ではなく、MacPortsのgd2が使われる模様。

5月 14, 2008
» symfonyの機能テストを個別に実行するとsfParseExceptionが発生する

symfonyでは以下のようにして、アプリケーション全体の機能テストを実行することができます。

$ symfony test-functional frontend

同様に、機能テストを個別に実行したい場合は、

$ symfony test-functional frontend fooActions

のようにします。

ところが、先日リリースされたばかりのPHP 5.2.6では豪快にsfParseExceptionが発生してしまい残念な感じです。

$ symfony test-functional frontend fooActions
[exception]   sfParseException
[message]     Configuration file “/opt/local/lib/php/data/symfony/config/php.yml” specifies key “magic_quotes_runtime” which cannot be overrided

個別にテストが実行できないと非常に効率が悪いのでどうしたものかと調べてみると、1.0ブランチではChangeset 8861 - symfony - Tracで既に修正されていることが判明。手元のsfPhpConfigHandler.class.phpを直接修正して無事に動作するようになりました。

5月 2, 2008
» Zend LLVM Extension

Google Summer of Code - Zend LLVM Extension、これは面白そうな試み。ZendオペコードをLLVMのIR(中間コード)に変換して実行するZend extensionを作るようです。

Hence from the users perspective, the extension would behave much like existing opcode caches, like eAccelerator, but the crucial difference is that LLVM is allowed to perform its aggressive optimizations, which should improve the performance further if enough Zend opcodes are converted to native LLVM IR.

4月 30, 2008
» フレームワーク選びの基準

kunitさんのところから。

仕事でフレームワークを選定する場合は「個人の問題」ではないんです。「チームの問題」「会社の問題」になるんです。

なので、「なぜその仕事でそのフレームワークを選んでるんだ!」という問いは、個人ではなく、チームや会社に問わないといけないわけです。

なので、個人の好みの問題ではないんすよね・・・そのあたりどうもPHPでは個人の好みレベルの議論が多くてまだまだ成熟度がたらんなぁと。

業務で使うフレームワークを「開発方法論とセット」で捉えるべきというのは確かにその通りで、Seasarなんかはその視点を持っていますよね。

かつてのMVCな設計を提供するだけのシンプルなフレームワークだったら好みで選んでも構わない気がしますが、Rails以降のフレームワークは多かれ少なかれユニットテスト、デプロイ機構、データベースマイグレーション、ドキュメント生成といった開発にまつわるあれこれをサポートしていますから、そのフレームワークが前提とする「開発の仕方」、「運用の仕方」が馴染むかどうかは結構重要なところ。

CakePHPやsymfonyもユニットテストやマイグレーションサポートを盛り込んできてはいますが、そのフレームワークでどうやって開発のイテレーションを回していくか、という視点での情報・サポートがあんまりないのが残念です。ユニットテストができればアジャイル開発なのかっていうとそんなことはないので、様々な要素をどう繋いでいけば開発がテンポよく回るのかっていう枠組みまでを提示していって欲しいですね。

などと書きつつ、個人的には「フレームワークのデザイナ(設計者)のセンスを信じられるか」というのがフレームワーク選定の最大の要素であったりします。DHHの言うOpinionated Softwareに共感できるかってこと。あまり合理的ではありませんけどね。

3月 25, 2008
» CakePHPを使ったMVC設計のベストプラクティス

個人的にはCakePHPはあまり好きではないのですが、CakePHP開発メンバーによるMVCデザインの記事 (CakePHP のおいしい食べ方)で紹介されていたBest Practices in MVC Design with CakePHP (php|architect’s C7Y)はMVCフレームワーク利用者にとってとても有用な情報だったので、訳してみました(php|architectの方には翻訳許可を頂いています)。

この記事を読んでドメインモデルに興味を持った方は、エンタープライズ アプリケーションアーキテクチャパターン(PoEAA)Domain-Driven Design: Tackling Complexity in the Heart of Softwareに手を出してみるのもいいかも。他に、InfoQにユーザー登録すれば、Domain Driven Design Quicklyという書籍のPDFが無料でダウンロードできるので、こちらもおすすめ。

Best Practices in MVC Design with CakePHP

by Nate Abele (2008-03-10)
Copyright © 2002-2008 Marco Tabini & Associates, Inc. — All Rights Reserved

このところのMVCフレームワークの流行もあって、みんなMVCパターンとその仕組みには馴染みがあると思う。たぶん、そういったフレームワークの上でアプリケーションを書いたこともあるだろう。しかしながら、MVCを採用した場合にもそのパターンが持つポテンシャルを発揮させることなく、「ページ指向の設計」のようなPHPにおける典型的なやり方をとってしまうPHPプログラマは多い。

今日はCakePHPを使った簡単なサンプルコードを取り上げて、MVCとその設計思想についてのよりよい理解を深めてみよう。MVCはデザインパターンであり、他のデザインパターンと同様に「ただひとつの真の実装」といったものは存在しないということを肝に銘じておくこと。MVCはもともと、「Web」がティム・バーナーズ=リーの眼前に現れるよりもはるか前の1979年にSmalltalkの開発過程において考案された。もともとの実装と現在Webフレームワークの中で見られるものには注目すべき違いもいくつかあるが、今回は取り上げないでおく。憶えておくべき重要なことは、パターンの実装に「たったひとつの正しいやり方」といったものはないということだ(無数の間違ったやり方が存在しているとも言える)。

CakePHPはほとんどのWebフレームワークと同様に、コントローラがリクエストに応答して必要であればモデルとのやり取りを行なうという構造になっている。コントローラはそのデータをビューに受け渡し、データが表示される。

PHP界での(ほぼ間違いなく)最大の問題があるとすれば、それはスパゲッティコードだ。それも、特にビューロジックとビジネスロジックが混ざり合ったものだ。我々は通常、コントローラとビュー層の分離をもっとも気にかける。それ自体は良いことなのだが、多くの場合それはモデルとコントローラとの分離をおろそかにすることになってしまう。その結果、肥大化したコントローラと貧弱なモデル、それにメンテ不可能なコードができあがる。以下のコードを見て欲しい:

/* controllers/posts_controller.php */
 
class PostsController extends AppController {
 
  public function index() {
    $posts = $this->Post->find("all", array(
      "limit" => 10, "order" => "Post.created DESC"
    ));
    $this->set(compact("posts"));
  }
 
  public function feed() {
    $posts = $this->Post->find("all", array(
      "limit" => 50, "order" => "Post.created DESC"
    ));
    $this->set(compact("posts"));
  }
}

このコードにはいくつかまずい点があるがそれには目をつぶってもらって、実際に行なっていることについて注目して欲しい。/posts/index(または/posts)というURLは、指定されたクエリパラメータ(“limit” => 10, “order” => “Post.created DESC”)というクエリパラメータを使ってレコードを取得し、それを$posts変数にセットすることで最新の記事10件を含むページを表示する。変数は、set()メソッドによってビューに送り出される。

メソッド名から推測できるとおり、/posts/feedというURLもRSSフィードを表示するということ以外はほとんど同じことをする(話を簡単にするためビューのコードは載せないので、想像で補ってほしい)。フィードは最新の50件の投稿を表示する。

さて、これはシンプルで明白な例ではあるが、このやり方はアプリケーション内にロジックの重複を生みだしてしまう。モデルは単なるデータ保存場所ではなくて、アプリケーションのドメインエンティティなのだ。テーブルとのやりとりにモデルの機能はとても重宝するが、モデルをそのためだけに使っているのであれば、あなたは損をしていることになる。ちゃんとしたMVCではモデルこそが第一級の存在であり、そのように扱われる。コントローラはデータをモデルから取得してビューへと送り出すだけのシンプルな糊として振るまい、アプリケーション内で最も魅力のない部分となる。

ロジックをどこに配置すべきか決断するときに使える便利な経験則: モデルに置けるあらゆるものは、そうすべきである(少なくとも「モデルかコントローラか」といった場合には。やり過ぎてビューロジックをモデルに置いた人を見たこともあるが…)。アプリケーションの核となるビジネスロジックと連携しないものは(セッション管理、リクエスト・レスポンス処理、セキュリティやアクセス制限に関するもの)、コントローラに置いたままにすべきである。それ以外のあらゆるものはモデルに放り込め。

さて、先ほどのコードをこの方針に沿って書き換えるとどうなるか?

/* models/post.php */
 
class Post extends AppModel {
  protected $order = "Post.created DESC";
}
 
/* controllers/posts_controller.php */
 
class PostsController extends AppController {
 
  public function index() {
    $posts = $this->Post->find("all", array(
      "limit" => 10
    ));
    $this->set(compact("posts"));
  }
 
  public function feed() {
    $posts = $this->Post->find("all", array(
      "limit" => 50
    ));
    $this->set(compact("posts"));
  }
}

出だしとしては悪くない。このように、CakePHPではデフォルトの$orderプロパティをモデルオブジェクトに持たせることができるので、クエリに含まれていた共通の部分を取り除くことができた。

しかし、まだ、ほとんど同じようなことをしている2つのメソッドが残っている。それは最初は目につかなかったかもしれないが、宿敵の帰還である: 私たちはビューロジックとビジネスロジックを混在させている。いずれのメソッドも、投稿の一覧を提供するという本質的には同じ処理を行なっている。表示部分とクエリが異なるが、基本的な処理は同じだ。

幸いなことにCakePHPはファイル拡張子を利用することで、同じアクションから複数のコンテンツタイプ(Content type)を返すことができる。ファイル拡張子は、設定ファイルroutes.phpに次の行を追加することで有効にできる:

/* config/routes.php */
 
Router::parseExtensions();

詳細は今後のチュートリアルで説明するとして、今はコントローラのindex()アクションを/posts.rss(または/posts/index.rss)でもアクセスできるようにするもの、とだけ憶えておけばよい。こんな感じになる:

/* controllers/posts_controller.php */
 
class PostsController extends AppController {
 
  public $components = array("RequestHandler");
 
  public function index() {
    switch ($this->params['url']['ext']) {
      case 'html':
        $options = array("limit" => 10);
      break;
      case 'rss':
        $options = array("limit" => 50);
      break;
    }
    $posts = $this->Post->find("all", $options);
    $this->set(compact("posts"));
  }
}

だいぶ良くなった。2つの似たようなアクションを1つにまとめたし、RequestHandlerコンポーネントを利用してテンプレートの切り替えをアプリケーションのコードから取り除くこともできた。RequestHandlerコンポーネントの働きについても後のチュートリアルで触れるが、本質的には同じアクションが複数のテンプレートを持てるようにして、コンテンツタイプに応じてそれらを切り替えるということをやっている。 さらに改良していけるかやってみよう:

/* controllers/posts_controller.php */
 
class PostsController extends AppController {
 
  public $components = array("RequestHandler");
 
  protected $_types = array(
    "html" => array("limit" => 10),
    "rss"  => array("limit" => 50)
  );
 
  function index() {
    $type = $this->params['url']['ext'];
    $posts = $this->Post->find("all", $this->_types[$type]);
    $this->set(compact("posts"));
  }
}

素晴らしい。コントローラのコードがより短く読みやすくなっただけでなく、表現力も上がっている。しかし、RSSフィードと同じデータを表示するブログのインデックスページがあるというのもちょっと冗長だ。代わりに、最新の人気記事をHTMLで表示したくなったらどうする?

switch文を使ったやり方に後戻りせずに問題を解決する方法はいくつかあるが、ここではカスタムfindメソッドについて掘り下げてみよう。CakePHP 1.2 betaでは新しいカスタムfindの書式が追加されており、findAll()のようなメソッドを呼んでいた箇所でfind(”all”)のように呼べる。これによって、より柔軟で再利用性の高いコードを書くことができ、ドメインエンティティにおけるビジネスロジックのカプセル化促進にも繋がる。それでは、我々のPostモデルに適用してみよう:

/* models/post.php */
 
class Post extends AppModel {
  protected $order = "Post.created DESC";
 
  public function find($type, $options = array()) {
    switch ($type) {
      case "popular":
        return parent::find('all', array_merge(
          array(
            'limit' => 10,
            'order' => 'Post.view_count DESC'
          ),
          $options
        );
      default:
        return parent::find($type, $options);
    }
  }
}

見て分かるように、デフォルトでいくつかのクエリパラメータを設定したカスタムのtype検索メソッドを作成した(デフォルトのパラメータは$optionsに設定したキーによって上書きできる)。ほんの少し創造力を働かせれば、多くの箇所でコードを減らすためにこの手法が適用できる。今回も、コントローラはシンプルな構造を維持したまま、ちょっと変更するだけで済む。

/* controllers/posts_controller.php */
 
class PostsController extends AppController {
 
  public $components = array("RequestHandler");
 
  protected $_types = array(
    "html" => array("popular"),
    "rss"  => array("all", "limit" => 50)
  );
 
  function index() {
    $options = $this->_types[$this->params['url']['ext']];
    $posts = $this->Post->find($options[0], $options);
    $this->set(compact("posts"));
  }
}

バッチリだ。我々のシンプルな糊(コントローラ)はいい感じで最小限、主要なビジネスロジックはドメインオブジェクト内にカプセル化。そして、実に説明的なメソッド呼び出しの構文も手に入った。

シンプルな法則(とクールなCakePHPの機能)をどのように取り入れれば、アプリケーションを単純にして、メンテナンスの必要なコードの量をとにかく減らすことができるのか理解してもらえただろうか。今後のチュートリアルでは、コードの再利用性を高めるためにMVCの各層を拡張する他の方法について取り上げるつもりだ。

3月 16, 2008
» 第2回PHP懇親会

第2回PHP懇親会から戻ってきました。

発表ではid:koyhogeさんのRequest for Comments: Traits for PHPの紹介が興味深かったですね。ちょうど先週、WEB+DB PRESSの最新号を読んでScalaのTraitsを知ったところだったのでなおさら。

追記(2008/03/17 11:56):TOMさんのところで、いいまとめが掲載されています。

1月 19, 2008
» markdown-mode

ブログのポストや普段のメモ書きはMarkdownで書いています。プロセッサにはテーブルや定義リストといった拡張文法を持つPHP Markdown Extraを使っていて、Wordpressのプラグインディレクトリに放り込みつつ、以下のように変更したものを手元でも使っています。

--- markdown.org.php	2008-01-20 17:15:08.000000000 +0900
+++ markdown.php	2008-01-20 17:16:21.000000000 +0900
@@ -1,3 +1,4 @@
+#!/usr/bin/env php
 <?php
 #
 # Markdown Extra  -  A text-to-HTML conversion tool for web writers
@@ -2633,4 +2634,11 @@
 software, even if advised of the possibility of such damage.
 
 */
-?>
\ No newline at end of file
+
+if ($argc < 2) {
+    $src = 'php://stdin';
+} else {
+    $src = $argv[1];
+}
+echo Markdown(file_get_contents($src));
+?>

実際に、ローカルのテキストファイルをHTML化する時には以下のような関数を使ってEmacsから呼び出しています。

;; リージョンの内容をMarkdownExtraで変換する関数
(defun markdown-region (from to)
  (interactive "r")
  (if (> from to)
      (rotatef from to))
  (let ((buffer-output (get-buffer-create "*markdown*")))
    (with-current-buffer buffer-output
      (erase-buffer))
    (call-process-region from
                         to
                         "markdown.php"
                         nil
                         buffer-output
                         nil)
    (switch-to-buffer-other-window buffer-output)))

このままでも結構満足していたのですが、昨日になってEmacs markdown-modeという素晴らしいものを見つけてしまいました。

markdown-modeは、Markdownで書かれたテキストのSyntax Highlightingとhtml-helper-modeライクなキーバインドでのマークアップを支援してくれ、ブラウザでのプレビュー(C-c C-c p)やバッファ内容のHTML化(C-c C-c m)もできる優れもの。.emacsでは以下のように書いて、変換に使用するコマンドを指定しています。

;; markdown-mode
;; http://jblevins.org/projects/markdown-mode/
(autoload 'markdown-mode "markdown-mode.el"
  "Major mode for editing Markdown files" t)
(setq auto-mode-alist
      (cons '("\\.mdml$" . markdown-mode) auto-mode-alist))
 
;; markdown-modeで利用するコマンド
(setq markdown-command "markdown.php")

markdown-modeにはリージョンの内容をHTML化する機能はなさそうなので前述のmarkdown-regionも併用していますが、これでかなり便利になりました。

11月 12, 2007
» foreach内では内部配列ポインタを触るな

先日のエントリ、PHP 5.1.6のforeachではポインタの進み方が違う?に関してkomuraさんより以下のようなコメントを頂き、

少し前に同じことを調べていました。
基本的な関数でこのように挙動が変化するのはどうか
と思いますね。

恐らく、意図した変更ではないように思います。
修正方法を少し考えていたのですが、結構