ZendFramework入門・その2 アクションメソッドの追加とリンクの扱い方2007年09月03日 02時08分59秒

のっけからお詫び

すみません、このエントリ書くまで気づきませんでしたが、前回示したフォルダツリーで、ビュースクリプト関連で1階層抜けてました。具体的には「htdocs/zf1/application/views/scripts/index/」の「scripts」部分が抜けてました。どうもすみませんでした。

2つ目のアクションメソッド

気を取り直して、本題です。

少し期間が空いてしまいましたが、前回の続きでアクションコントローラを扱います。前回はデフォルトのアクションメソッド(indexAction)のみの実装でしたが、まずは2つ目のアクションメソッドを実装するところからはじめましょう。

アプリケーションは前回のものをそのまま流用しますので、これからやる作業は

  • IndexController.phpにアクションメソッド「secondAction」を追加する
  • secondActionに対応するビュースクリプト「second.phtml」を/views/scriptsに作成する
の2点です。

/application/controllers/IndexController.phpを以下のようにします。

<?php
require_once 'Zend/Controller/Action.php';

// IndexController
class IndexController extends Zend_Controller_Action {
	// indexアクション
	public function indexAction() {
		$this->view->assign( 'message', 'ようこそ Zend Frameworkへ' );
	}
	
	// 今回追加したsecondアクション
	public function secondAction() {
		// indexActionと同様にメッセージをassignする
		$this->view->assign( 'message', '2つ目のアクション' );
	}
}
IndexController.php

対応するsecond.phtmlは、とりあえずindex.phtmlをそのままコピーしてリネームしてください。

ここまでの作業で、ファイル一式は以下のようになります。

  • htdocs/
    • zf1/
      • application/
        • controllers/
          • IndexController.php → アクションメソッド「secondAction」を追加
        • views/
          • scripts/
            • index/
              • index.phtml
              • second.phtml → secondActionに対応するビュースクリプト
      • index.php
      • .htaccess → Apacheを使用している方はこれを忘れずに
この状態で「http://<host名>/zf1/index/second」にアクセスすると、「2つ目のアクション」と表示されるはずです。

以降、基本的には、「アクションメソッドを増やしたら、対応するビュースクリプトを追加する」というような拡張方法になります。

相互にリンクを張ってみる

さて、せっかくアクセスできるURLが2つに増えたのに、それぞれいちいちURLを直接打ち込んでいたら面倒この上ないので、それぞれのアクションメソッド(のビュー)からお互いにリンクを張ってみることにします。

ビュースクリプト中に直接記述してもいいのですが、リンクアドレスの情報はコントローラ側で管理し、ビュースクリプトはそれをレンダリングするだけに分離するのが筋だと思いますので、そのようにしてみます。

まずは2つのアクションメソッド内で「$this->view」にリンクさせるアドレスをassignさせます。こんな感じでしょうか。

<?php
require_once 'Zend/Controller/Action.php';

// IndexController
class IndexController extends Zend_Controller_Action {
	// indexアクション
	public function indexAction() {
		$this->view->assign( 'message', 'ようこそ Zend Frameworkへ' );
		
		// secondActionへのリンクをビューへ追加
		$this->view->assign( 'link', 'index/second' );
	}
	
	// secondアクション
	public function secondAction() {
		$this->view->assign( 'message', '2つ目のアクション' );
		
		// indexActionへのリンクをビューへ追加
		$this->view->assign( 'link', 'index/index' );
	}
}

そしてビュースクリプトです。index.phtml、second.phtmlとも以下の内容に修正します。

<html>
<head>
	<title>zf1</title>
</head>
<body>
	<h3>
		<?php echo $this->message; ?>
	</h3>
	<hr>
	
	<!-- プロパティ「link」の内容をhrefに使用する -->
	<a href="<?php echo $this->link; ?>"><?php echo $this->link; ?></a>
</body>
</html>
index.phtml、second.phtml

