4日目: Controller と View
昨日は symfony がどうやってデータベースエンジン間の違いを吸収していたり、オブジェクト指向クラスに変換しているかを見ました。そして ##ORM## でデータベーススキーマを記述したりテーブルを作成したり初期データをデータベースに投入したりしました。
今日は昨日作った job
モジュールの基本的なカスタマイズを続けます。job
モジュールは Jobeet に必要なコードをすべて有しています:
- 求人の一覧ページ
- 新しく求人を投稿するページ
- 投稿した求人を更新するページ
- 求人を削除するページ
すでにコードの準備ができてるのでモックアップに近づくようにリファクタリングしてゆきます。
~MVC~ アーキテクチャ
もしフレームワークなしで PHP で Web サイトの開発を行うならば、HTML ごとに1つの PHP ファイルのパラダイムを使うでしょう。これらの PHP ファイルは同じ種類の構造を含んでます。それは初期化、全体設定、ページリクエストのためのビジネスロジックやデータベースからレコードの検索、最終的にはページを生成するための HTML コードを含んでいます。
HTML からロジックを分離するためにテンプレートエンジンを利用しているかもしれません。ビジネスロジックからモデルとのやりとりを分離するためにデータベース抽象化レイヤーを利用しているでしょう。しかしたいていの場合、メンテナンスが悪夢になるたくさんのコードで終わることになります。速く作れますが、時間が経つにつれて、とりわけ変更するのが難しくなります。どのように作り、どのように動くのか、あなた以外は誰も理解できないからです。
これらすべての問題に対し、よい解決方法があります。Web 開発の分野では近年コーディングのための最適解として認識されているのはMVC ~デザインパターン~です。手短に言えば、MVC デザインパターンはコードの性質ごとに体系化する方法を定義しています。このパターンは3つのレイヤーにわけられます。
-
~Model~ レイヤーはビジネスロジックを定義します (データベースはこのレイヤーに所属する)。ご存じのとおり、symfony は Model に関連するすべてのクラスとファイルを
lib/model/
ディレクトリに保存します。 -
~View~ はユーザーが情報をやりとりするレイヤーです (テンプレートエンジンはこのレイヤーの一部)。symfony において、View レイヤーは主に PHP テンプレートで構成されます。今日の後で見るようにこれらは
templates/
ディレクトリに保存されます。 -
~Controller~ はモデルからデータを取得し、クライアントへ表示するため View にデータを渡す処理を担当します。symfony をインストールした初日に、すべてのリクエストはフロントコントローラ (
index.php
とfrontend_dev.php
) によって管理されているのを見ました。
これらフロントコントローラは実際の動作はアクション (action) で行われます。昨日見たようにこれらアクションはモジュール (module) で論理的にグループにわけられます。
今日は、ホームページと求人ページをカスタマイズするために2日目で定義したモックアップを使います。これらを動的なものにもします。この先、symfony のディレクトリを構造とレイヤーのあいだでコードを分離する方法を示すためにたくさんの異なるファイルでたくさんの調整を行います。
レイアウト
まず、モックアップをじっと見てみると各ページのほとんどが同じ部品であることに気づくでしょう。PHP や HTML であろうとなかろうと、コードの重複はわるいことです。ですので、コードが重複している View 要素を抑える方法が必要となります。
この問題を解決する1つの方法としてテンプレートごとにヘッダーとフッターを定義する方法があります:
しかし、この場合、ヘッダーやフッターは有効な HTML を含んでいません。よい方法であることは違いありません。車輪の再発明をする代わりに、この問題を解決するため別のデザインパターンを使うことにします。それは ~Decorator~ デザインパターンです。Decorator デザインパターンは別のやり方で問題を解決します。
グローバルテンプレートによって表示されるコンテンツの後に、デコレートされるテンプレートを使います。
symfony ではグローバルテンプレートを~レイアウト~と呼びます:
アプリケーションのデフォルトテンプレートとして layout.php
が呼び出されます。それは apps/frontend/templates
ディレクトリにあります。このディレクトリにはアプリケーションのグローバルテンプレートすべてが置かれます。
symfony のデフォルトレイアウトを下記コードに置き換えましょう:
<!-- apps/frontend/templates/layout.php --> <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> <head> <title>Jobeet - Your best job board</title> <link rel="shortcut icon" href="/favicon.ico" /> <?php include_javascripts() ?> <?php include_stylesheets() ?> </head> <body> <div id="container"> <div id="header"> <div class="content"> <h1><a href="<?php echo url_for('job/index') ?>"> <img src="/images/logo.jpg" alt="Jobeet Job Board" /> </a></h1> <div id="sub_header"> <div class="post"> <h2>Ask for people</h2> <div> <a href="<?php echo url_for('job/index') ?>">Post a Job</a> </div> </div> <div class="search"> <h2>Ask for a job</h2> <form action="" method="get"> <input type="text" name="keywords" id="search_keywords" /> <input type="submit" value="search" /> <div class="help"> Enter some keywords (city, country, position, ...) </div> </form> </div> </div> </div> </div> <div id="content"> <?php if ($sf_user->hasFlash('notice')): ?> <div class="flash_notice"> <?php echo $sf_user->getFlash('notice') ?> </div> <?php endif ?> <?php if ($sf_user->hasFlash('error')): ?> <div class="flash_error"> <?php echo $sf_user->getFlash('error') ?> </div> <?php endif ?> <div class="content"> <?php echo $sf_content ?> </div> </div> <div id="footer"> <div class="content"> <span class="symfony"> <img src="/images/jobeet-mini.png" /> powered by <a href="http://www.symfony-project.org/"> <img src="/images/symfony.gif" alt="symfony framework" /> </a> </span> <ul> <li><a href="">About Jobeet</a></li> <li class="feed"><a href="">Full feed</a></li> <li><a href="">Jobeet API</a></li> <li class="last"><a href="">Affiliates</a></li> </ul> </div> </div> </div> </body> </html>
symfony の~テンプレート~は単なるプレーンな PHP ファイルです。レイアウトテンプレートにおいて、PHP 関数の呼び出しと PHP 変数への参照が見られます。~$sf_content
~ はもっとも興味深い変数です: この変数には symfony によって定義され、アクションによって生成される HTML が収められています。
job
モジュール (http://jobeet.localhost/frontend_dev.php/job
) を見ると、すべてのアクションがレイアウトによってデコレートされていることがわかります。
スタイルシート、画像、JavaScript
このチュートリアルの目的は Web デザインではないので、Jobeet で必要なすべてのアセットはすでに用意されています: 画像ファイルをダウンロードして web/images/
ディレクトリに設置します; スタイルシートファイルをダウンロードして web/css/
ディレクトリに設置します。
レイアウトにおいて、ファビコン (favicon) を含めることができます。Jobeet のファビコンをダウンロードして
web/
ディレクトリに設置します。
標準では、
generate:project
タスクはプロジェクトのアセットとして3つのディレクトリを生成します。web/images
は画像用、web/~css|CSS~
は~スタイルシート~用、web/js
は ~JavaScript~ 用です。これは symfony で定義される多くの~規約~の1つですが、もちろんweb
ディレクトリの下であればどこにでも置くことはできます。
鋭い読者なら main.css
がデフォルトレイアウトのどこにも記述されていないことにお気づきでしょう。main.css
は生成された HTML のなかに確かに含まれています。しかしどこにも見当たりません。どうやって可能にしているのでしょうか?
スタイルシートはレイアウトの <head>
タグブロックで見つかる include_stylesheets()
関数の呼び出しによってインクルードされました。include_stylesheets()
関数はヘルパー (helper) と呼ばれます。ヘルパーは symfony によって定義される関数で、パラメータを受け取り、HTML コードを返します。たいていの場合、ヘルパーによって時間が節約され、テンプレートで頻繁に使われるコードスニペットをパッケージにまとめます。
include_stylesheets()
ヘルパーはスタイルシート用に <link>
タグを生成します。
それにしても、ヘルパーはどうやって格納するスタイルシートを知るのでしょうか?
~View~ レイヤーはアプリケーションの設定ファイルである ~view.yml
~ のスタイルシートのキーを編集することで設定できます。generate:app
タスクがデフォルトで生成する view.yml
は次のとおりです:
---
# apps/frontend/config/view.yml
default:
http_metas:
content-type: text/html
metas:
#title: symfony project
#description: symfony project
#keywords: symfony, project
#language: en
#robots: index, follow
stylesheets: [main.css]
javascripts: []
has_layout: on
layout: layout
view.yml
ファイルはアプリケーションのすべてのテンプレートの default
を設定します。たとえば、stylesheets
エントリはアプリケーションすべてのページに含むためのスタイルシートファイルの配列が定義されます (含めたファイルは include_stylesheets()
ヘルパーから呼び出されます)。
標準の
view.yml
ファイルには、参照ファイルとしてmain.css
が設定されており、/css/main.css
ではありません。実際のところ、symfony によって相対パスに~プレフィックス~の/~css|CSS~/
がつけられるので、両方の定義は同じです。
多くのファイルが定義されたのであれば、定義と同じ順序でこれらをインクルードします:
---
stylesheets: [main.css, jobs.css, job.css]
media
属性を変更し、.css
を省略することもできます。:
この設定では下記のような表示となります:
<link rel="stylesheet" type="text/css" media="screen" href="/css/main.css" /> <link rel="stylesheet" type="text/css" media="screen" href="/css/jobs.css" /> <link rel="stylesheet" type="text/css" media="screen" href="/css/job.css" /> <link rel="stylesheet" type="text/css" media="print" href="/css/print.css" />
view.yml
設定ファイルはアプリケーションによって使われるデフォルトの~レイアウト~も定義します。デフォルトでは、名前はlayout
で、symfony はすべてのページをlayout.php
ファイルでデコレートします。~has_layout
~ エントリをfalse
に切り替えることでデコレーションプロレスを無効にすることもできます。
jobs.css
ファイルはホームページに必要とされるときだけ読み込まれ、job.css
ファイルは求人ページだけに適用されます。 view.yml
ファイルはモジュール単位でカスタマイズできます。アプリケーションの view.yml
ファイルは main.css
だけをもつように変更します:
---
# apps/frontend/config/view.yml
stylesheets: [main.css]
job
モジュールの View をカスタマイズするには、apps/frontend/modules/job/config
ディレクトリのなかで view.yml
ファイルを生成します:
---
# apps/frontend/modules/job/config/view.yml
indexSuccess:
stylesheets: [jobs.css]
showSuccess:
stylesheets: [job.css]
indexSuccess
と showSuccess
セクション (index
と show
アクションで使われるテンプレート名であり、後で出てきます) の下で、アプリケーションの view.yml
のデフォルトセクションで見たようなエントリを使ってカスタマイズできます。すべての固有のエントリはアプリケーションのコンフィギュレーションとしてマージされます。all
セクションを使えば、モジュールのすべてのアクションに対してコンフィギュレーションを定義できます。
symfony のコンフィギュレーションの原則
symfony の多くの~コンフィギュレーション~ファイル間では、異なるレベル単位で同じ設定が定義できます:
- デフォルトコンフィギュレーションはフレームワーク内にあります
- プロジェクトに対応するグローバルコンフィギュレーションは
config
ディレクトリにあります- アプリケーションに対応するローカルコンフィギュレーションは
apps/APP/config
ディレクトリにあります- モジュールにだけ適用されるローカルコンフィギュレーションは
apps/APP/modules/MODULE/config
ディレクトリにあります実行時には、設定システムはファイルが存在するかキャッシュを見つけると、すべての値をマージしようとします。
経験上、設定ファイル経由で変更可能なのは、PHP コードで完成するのと同じことです。例として job
モジュールに view.yml
ファイルを作る代わりに、テンプレートからスタイルシートを呼び出すための ~use_stylesheet()
ヘルパー~を使うこともできます:
<?php use_stylesheet('main.css') ?>
スタイルシートを全体に含めるために、レイアウト内で上記のヘルパーを使うことも可能です。
このメソッドもしくはほかのメソッドを選ぶのは本当に好みの問題です。view.yml
ファイルはモジュールのすべてのアクション用の内容を定義する方法を提供します。これはテンプレートでは定義できませんが、コンフィギュレーションはとても静的です。 一方で、use_stylesheet()
~ヘルパー~を利用すればより柔軟になり、さらに、同じ場所ですべての内容: スタイルシートの定義と HTML コードを同じ位置で定義されます。Jobeet に関しては、use_stylesheet()
ヘルパーを使うので、先ほど作った view.yml
を削除して use_stylesheet()
を呼び出して job
テンプレートを更新できます:
<!-- apps/frontend/modules/job/templates/indexSuccess.php --> <?php use_stylesheet('jobs.css') ?> <!-- apps/frontend/modules/job/templates/showSuccess.php --> <?php use_stylesheet('job.css') ?>
対照的に、JavaScript のコンフィギュレーションも
view.yml
のjavascripts
エントリを使ったり、テンプレートのなかから ~use_javascript()
ヘルパー~で呼び出すことで行うことができます。
job モジュールのホームページ
3日目に見たように、ホームページは job
モジュールの index
アクションで作られています。index
アクションはページの Controller 部分で、関連テンプレートである indexSuccess.php
は View 部分です:
apps/
frontend/
modules/
job/
actions/
actions.class.php
templates/
indexSuccess.php
アクション
各~アクション~はクラスメソッドで表されます。ホームページでは jobActions
クラス (モジュール名の末尾に Actions
をつけたもの) と executeIndex
メソッド (execute
の末尾にアクション名をつけたもの) が使われます。データベースからすべての求人情報を取得します:
// apps/frontend/modules/job/actions/actions.class.php class jobActions extends sfActions { public function executeIndex(sfWebRequest $request) {
$this->jobeet_jobs = JobeetJobPeer::doSelect(new Criteria()); $this->jobeet_jobs = Doctrine::getTable('JobeetJob') ->createQuery('a') ->execute(); }
// ...
}
コードをよく見てみましょう: executeIndex()
メソッド (Controller) はすべての求人情報を検索するために Model のJobeetJobPeer
を呼び出します (new Criteria()
)。これは jobeet_jobs
オブジェクトプロパティに割り当てられた JobeetJob
オブジェクトの配列を返します。 コードをよく見てみましょう: すべての求人情報を検索するクエリを作るために executeIndex()
メソッド (Controller) はテーブルの JobeetJob
を呼び出します。jobeet_jobs
オブジェクトプロパティに割り当てられた JobeetJob
オブジェクトの Doctrine_Collection
を返します。 このようなすべてオブジェクトプロパティは自動的にテンプレート (View) に渡されます。Controller からのデータを View に渡すには、次のように新しいプロパティを作ります:
public function executeFooBar(sfWebRequest $request) { $this->foo = 'bar'; $this->bar = array('bar', 'baz'); }
このコードはテンプレートからアクセス可能な $foo
と $bar
変数を定義しています。
テンプレート
デフォルトでは、アクションに関連する~テンプレート~は symfony によって推測されます (アクションの名前に サフィックスの Success
をつけたもの)。
indexSuccess.php
テンプレートはすべての求人用の HTML テーブルを生成します。現在のテンプレートコードは次のとおりです:
<!-- apps/frontend/modules/job/templates/indexSuccess.php --> <?php use_stylesheet('jobs.css') ?> <h1>Job List</h1> <table> <thead> <tr> <th>Id</th> <th>Category</th> <th>Type</th> <!-- more columns here --> <th>Created at</th> <th>Updated at</th> </tr> </thead> <tbody> <?php foreach ($jobeet_jobs as $jobeet_job): ?> <tr> <td> <a href="<?php echo url_for('job/show?id='.$jobeet_job->getId()) ?>"> <?php echo $jobeet_job->getId() ?> </a> </td> <td><?php echo $jobeet_job->getCategoryId() ?></td> <td><?php echo $jobeet_job->getType() ?></td> <!-- more columns here --> <td><?php echo $jobeet_job->getCreatedAt() ?></td> <td><?php echo $jobeet_job->getUpdatedAt() ?></td> </tr> <?php endforeach ?> </tbody> </table> <a href="<?php echo url_for('job/new') ?>">New</a>
テンプレートコードのなかでは foreach
と Job
オブジェクト ($jobeet_jobs
) のリストを繰り返し取得して、各求人ごとのカラムごとに出力させます。覚えておいて欲しいのは、カラムの値は get
から始まりカラム名の~ラクダ記法|コードのフォーマッティング~ になっているアクセサメソッドが呼び出されていることです (たとえば、getCreatedAt()
メソッドは create_at
カラムからデータを取得します)。
利用できるカラムのみを表示するように整理してみましょう:
<!-- apps/frontend/modules/job/templates/indexSuccess.php --> <?php use_stylesheet('jobs.css') ?> <div id="jobs"> <table class="jobs"> <?php foreach ($jobeet_jobs as $i => $job): ?> <tr class="<?php echo fmod($i, 2) ? 'even' : 'odd' ?>"> <td class="location"><?php echo $job->getLocation() ?></td> <td class="position"> <a href="<?php echo url_for('job/show?id='.$job->getId()) ?>"> <?php echo $job->getPosition() ?> </a> </td> <td class="company"><?php echo $job->getCompany() ?></td> </tr> <?php endforeach ?> </table> </div>
テンプレートの中で呼び出されている url_for()
関数は symfony のヘルパーで、明日詳しく説明します。
job ページのテンプレート
求人ページのテンプレートをカスタマイズしましょう。showSuccess.php
ファイルを開いて、次のコードに置き換えてください:
<!-- apps/frontend/modules/job/templates/showSuccess.php --> <?php use_stylesheet('job.css') ?> <?php use_helper('Text') ?> <div id="job"> <h1><?php echo $job->getCompany() ?></h1> <h2><?php echo $job->getLocation() ?></h2> <h3> <?php echo $job->getPosition() ?> <small> - <?php echo $job->getType() ?></small> </h3> <?php if ($job->getLogo()): ?> <div class="logo"> <a href="<?php echo $job->getUrl() ?>"> <img src="/uploads/jobs/<?php echo $job->getLogo() ?>" alt="<?php echo $job->getCompany() ?> logo" /> </a> </div> <?php endif ?> <div class="description"> <?php echo simple_format_text($job->getDescription()) ?> </div> <h4>How to apply?</h4> <p class="how_to_apply"><?php echo $job->getHowToApply() ?></p> <div class="meta">
posted on getCreatedAt('m/d/Y') ?> posted on getDateTimeObject('created_at')->format('m/d/Y') ?>
<div style="padding: 20px 0">
<a href="<?php echo url_for('job/edit?id='.$job->getId()) ?>">
Edit
</a>
</div>
</div>
求人情報を表示するために、テンプレートはアクションから渡される $job
変数を使います。テンプレートへ渡す変数を $jobeet_job
から $job
にリネームしたいので、show
アクションの該当箇所を変更してください (変数が2カ所にあることにご注意ください):
// apps/frontend/modules/job/actions/actions.class.php public function executeShow(sfWebRequest $request) {
$this->job = ➥ JobeetJobPeer::retrieveByPk($request->getParameter('id')); $this->job = Doctrine::getTable('JobeetJob')-> ➥ find($request->getParameter('id')); $this->forward404Unless($this->job); }
Propel ~アクセサ~のなかには引数を受け取るものがあることにご注意ください。created_at
カラムをタイムスタンプとして定義したので、getCreatedAt()
アクセサは最初の引数として日付の整形パターンを受け取ります:
$job->getCreatedAt('m/d/Y');
日付のカラムを PHP の DateTime オブジェクトインスタンスに変換できることに注目してください。created_at
カラムをタイムスタンプとして定義したので、getDateTimeObject()
メソッドを使うことでカラムの値を DateTime オブジェクトに変換することができ、最初の引数として日付の書式パターンをとる format()
メソッドを呼び出します:
$job->getDateTimeObject('created_at')->format('m/d/Y');
求人の説明文で使われている
simple_format_text()
ヘルパーは HTML を整形します。たとえば、改行を<br />
へ置き換えます。このヘルパーはText
ヘルパーグループに属しており、デフォルトではロードされないので、~use_helper()
ヘルパー~を使って手動でロードさせています。
~スロット~
今のところ、すべてのページのタイトルはレイアウトの <title>
タグで定義されます:
<title>Jobeet - Your best job board</title>
しかし求人ページでは会社名や役職のようなもっと役に立つ情報を提供したいと考えます。
symfony ではレイアウトの領域が表示されるテンプレートに依存するとき、スロットを定義する必要があります:
動的にタイトルを変更するために、スロットをレイアウトに追加します:
// apps/frontend/templates/layout.php <title><?php include_slot('title') ?></title>
各スロットは (title
) という名前で定義され、~include_slot()
~ ヘルパーで表示されます。今から showSuccess.php
テンプレートの冒頭部分で、求人ページのコンテンツについて定義した slot()
ヘルパーを使うようにします:
// apps/frontend/modules/job/templates/showSuccess.php <?php slot( 'title', sprintf('%s is looking for a %s', $job->getCompany(), $job->getPosition())) ?>
タイトルが複雑であるなら、slot()
ヘルパーをコードブロックで使うこともできます:
// apps/frontend/modules/job/templates/showSuccess.php <?php slot('title') ?> <?php echo sprintf('%s is looking for a %s', $job->getCompany(), $job->getPosition()) ?> <?php end_slot() ?>
(ホームページのように)ページのなかには、一般的なタイトルが必要となる場合があります。テンプレートのなかで同じタイトルを何度も繰り返す代わりに、レイアウトのなかでデフォルトのタイトルを定義します:
// apps/frontend/templates/layout.php <title> <?php include_slot('title', 'Jobeet - Your best job board') ?> </title>
include_slot()
メソッドの第2引数はスロットが定義されていない場合のデフォルト値です。デフォルトの値が長いもしくは HTML タグをもつ場合、次のコードのように定義できます:
// apps/frontend/templates/layout.php <title> <?php if (!include_slot('title')): ?> Jobeet - Your best job board <?php endif ?> </title>
include_slot()
ヘルパーはスロットが定義されていれば true
を返します。よって、テンプレートコンテンツのなかで title
スロットが定義されていれば、それを使い、デフォルトタイトルを使うようになります:
ごくわずかですが
include_
で始まるヘルパーを見てきました。これらのヘルパーは HTML を出力し、たいていの場合、内容を返すためだけにget_
ヘルパーに対応するものがあります:<?php include_slot('title') ?> <?php echo get_slot('title') ?> <?php include_stylesheets() ?> <?php echo get_stylesheets() ?>
求人ページのアクション
求人のページは job
モジュールの executeShow()
メソッドで定義される show
アクションで生成されます:
class jobActions extends sfActions { public function executeShow(sfWebRequest $request) {
$this->job = ➥ JobeetJobPeer::retrieveByPk($request->getParameter('id')); $this->job = Doctrine::getTable('JobeetJob')-> ➥ find($request->getParameter('id')); $this->forward404Unless($this->job); }
// ...
}
index
アクションに関しては、今回の場合、JobeetJobPeer
クラスは retrieveByPk()
メソッドを利用して求人情報を検索するために使われます。このメソッドのパラメータは job、~主キー~の一意性をもつ識別子です。次の節では $request->getParameter('id')
ステートメントが job の主キーを返す理由を説明します。 index
アクションに関しては、今回の場合、JobeetJob
テーブルクラスは find()
メソッドを利用して求人情報を検索するために使われます。このメソッドのパラメータは job、~主キー~の一意性をもつ識別子です。次の節では $request->getParameter('id')
ステートメントが job の主キーを返す理由を説明します。
生成モデルクラスには、プロジェクトのオブジェクトとやりとりするためにたくさんの便利なコードが入っています。
lib/om/
ディレクトリに設置されるコードを見る時間をかけ、これらのクラスに埋め込まれているすべての力を見つけ出してください。
求人データがデータベースに存在しない場合に、ユーザーを~404エラー~のページに転送させることを考えます。この役目はまさに forward404Unless()
メソッドが担うことです。最初の引数のブール値をチェックして、true
でなければ現在実行中のフローを中止します。forward
メソッドは sfError404Exception
に投げて実行中のアクションをすぐに停止させるので、その後で return 文は必要ありません。
~例外|例外処理~に関して、ユーザーに表示されるページは prod
~環境~と dev
環境で異なります:
Jobeet の Web サイトを運用サーバーにデプロイする前に、デフォルトの404エラーページをカスタマイズする方法を学びます。
~「forward」|アクションのフォワード~メソッドファミリ
forward404Unless
呼び出しは実際には次のコードと同じです:$this->forward404If(!$this->job);また次のコードとも同じです:
if (!$this->job) { $this->forward404(); }
forward404()
メソッド自身は次のコードのショートカットにすぎません:$this->forward('default', '404');
forward()
メソッドは同じアプリケーションの別のアクションに転送します。前の例ではdefault
モジュールの404
アクションに転送します。default
モジュールは symfony に搭載されており、デフォルトのアクションを提供します。
リクエストとレスポンス
/job
ページや /job/show/id/1
ページをブラウザ上で見る際、データが Web サーバーのあいだを往復し始めます。ブラウザは~リクエスト|HTTP リクエスト~ (request) を送信し、サーバーは~レスポンス|HTTP レスポンス~ (response) を返します。
すでに symfony がリクエストを sfWebRequest
オブジェクトでカプセル化されるのは見ました (executeShow()
メソッドをご覧ください)。symfony はオブジェクト指向フレームワークであるのでレスポンスもオブジェクトです。これは sfWebResponse
クラスです。$this->getResponse()
メソッドを呼び出すことで、アクションのなかからレスポンスオブジェクトにアクセスすることができます。
これらオブジェクトは PHP 関数やグローバル変数から情報を受け取るために便利なメソッドをたくさん提供します。
なぜ symfony は既存の PHP 関数をラップしているのでしょうか?第一に、symfony のメソッドは PHP の標準関数より強力です。そして、アプリケーションのテストをするときは、グローバル変数をあれこれいじったり、マジックのような
header()
関数を使うよりも、リクエストやレスポンスオブジェクトを使えばもっと簡単になります。
リクエスト
sfWebRequest
クラスは ~$_SERVER
~、~$_COOKIE
~、~$_GET
~、~$_POST
~、~$_FILES
~ といった PHP のスーパーグローバルをラップしています:
メソッドの名前 | 対応する PHP のスーパーグローバル |
---|---|
getMethod() |
$_SERVER['REQUEST_METHOD'] |
getUri() |
$_SERVER['REQUEST_URI'] |
getReferer() |
$_SERVER['HTTP_REFERER'] |
getHost() |
$_SERVER['HTTP_HOST'] |
getLanguages() |
$_SERVER['HTTP_ACCEPT_LANGUAGE'] |
getCharsets() |
$_SERVER['HTTP_ACCEPT_CHARSET'] |
isXmlHttpRequest() |
$_SERVER['X_REQUESTED_WITH'] == 'XMLHttpRequest' |
getHttpHeader() |
$_SERVER |
getCookie() |
$_COOKIE |
isSecure() |
$_SERVER['HTTPS'] |
getFiles() |
$_FILES |
getGetParameter() |
$_GET |
getPostParameter() |
$_POST |
getUrlParameter() |
$_SERVER['PATH_INFO'] |
getRemoteAddress() |
$_SERVER['REMOTE_ADDR'] |
すでに getParameter()
メソッドを使ってリクエストパラメータにアクセスしました。このメソッドは $_GET
または $_POST
グローバル変数や ~PATH_INFO
~ 変数から値を返します。
これらのなかの特定の1つを取得できるようにしたいのであれば、getGetParameter()
や getPostParameter()
や getUrlParameter()
メソッドを利用する必要があります。
特定の ~HTTP メソッド~用のアクションを制限したい場合、たとえばフォームが
POST
として投稿されることを保証したい場合、isMethod()
メソッドを使うことができます:$this->forwardUnless($request->isMethod('POST'));
レスポンス
sfWebResponse
クラスは PHP 関数の ~header|HTTP ヘッダー~()
と setraw~cookie|Cookie~()
をラップします:
メソッドの名前 | 対応する PHP 関数 |
---|---|
setCookie() |
setrawcookie() |
setStatusCode() |
header() |
setHttpHeader() |
header() |
setContentType() |
header() |
addVaryHttpHeader() |
header() |
addCacheControlHttpHeader() |
header() |
もちろん sfWebResponse
クラスはレスポンスのコンテンツをセットする方法 (setContent()
) とブラウザにレスポンスを送る方法 (send()
) も提供します。
本日のチュートリアルの最初の方で view.yml
とテンプレートの両方でスタイルシートや JavaScript を管理するやり方を見ました。結局2つのテクニックともレスポンスオブジェクトの addStylesheet()
と addJavascript()
メソッドを使います。
sfAction
、sfRequest
、sfResponse
クラスもたくさんの有用なメソッドを提供します。API ドキュメントを読んで symfony の内部クラスをもっと学習しましょう。
また明日
今日は、symfony で使われているいくつかのデザインパターンを説明しました。プロジェクトのディレクトリ構造の理解が進むことを願っております。レイアウトとテンプレートファイルを操作することでテンプレートで遊びました。スロットとアクションのおかげでこれらを少し動的なものに変えることもしました。
明日は、今日使った url_for()
ヘルパーとルーティングサブフレームワークについて学びます。
ORM
インデックス
Document Index
関連ページリスト
Related Pages
- 1日目: プロジェクトを始める
- 2日目: プロジェクト
- 3日目: ~データモデル~
- 4日目: Controller と View
- 5日目: ルーティング
- 6日目: モデルの詳細
- 7日目: カテゴリページで遊ぶ
- 8日目: ユニットテスト
- 9日: 機能テスト
- 10日目: フォーム
- 11日目: フォームをテストする
- 12日目: アドミンジェネレータ
- 13日目: ユーザー
- 14日目: フィード
- 15日目: Web サービス
- 16日目: ~メーラー~
- 17日目: 検索
- 18日目: ~AJAX~
- 19日目: 国際化とローカライゼーション
- 20日目: プラグイン
- 21日目: キャッシュ
- 22日目: デプロイ
- 23日目: 別の視点から symfony を見る
- Appendix A - License
- 謝辞
日本語ドキュメント
Japanese Documents
- 2011/01/18 Chapter 17 - Extending Symfony
- 2011/01/18 The generator.yml Configuration File
- 2011/01/18 Les tâches
- 2011/01/18 Emails
- 2010/11/26 blogチュートリアル(8) ビューの作成
リリース情報
Release Information
- 2.0 : 2.0.15(2011/05/30)
Symfony2日本語ドキュメント - 1.4 : 1.4.18(2012/05/30)
Changelog