• [Zend Framework] Zend_ControllerアクションヘルパーのContextSwitch

    Zend Framework1.5から新しく追加されたContextSwitchアクションヘルパーを使ってみました。 あるページのレスポンス形式をhtmlに加えてrssやatomフィードも追加したいとか、Ajaxで受け取るデータをxmlとjsonの両方に対応したい場合に、新しくactionメソッドを追加したり、処理を複雑にしないで対応することができます。 例として、FooControllerのbarActionメソッドはある画面から非同期通信で呼び出されjson形式でデータを返していたが、あるパラメータが設定されていた場合にXML形式でレスポンスを返すように処理を追加するという処理を考えます。 おそらく私は今までならば以下のように処理をしていたと思います。``` public function barAction() { $format = $this->getRequest() ->getQuery(‘format’, “json”); if($format == "xml") { /\* XML形式で返す \*/ // レスポンスヘッダーの設定 $this->getResponse() ->setHeader('Content-Type', 'application/json'); } else { /\* JSON形式で返す \*/ // レスポンスヘッダの設定 $this->getResponse() ->setHeader('Content-Type', 'text/xml'); } // データの取得など ... // データの設定 $this->view->assign($data); // ビューレンダラーのOFF $this->getFrontController() ->setParam('noViewRenderer', true); // データの出力 $this->view->setScriptPath("path/to/view/"); echo $this->view->render($format . '.phtml'); } * レスポンスヘッダの設定 * ビューの設定 * (Onになっている場合は)ビューレンダラーのOff * ビューファイルのパスと、ファイルの設定 という処理を付け加える必要がありました。 ContextSwitchアクションヘルパーを使うことで、ここの処理が自動化されます。 では、ContextSwitchアクションヘルパーの設定をしてみます。ますinit()メソッドで以下のようにアクションヘルパーの初期化をします。``` public function init() { $contextSwitch = $this->\_helper->getHelper('contextSwitch'); $contextSwitch->addActionContext('bar', 'xml') ->addActionContext('bar', 'json') ->initContext(); } ```まずContextSwitchアクションヘルパーのオブジェクトを取得して、addActionContext()でコンテキストの設定を行います。barAction()メソッドに対してxmlとjsonのコンテキストを追加しています。 initContext()で初期化を行って、設定は終わりです。 アクションメソッドではデータの取得など特に処理を変更する必要はありません。 出力する値をアサインしておきます。``` public function barAction() { $this->view->bar = "bar"; } ```続いてビュースクリプトの設定を行います。 通常デフォルトであれば、ビュースクリプトのファイル名はbar.
  • [Zend Framework] Zend_ControllerのContextSwitchアクションヘルパーでZend_View_Interfaceが使えない件

    Zend Framework1.5から新しく追加されたZend_ControllerのContextSwitchアクションヘルパーを使ってJSON形式でレスポンスを返そうとしたところ、``` Fatal error: Call to undefined method Lib_ViewSmarty::getVars() in … というエラーが出ました。確かに[ここ](http://framework.zend.com/manual/ja/zend.view.scripts.html#zend.view.scripts.templates.interface)を参考に作ったSmartyのラッパーにはそのような関数はありません。そもそもZend\_View\_Interfaceにそういう関数のインターフェースがないのです。ということで少々Smartyラッパーに手を加えないといけません。 Zend\_View\_AbstractにはちゃんとgetVars()というものがあるので、これを見ることにします。 public function getVars() { $vars = get_object_vars($this); foreach ($vars as $key => $value) { if ('_’ == substr($key, 0, 1)) { unset($vars[$key]); } } return $vars; } この関数では、設定された(assign()された)値をすべて返しているようです。 処理的には、Zend\_Viewクラスのプロパティーをすべて取得して、'\_'で始まるプロパティーを排除しています。このクラスに設定されているプロパティは、もともとクラスに備わっているプロパティとviewに出力する値として設定された(assign()された)プロパティの2種類があります。 前者は'\_'で始まりますのでこれらを排除して、後者の値のみを返す処理になっています。初めて知ったのですが、assign()では、設定する値の名前が'\_'で始まる場合にはExceptionをスローする仕組みになっているので、このgetVars()の設定が可能になるのです。 public function assign($spec, $value = null) { // which strategy to use? if (is_string($spec)) { // assign by name and value if ('_’ == substr($spec, 0, 1)) { require_once ‘Zend/View/Exception.
  • [Zend Framework] Zend_ControllerへSmarty組み込む (続きの続き)

    前回デフォルトのmodifierを設定して、出力に対して自動的にエスケープ処理を行うように設定したのですが、urlエンコードをかけたいときは``` {$hello|smarty:nodefaults|escape:“url”} ### Smarty自作のmodifier で、前回設定したmodifierは自作もできるので、オリジナルで作ってそれをデフォルト設定することにしました。通常はhtmlspecialchars()でエスケープをかけて、ある特定の条件でアサインされた値にはrawurlencode()でURLエンコード処理行うようにします。ちなみに、{$foo|escape:"html"}とした場合にはhtmlspecialchars()で、{$foo|escape|"url"}とした場合にはrawurlencode()でそれぞれエスケープ処理がなされます。 ある特定の条件でというところが迷ったのですが、足りない頭でいろいろ考えた結果、今回はアサインされた値の先頭に特定の文字列があった場合に別のエスケープ処理を施すという方法になりました。%xxx%のように%で囲まれた文字列が先頭にあった場合に、xxxに対応するエスケープ処理を施すという内容です。 アサインされた文字が"foo"の場合 →"foo"に対してhtmlspecialchars()処理を行う アサインされた文字が"%url%foo"の場合 →%url%を切り取った"foo"に対してrawurlencode()処理を行う ### 自作modifierのつくり方 ファイル名はmodifierを含めたプラグインの命名規則に従って付与します。``` < プラグインの型>.<modifier の名前>.php ```プラグインの型はmodifierとなります。プラグインの名前は適当なものを付けます。ここでつけた名前はテンプレートでmodifierを指定するときの名前になります。``` {$foo|escape:"html"} ```ならば、modifierプラグインのファイル名は``` modifier.escape.php ```です。今回はsanitizeというmodifier名にしますので、ファイル名は``` modifier.sanitize.php ```とします。 このファイルをSmarty.class.phpのあるディレクトリのpluginsディレクトリ内に配置してください。もし任意の場所にファイルを配置したい場合は、Smartyのパラメタであるplugins\_dirを設定する必要があります。前出のpluginsディレクトリに加えて、カレントディレクトリ内にあるtestというディレクトリを追加したい場合は以下のようになります(前回、前々回に作ったViewSmartyクラスを想定しています)。``` $this->\_smarty->plugins\_dir=array("plugins", "test"); ※絶対パスでの指定も可能です ```modifier.sanitize.phpには以下の命名規則で関数を定義します。``` function smarty\_modifier\_<modifier 名>(処理対象の値, \[パラメータ1, ...\]) ```modifier名はsanitizeになります。処理対象の値は、通常はアサインされた値で、パラメタは{$foo|sanitize:param1:param2}のparam1、param2の部分になります。今回作成するmodifierではパラメタがないので、``` function smarty\_modifier\_sanitize($string) ```という関数を定義することになります。 以上で自作のmodifierプラグインの作り方は終わりです。 今回作るmodiferの関数の定義は以下のようになります。``` <?php define("SANITIZE\_TYPE", '/%(w+)%/'); function smarty\_modifier\_sanitize($string) { $split = preg\_split(SANITIZE\_TYPE, $string, 2, PREG\_SPLIT\_NO\_EMPTY|PREG\_SPLIT\_DELIM\_CAPTURE); if(count($split) == 2) { switch ($split\[0\]) { case 'url': return rawurlencode($split\[1\]); } } return htmlspecialchars($string, ENT\_QUOTES); } ```これでmodifierは完成です。SANITIZE\_TYPEを変更することで、値の前につける文字列のパターンを変更でき、%xxx%のxxxの部分をcaseに追加することでエスケープの種類を追加することができます。 ### テスト では実際にデータが処理されるか確認してみます。以下のようなデータを用いて検証します。``` $url = "http://wadslab.
  • [Zend Framework] Zend_ControllerへSmarty組み込む (続き)

    前回SmartyをZend_Controllerへ組み込みましたが、このままでは出力はエスケープされません。Smartyでの出力のエスケープ処理は以下のようになります。``` {$hello|escape:“html”} * 面倒くさい * ものにもよるけど、エスケープすべき出力のほうが多いのではないか? * エスケープのもれが怖い などの理由で(特に1番最後)、デフォルトで出力のエスケープをするように設定しておいたほうが安心です。 対応は簡単です。 コンストラクタでdefault\_modifiersというSmarty変数に'escape:"html"'を設定するだけです。``` require\_once 'Zend/View/Interface.php'; require\_once 'Smarty/Smarty.class.php'; class ViewSmarty implements Zend\_View\_Interface { protected$\_smarty; public function \_\_construct($tmplPath = null, $extraParams = array()) { $this->\_smarty = new Smarty; if (null !== $tmplPath) { $this->setScriptPath($tmplPath); } foreach ($extraParams as $key => $value) { $this->\_smarty->$key = $value; } // ↓これ $this->\_smarty->default\_modifiers=array('escape:"html"'); } ... } ```では確認してみましょう。 コントローラの内容``` class FooController extends Zend\_Controller\_Action { public function indexAction() { $this->view->hello = "<strong>Hello World</strong>"; } } ```ビューの内容``` hello {$hello} ```出力は以下のようになりました。<strong>タグがエスケープされているので、強調表示されていません。``` **Hello World** ```HTMLのソースは以下のとおり。成功です。``` <strong>Hello World</strong> ```エスケープしたくないところでは以下のように書けばOKです。``` {$hello|smarty:nodefaults}
  • [Zend Framework][Smarty] Zend_ControllerへSmarty組み込む

    Zend_ViewでSmartyを使う方法は、このページを参考に、Zend_View_Interfaceを実装して、最低限Smartyのプロパティである$template_dirと$compile_dirを設定してあげれば使えます。``` require_once ‘Zend/View/Interface.php’; require_once ‘Smarty/Smarty.class.php’; class ViewSmarty implements Zend_View_Interface { … } 上記のようなviewクラスを作ったら、以下のように初期化します。 // プロパティの設定 $templete_dir = “path/to/tmplete/dir”; $opt = array( “compile_dir"=>"path/to/compile/dir” ); $view = new ViewSmarty($tmplete_dir, $opt); // 以下のような設定も可能 //$opt = array( // “templete_dir” => “path/to/tmplete/dir”, // “compile_dir” => “path/to/compile/dir” //); //$view = new ViewSmarty(null, $opt); 以下のように変数をアサインして画面を描画します。 $view->foo = “foo”; $view->assign(“bar”, “bar”); $view->render(“hello.phtml”); ViewSmartyクラスをViewRendererアクションヘルパーに組み込む法方は以下のとおりです。 $viewRenderer = new Zend_Controller_Action_Helper_ViewRenderer(new My_View_Smarty(null, $option)); Zend_Controller_Action_HelperBroker::addHelper($viewRenderer); これでviewRendererでSmartyを使えるようになりました。 **2008/4/2追記** Zend Framework 1.5 でリファレンスマニュアルのSmartyラッパーの例をそのまま使うと、エラーが出る場合があるので[こちら](http://wadslab.net/2008/04/zend_view_smarty-4/)もご覧ください。 私は以下のようなviewを使っています。 require_once ‘Zend/View/Interface.
  • [Zend Framework] Zend_Controllerでの基本的なフロントコントローラ

    Zend_Controllerでフロントコントローラ(Zend_Controller_Front)は以下のような役割を果たします。 リクエストを適切なActionControllerへ引き渡す レスポンスの内容を取得して、リクエスト元へ返す Zend_Controllerでの基本的なフロントコントローラの作り方について書きます。 ○インスタンスの生成 Zend_Controller_Frontはシングルトンパターンも実装しているので、常に1つのインスタンスしか生成できません。つまり、newはできません(コンストラクタはprivateになっています)。またオブジェクトのハードコピーも出来ません(__clone()もprivateになってます)。 インスタンスの生成にはstaticメソッドのgetInstance()を使用します。``` /* インスタンス生成 */ // できる例 $front = Zend_Controller_Front::getInstance(); // できない例// $front = new Zend_Controller_Front; /* オブジェクトのコピー */ // できる例 $copy = $front; // 参照先(オブジェクトのあるアドレス)をコピー // できない例 $copy = clone $front; // ハードコピー(オブジェクトそのものがコピーされる) **○コントローラディレクトリの設定** フロントコントローラを動作させるためには、コントローラのあるディレクトリへのパスを最低ひとつ設定しないといけません。設定にはsetControllerDirectory()またはaddControllerDirectory()を使用します。 // デフォルト(default)モジュールのコントローラディレクトリを設定 $front->addControllerDirectory(‘path/to/the/controller’); // もしくは $front->setControllerDirectory(‘path/to/the/controller’); // ‘blog'モジュールのコントローラディレクトリを設定 $front->addControllerDirectory(‘path/to/controller’, ‘blog’); // もしくは $front->setControllerDirectory(‘path/to/controller’, ‘blog’); // 複数のモジュールを設定 $front->setControllerDirectory(array( ‘default’ => ‘path/to/controller’, ‘blog’ => ‘path/to/blog-module/controller’ )); // こんなのも可能です $front->addControllerDirectory(‘path/to/controller’) ->addControllerDirectory(‘path/to/blog-module/controller’, ‘blog’);
  • [Zend Framework] Zend_Validate_StringLengthの処理

    文字数が指定範囲内にあるかどうかをチェックするZend_Validate_StringLengthのバリデート処理は以下のようになっています。public function isValid($value) { $valueString = (string) $value; $this->\_setValue($valueString); $length = iconv\_strlen($valueString); if ($length < $this->\_min) { $this->\_error(self::TOO\_SHORT); } if (null !== $this->\_max && $this->\_max < $length) { $this->\_error(self::TOO\_LONG); } if (count($this->\_messages)) { return false; } else { return true; } }文字列の長さをiconv_strlen()という関数で出しています。 この関数は、第二引数が指定されていない場合エンコードはiconv.internal_encodingであると判断されます(Zend_Validate_StringLengthでは省略されています)。 iconv関連の設定はphp.iniではデフォルトでコメントアウトされていて、iconv.internal_encodingはデフォルト値のISO-8859-1(Latin1)となります。 このままでは、日本語でのバリデート処理がうまくいきません。 対策として、 1. php.iniの iconv.internal_encodingを使用している文字セットに変更する。 2. Zend_Validate_StringLengthのisValid()を呼び出す前に、iconv_set_encoding()でiconv.internal_encodの値を変更するutf-8の場合 ingiconv\_set\_encoding('internal\_encoding', 'utf-8');3. mbstringが有効になっているならば、Zend_Validate_StringLengthのisValidの部分を拡張して新しいクラスを作る。``` require_once ‘Zend/Validate/StringLength.php’; class Zend_Validate_MbstringLength extends Zend_Validate_StringLength { public function isValid($value) { $valueString = (string) $value; $this->_setValue($valueString); $length = mb_strlen($valueString); // ←ここを変更 if ($length < $this->_min) { $this->_error(self::TOO_SHORT); } if (null !
  • [Zend Framework] Zend_Controller クイックスタート

    復習の意味も込めて、Zend_Controllerについて。 Zend Frameworkのリファレンスガイドとソースコードを参考に書いていきます。 Zend_ControllerはZendFrameworkでのMVCモデルの根幹をなす部分で、フロントコントローラパターンを実装しています。 フロントコントローラパターンでは、リクエストをいったんフロントコントローラであるZend_Controller_Frontで受け取り、URLにもとづいて対応するアクションコントローラー(Zend_Controller_Actionを継承したクラス)のアクション(コントローラ内のメソッド)に処理を割りあてます。 アクションコントローラーはXxxController.php(最初の文字は大文字です)というファイルに、XxxControllerという名前で、Zend_Controller_Actionを継承したクラスを定義したものです。 アクションはアクションコントローラー内にyyyActionという名前で定義されているpublic メソッドです。``` // XxxController.phpの内容 require_once ‘Zend/Controller/Action.php’; class XxxController extends Zend_Controller_Action { public function yyyAction() { } } たとえば以下のようなURLにリクエストがあったとします。 http://exapmle.com/foo/bar この場合は、まずフロントコントローラで処理を受け取り、FooConroller.php内のFooControllerというクラスの、barAction()というメソッドが実行されます。ちなみに、コントローラー名が省略された場合にはindexコントローラーであるとみなされ、アクション名が省略された場合は同様にindexアクションであるとみなされます。 以下で、Hello Worldを出力するシンプルなアプリケーションの作成例を書きます。 今回は以下のようにアプリケーションを配置すると仮定し、http://localhost/ にアクセスした場合の例を書きます。 htdocs/ ├ application/ │ ├ controllers/ │ ├ models/ │ └ views/ │ ├ index.php └ .htaccess すべてのリクエストは一度フロントコントローラで受け取ります。今回この役割を果たすのは、htdocs直下にあるindex.phpです。 このPHPファイルにリクエストをまとめるために、apacheのmod\_rewriteを用いてURLを書き換えます。もし、apacheのmod\_rewriteが有効になっていない場合は有効にしておいてください。設定が有効になっていることを確認したら、.htaccessに以下のような記述を追加します。 RewriteEngine on RewriteRule !.(js|ico|gif|jpg|jpeg|png|css)$ index.php これは画像やスタイルシートへのリクエスト以外を、フロントコントローラ(htdocs/index.php)へ向けるための設定です。ここで設定した拡張子以外でもフロントコントローラへ処理をまわしたくないものがあれば、その拡張子を追加してください。 続いて、フロントコントローラの役割を果たすindex.phpの内容を以下のように書きます。 require_once ‘Zend/Controller/Front.php’; Zend_Controller_Front::run(‘application/controllers’); Zend\_Controller\_Front::run()の引数部分には、コントローラのあるディレクトリまでのパスを書きます。 これでリクエストされたurlに対応したコントローラのアクションへ処理が渡されます。 今回は、http://localhost/にアクセスする例ですが、このURLはコントローラー名とアクション名が省略されています。 上でも述べましたが、コントローラ名とアクション名が省略された場合は、それぞれindexという名前のものが割り当てられます。 今回の場合は、htdocs/application/controllers/IndexController.php のファイルに定義されている、IndexControllerクラスのindexAction() という関数が実行されます。 続いてコントローラを作成します。 htdocs/application/controllers ディレクトリ以下にIndexController.phpというファイルを作って、以下のように書きます。 require_once ‘Zend/Controller/Action.
  • [Zend Framework] Zend Frameworkのリファレンスマニュアルはすでに1.5仕様になっていた

    今日Zend_Db_Tableのリファレンスガイドを見ていたら以下のような記述がありました。 取得操作用のAPIは変更され、Zend\_Db\_Table\_Selectオブジェクトで クエリを変更できるようになりました。 しかし、昔ながらの方法であるfetchRow()やfetchAll()は今でも同じよ うに使用することができます。 次の文は、どれも正しくて同じ動作をします。しかし、新しい使用法に対応 するためにもできるだけ新しい書き方に変更することをお勧めします。 // 行セットを取得します $rows = $table->fetchAll('bug\_status = "NEW"', 'bug\_id ASC', 10, 0); $rows = $table->fetchAll($table->select()->where('bug\_status = ?', 'NEW') ->order('bug\_id ASC') ->limit(10, 0)); // 単一の行を取得します $row = $table->fetchRow('bug\_status = "NEW"', 'bug\_id ASC'); $row = $table->fetchRow($table->select()->where('bug\_status = ?', 'NEW') ->order('bug\_id ASC')); 1.0.3でこんな書き方(下のほうの例)できるのかなと調べたのですが、どうも無理そうです。 1.5RC1版をダウンロードしたところ、Zend/Db/Table/以下にSelect.phpというファイルがありました(Zend_Db_Table_Selectクラス)。 Zend_Db_Table_Selectクラスはよく見ていませんが、Zend_Db_Selectクラスを拡張したものです。 Zend_Db_Table_Abstractクラスのselect()メソッドでZend_Db_Table_Selectクラスのオブジェクトを取得でき、上記のような記述ができるようです(単に関数内で、newしてました)。 public function select() { require\_once 'Zend/Db/Table/Select.php'; return new Zend\_Db\_Table\_Select($this); } リファレンスマニュアルとその翻訳も1.5版に向けて徐々に加筆・修正がなされているようです。 本日、1.0.4のリリースと1.5RC1が利用可能であると、メーリングリストからお知らせが来ました。 1.0.4のリリースについては、以下のように書いてありました。 With this mini-release we bid a fond farewell to the 1.
  • [Zend Framework] Zend FrameworkでRSSフィードをproxy経由で取得

    手順としては、 Zend_Http_Clientを用いてproxy経由でRSSフィードを取得 レスポンスデータからRSSを取り出し、Zend_Feed::importString()で読み込む あとはZend_Feedを用いていろいろできます。 proxy経由でリクエストをかけるには、Zend_Http_ClientのアダプタをZend_Http_Client_Adapter_Proxyに設定して、サーバー名、ポートなどを指定すればOKです。 上記の通り取得したRSSはレスポンスデータのBody部にあるので、getBody()で取得します。 コードは以下の通りです。$confの設定はお使いの環境に合わせてください。``` require_once ‘Zend/Http/Client.php’; require_once ‘Zend/Feed.php’; // Zend_Http_Clientの設定 $conf = array( ‘adapter’ => ‘Zend_Http_Client_Adapter_Proxy’, ‘proxy_host’ => ‘server-name’, //proxyサーバー名、またはipアドレス ‘proxy_port’ => 80, // ポート番号 ); try { // リクエスト $client = new Zend_Http_Client(‘http://wadslab.net/feed/', $conf); $response = $client->request(); // Zend\_Feedにインポート $rss = Zend\_Feed::importString($response->getBody()); } catch (Zend_Exception $e) { // 例外処理 } …