さて、まずは「http://<host名>/zf1/」にアクセスしてみてください。以下のようになるはずです。

ようこそ Zend Frameworkへ


index/second
http://<host名>/zf1/
ここまでは従来と変化がほとんどないので問題ないはずです。確認点としてはリンクがちゃんと表示されているか、くらいでしょうか。

次にページ中のリンク「index/second」をクリックしてみてください。以下のようになります。

2つ目のアクション


index/index
http://<host名>/zf1/index/second/
ちゃんと表示が変わっているでしょうか?

リンク先がなんだかヘン

さて、ここまで特に問題はないはずなのですが、どこかおかしい所はないでしょうか?

index/secondに表示されているリンクをよく確認してください。未訪問の色のままになっていませんか

リンク「index/index」にカーソルを合わせてみるとわかると思いますが、実は現在のサンプルのままでは、リンク先が「http://<host名>/zf1/index/index/index」になってしまいます。

よくよく考えてみると(よくよく、でなくてもわかりますが)、Zend Frameworkのコントローラモデルでは、すべてのアクセスがディレクトリパスに展開されるため、相対でリンクを示すと常に現在のパスに対して階層が追加されてゆくことになってしまいます。

これが、アプリケーションがホストのトップであれば、「/index/second」などと絶対パスが使用できるので問題ありませんが、アプリケーションごとにホストを用意したりバーチャルホストを用意したりするのはあまり現実的ではありません。

また、ドキュメント中に出現するURLはリンク先だけではありません。JavaScriptやCSSなどのリソース、画像、さらにはformのpost先など、1つのWebアプリケーション内でアプリケーション内のほかのURLを指定する必要があるところはたくさんありますので、このままではちょっと困ったことになってしまいます。

さて、どうやって解決したものでしょうか...

base要素を活用

これはdara-j的なアプローチですが、リンクの解決にbase要素を使用してみましょう。そう、すべてのページのhead内で「<base href="http://<host名>/zf1/"></base>」が出力されてさえいれば、前述のように「コントローラ名/アクション名」形式でリンクを指定することができます。

base要素に指定するアプリケーションのルートURLですが、直接ビュースクリプト中に指定してもよいのですが、他のアプリケーションを構築する際にもそのまま流用できるように関数を作成することにします。

起動スクリプト「index.php」に以下のような関数を追加します。

function getApplicationUrl($request) {
	if( $request == null ) return false;
	
	return ( preg_match('/^HTTPS/i', $request->getServer('SERVER_PROTOCOL')) ? 'https' : 'http' )
		. '://'
		. $request->getServer('HTTP_HOST')
		. $request->getBaseUrl();
}
引数として受け取る「$request」は、Zend_Controller_Request_Httpというオブジェクトになります。このオブジェクト(クラス)は従来$_GETや$_POSTなどのスーパーグローバル変数から取得していたリクエスト情報を一元的に集約したバリューオブジェクトです。当然Zend Framework上でも従来のスーパーグローバル変数へアクセスはできますが、せっかくのオブジェクト指向プログラミングでもあり、なるべくこちらを使うようにしたほうがよいと思います。

アクションコントローラの初期化機構

では、このgeApplicationUrl関数を使用して、実際にbase要素を出力するようにしてみましょう。引数にZend_Controller_Request_Httpが必要ということもあり、

  • アクションコントローラからgetApplicationUrlを呼び出して値を取得し、
  • ビュースクリプトへassignする
というアプローチにしてみたいと思います。

これまでと同じように、各アクションメソッド内から$this->view->assignとしてもよいのですが、これはコントローラ内での共通処理とするような内容なので、どこか一箇所に集約しましょう。

アクションコントローラは固有の初期化処理用として、「init」メソッドを用意しています。これはZend_Controller_Actionのコンストラクタから始まる一連の初期化処理の中でサブクラス固有の初期化処理用として用意しているもので、これをオーバーライドすることになります。

initメソッドをオーバーライドし、base要素向けのベースURLをビューへassignするコードを追加したIndexController.phpは以下のようになります。

<?php
require_once 'Zend/Controller/Action.php';

// IndexController
class IndexController extends Zend_Controller_Action {
	// initメソッド。初期化処理用のフックメソッド
	// アクセスレベルが「public」な点に注意
	public function init() {
		// リクエストオブジェクト(Zend_Controller_Request_Abstract)を取得
		$request = $this->getRequest();
		
		// ベースURLを取得
		$baseUrl = getApplicationUrl( $request );
		
		// ビューへ割り当てる
		$this->view->assign( 'baseUrl', $baseUrl );
	}
	
	// indexアクション
	public function indexAction() {
		$this->view->assign( 'message', 'ようこそ Zend Frameworkへ' );
		
		// secondActionへのリンクをビューへ追加
		$this->view->assign( 'link', 'index/second' );
	}
	
	// secondアクション
	public function secondAction() {
		$this->view->assign( 'message', '2つ目のアクション' );
		
		// indexActionへのリンクをビューへ追加
		$this->view->assign( 'link', 'index/index' );
		
		// indexAction用のビュースクリプトをレンダリング
		$this->_helper->viewRenderer( 'index' );
	}
}
IndexController.php(初期化処理を追加)
ちょっと補足になりますが、getApplicationUrl()に渡すリクエストオブジェクトを「Zend_Controller_Request_Http」と説明していましたが、コード中のコメントでは「Zend_Controller_Request_Abstract」としています。 これは別に先の説明で嘘をついていたとか間違っているとかではなく、Zend_Controller_Action::getRequest()メソッドは抽象クラス「Zend_Controller_Request_Abstract」を返す仕様になっていて、httpアクセス環境化ではhttp通信向けサブクラスである「Zend_Controller_Request_Http」を返している、ということです。getApplicationUrl()内ではZend_Controller_Request_Httpであることを前提としているのであまりよいコードではないのですが、この連載中ではCLIなどは想定していないのでこのままでご容赦ください。

もう一点補足です。secondActionメソッドの最後に「$this->_helper->viewRenderer('index')」という見慣れないコードを追加していますが、これはまったく同じ内容の2つのビュースクリプトを定義しているのが気持ち悪いため、2つのアクションメソッドで1つのビュースクリプトを共用するためのコードです。

_helperやviewRendererがいったい何なのかはこの連載でもいずれ取り上げますが、「viewRendererヘルパーの(暗黙のメソッドである)setRenderメソッドでレンダリングするビュースクリプトを変更できる」ということができる、ということだけ理解してくだされば問題ないと思います。詳細を知りたい場合はリファレンスガイドの「7.8. アクションヘルパー」、および同ページ中の「例 7.8. 別のビュースクリプトの選択」をご覧ください。

他のソースの改変

init内で「baseUrl」として割り当てられたURLをbase要素として出力するよう改変したindex.phtmlは以下のようになります(second.phtmlは不要になったので削除してください)。

<html>
<head>
	<title>zf1</title>
	<!-- base要素の出力。末尾の「/」に注意 -->
	<base href="<?php echo $this->baseUrl; ?>/"></base>
</head>
<body>
	<h3>
		<?php echo $this->message; ?>
	</h3>
	<hr>
	
	<!-- プロパティ「link」の内容をhrefに使用する -->
	<a href="<?php echo $this->link; ?>"><?php echo $this->link; ?></a>
</body>
</html>
index.phtml(base要素の出力を追加)
base要素の出力時で、href属性の最後に「/」を記述しているのは、単にgetApplicationUrl()が末尾に「/」がない状態の文字列を返すからです。base属性は末尾が「/」ではない場合有効に機能しないので注意してください。

そしてgetApplicationUrl関数を追加した、最終的なindex.phpは以下のようになります。

<?php
// エラーを表示するようにしておく
ini_set( 'display_errors', 1 );

// フロントコントローラをrequire
require_once 'Zend/Controller/Front.php';

// ルーティング&ディスパッチ開始
Zend_Controller_Front::run( './application/controllers' );

// アプリケーションのベースURLをリクエスト情報から生成
function getApplicationUrl($request) {
	if( $request == null ) return false;
	
	return ( preg_match('/^HTTPS/i', $request->getServer('SERVER_PROTOCOL')) ? 'https' : 'http' )
		. '://'
		. $request->getServer('HTTP_HOST')
		. $request->getBaseUrl();
}

index.php(getApplicationUrl関数追加)

今回の最終的なファイルツリーは以下のようになります。

  • htdocs/
    • zf1/
      • application/
        • controllers/
          • IndexController.php → init()、secondAction()を追加
        • views/
          • scripts/
            • index/
              • index.phtml → ベースURLとリンクの出力を追加
      • index.php → getApplicationUrl()関数を追加
      • .htaccess

今回のまとめ

今回のポイントは以下のようになります。

  • アクションメソッドを増やしたら、対応するビュースクリプトを追加する
  • 相対URLを正常に使用するにはbase要素でアプリケーションのベースURLを指定する
  • リクエスト情報の取得には、スーパーグローバルの代わりにZend_Controller_Request_Httpを使用する
  • アクションコントローラの初期化はinit()メソッドをオーバーライドする
  • ビュースクリプトの重複・分散を避けるために、$this->_helper->viewRenderer()を使用して他のアクションメソッドに関連付けられたビュースクリプトを共用することもできる

次回の予定

次回は(今回やる予定だった)エラーの処理方法の概要と、関連して複数のアクションコントローラを使用する例を扱ってみたいと思います。

連載記事って大変だわ。2007年09月03日 03時34分51秒

いやはや、思ったより大変だ...

なにを思ったのか、先日より「ZendFramework入門」なる連載記事をはじめてみたのだが、これが思ったよりも大変。先ほどあげた記事なんかは、もともと漠然とした構成は頭の中にあったので「1時間くらいでできるかな?」などと考えていたのだが、結局3時間ほどかかってしまった。

書いているうちに「あれも必要では?」「これはどこに差し込もう?」なんて迷いがいろいろでるというのもあるし、なによりテキストエディタでHTMLを直接書いているという前時代的な作業が遅くなる原因なんだろうけど。

よく間違えるし

もともとHTML書くのにツールを使う習慣がないので、通常の記事であればエディタでしこしこHTML書くのはそんなに苦にならないんだけど(量を書かないからね)、今回ネックになるのは

  • HTMLのソース
  • フォルダツリー
の2点で、前者は実体参照が、後者はリストの入れ子が結構大変。こんなの手作業でやるのはなんか間違っている気はするのだが、ほどよいツールがなく結局しこしこと書くハメになってしまっている。もうちょっとなんか考えないとなぁ。

うれしいこともある

もちろん大変なことだけではなく、うれしいこともある。まだはじめたばかりで海のものとも山のものともつかないしょぼい連載なのだが、いくつかのサイトさんでこの連載をご紹介くださっているのだ。

紹介してくだすったお礼も兼ねて(兼ねられるのか?)、リンクを示しておこう。

あるSEのつぶやきさん、PHPの種さん、ご紹介ありがとうございます。

へこむこともある

ただ、上記の「PHPの種」さんのところで別の入門記事(というかクイックスタート)を紹介されているのだが、こんなの見ると「俺やってる意味あんのかな」とかへこんだりへこまなかったり。

ほんとにラボラットさんの記事は簡潔にうまくまとめてるので、正直ここの連載より役立つと思いますorz

こっちなんか2回(準備編入れて3回か)も費やしてるのに内容的にはラボラットさんとこより薄いもんなぁ。

今後はもう少し内容を濃くするようにがんばろう。