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.phpfrontend_dev.php) によって管理されているのを見ました。

これらフロントコントローラは実際の動作はアクション (action) で行われます。昨日見たようにこれらアクションはモジュール (module) で論理的にグループにわけられます。

MVC

今日は、ホームページと求人ページをカスタマイズするために2日目で定義したモックアップを使います。これらを動的なものにもします。この先、symfony のディレクトリを構造とレイヤーのあいだでコードを分離する方法を示すためにたくさんの異なるファイルでたくさんの調整を行います。

レイアウト

まず、モックアップをじっと見てみると各ページのほとんどが同じ部品であることに気づくでしょう。PHP や HTML であろうとなかろうと、コードの重複はわるいことです。ですので、コードが重複している View 要素を抑える方法が必要となります。

この問題を解決する1つの方法としてテンプレートごとにヘッダーとフッターを定義する方法があります:

ヘッダーとフッター

しかし、この場合、ヘッダーやフッターは有効な HTML を含んでいません。よい方法であることは違いありません。車輪の再発明をする代わりに、この問題を解決するため別のデザインパターンを使うことにします。それは ~Decorator~ デザインパターンです。Decorator デザインパターンは別のやり方で問題を解決します。

グローバルテンプレートによって表示されるコンテンツの後に、デコレートされるテンプレートを使います。

symfony ではグローバルテンプレートを~レイアウト~と呼びます:

レイアウト

アプリケーションのデフォルトテンプレートとして layout.php が呼び出されます。それは apps/frontend/templates ディレクトリにあります。このディレクトリにはアプリケーションのグローバルテンプレートすべてが置かれます。

symfony のデフォルトレイアウトを下記コードに置き換えましょう:

[php]
<!-- 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/ ディレクトリに設置します。

NOTE レイアウトにおいて、ファビコン (favicon) を含めることができます。Jobeet のファビコンをダウンロードして web/ ディレクトリに設置します。

レイアウトとアセットつきの job モジュール

TIP 標準では、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 は次のとおりです:

[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() ヘルパーから呼び出されます)。

NOTE 標準の view.yml ファイルには、参照ファイルとして main.css が設定されており、/css/main.css ではありません。実際のところ、symfony によって相対パスに~プレフィックス~の /~css|CSS~/ がつけられるので、両方の定義は同じです。

多くのファイルが定義されたのであれば、定義と同じ順序でこれらをインクルードします:

[yml]
stylesheets:    [main.css, jobs.css, job.css]

media 属性を変更し、.css を省略することもできます。:

[yml]
stylesheets:    [main.css, jobs.css, job.css, print: { media: print }]

この設定では下記のような表示となります:

[php]
<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" />

TIP view.yml 設定ファイルはアプリケーションによって使われるデフォルトの~レイアウト~も定義します。デフォルトでは、名前は layout で、symfony はすべてのページを layout.php ファイルでデコレートします。~has_layout~ エントリを false に切り替えることでデコレーションプロレスを無効にすることもできます。

jobs.css ファイルはホームページに必要とされるときだけ読み込まれ、job.css ファイルは求人ページだけに適用されます。 view.yml ファイルはモジュール単位でカスタマイズできます。アプリケーションの view.yml ファイルは main.css だけをもつように変更します:

[yml]
# apps/frontend/config/view.yml
stylesheets:    [main.css]

job モジュールの View をカスタマイズするには、apps/frontend/modules/job/config ディレクトリのなかで view.yml ファイルを生成します:

[yml]
# apps/frontend/modules/job/config/view.yml
indexSuccess:
  stylesheets: [jobs.css]

showSuccess:
  stylesheets: [job.css]

indexSuccessshowSuccess セクション (indexshow アクションで使われるテンプレート名であり、後で出てきます) の下で、アプリケーションの view.yml のデフォルトセクションで見たようなエントリを使ってカスタマイズできます。すべての固有のエントリはアプリケーションのコンフィギュレーションとしてマージされます。all セクションを使えば、モジュールのすべてのアクションに対してコンフィギュレーションを定義できます。

SIDEBAR symfony のコンフィギュレーションの原則

symfony の多くの~コンフィギュレーション~ファイル間では、異なるレベル単位で同じ設定が定義できます:

  • デフォルトコンフィギュレーションはフレームワーク内にあります
  • プロジェクトに対応するグローバルコンフィギュレーションは config ディレクトリにあります
  • アプリケーションに対応するローカルコンフィギュレーションは apps/APP/config ディレクトリにあります
  • モジュールにだけ適用されるローカルコンフィギュレーションは apps/APP/modules/MODULE/config ディレクトリにあります

実行時には、設定システムはファイルが存在するかキャッシュを見つけると、すべての値をマージしようとします。

経験上、設定ファイル経由で変更可能なのは、PHP コードで完成するのと同じことです。例として job モジュールに view.yml ファイルを作る代わりに、テンプレートからスタイルシートを呼び出すための ~use_stylesheet() ヘルパー~を使うこともできます:

[php]
<?php use_stylesheet('main.css') ?>

スタイルシートを全体に含めるために、レイアウト内で上記のヘルパーを使うことも可能です。

このメソッドもしくはほかのメソッドを選ぶのは本当に好みの問題です。view.yml ファイルはモジュールのすべてのアクション用の内容を定義する方法を提供します。これはテンプレートでは定義できませんが、コンフィギュレーションはとても静的です。 一方で、use_stylesheet() ~ヘルパー~を利用すればより柔軟になり、さらに、同じ場所ですべての内容: スタイルシートの定義と HTML コードを同じ位置で定義されます。Jobeet に関しては、use_stylesheet() ヘルパーを使うので、先ほど作った view.yml を削除して use_stylesheet() を呼び出して job テンプレートを更新できます:

[php]
<!-- apps/frontend/modules/job/templates/indexSuccess.php -->
<?php use_stylesheet('jobs.css') ?>

<!-- apps/frontend/modules/job/templates/showSuccess.php -->
<?php use_stylesheet('job.css') ?>

NOTE 対照的に、JavaScript のコンフィギュレーションも view.ymljavascripts エントリを使ったり、テンプレートのなかから ~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 の末尾にアクション名をつけたもの) が使われます。データベースからすべての求人情報を取得します:

[php]
// apps/frontend/modules/job/actions/actions.class.php
class jobActions extends sfActions
{
  public function executeIndex(sfWebRequest $request)
  {

$this->jobeetjobs = JobeetJobPeer::doSelect(new Criteria()); $this->jobeetjobs = 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 に渡すには、次のように新しいプロパティを作ります:

[php]
public function executeFooBar(sfWebRequest $request)
{
  $this->foo = 'bar';
  $this->bar = array('bar', 'baz');
}

このコードはテンプレートからアクセス可能な $foo$bar 変数を定義しています。

テンプレート

デフォルトでは、アクションに関連する~テンプレート~は symfony によって推測されます (アクションの名前に サフィックスの Success をつけたもの)。

indexSuccess.php テンプレートはすべての求人用の HTML テーブルを生成します。現在のテンプレートコードは次のとおりです:

[php]
<!-- 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>

テンプレートコードのなかでは foreachJob オブジェクト ($jobeet_jobs) のリストを繰り返し取得して、各求人ごとのカラムごとに出力させます。覚えておいて欲しいのは、カラムの値は get から始まりカラム名の~ラクダ記法|コードのフォーマッティング~ になっているアクセサメソッドが呼び出されていることです (たとえば、getCreatedAt() メソッドは create_at カラムからデータを取得します)。

利用できるカラムのみを表示するように整理してみましょう:

[php]
<!-- 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 ファイルを開いて、次のコードに置き換えてください:

[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カ所にあることにご注意ください):

[php]
// 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() アクセサは最初の引数として日付の整形パターンを受け取ります:

[php]
$job->getCreatedAt('m/d/Y');

日付のカラムを PHP の DateTime オブジェクトインスタンスに変換できることに注目してください。created_at カラムをタイムスタンプとして定義したので、getDateTimeObject() メソッドを使うことでカラムの値を DateTime オブジェクトに変換することができ、最初の引数として日付の書式パターンをとる format() メソッドを呼び出します:

[php]
$job->getDateTimeObject('created_at')->format('m/d/Y');

NOTE 求人の説明文で使われている simple_format_text() ヘルパーは HTML を整形します。たとえば、改行を <br /> へ置き換えます。このヘルパーは Text ヘルパーグループに属しており、デフォルトではロードされないので、~use_helper() ヘルパー~を使って手動でロードさせています。

求人ページ

~スロット~

今のところ、すべてのページのタイトルはレイアウトの <title> タグで定義されます:

[php]
<title>Jobeet - Your best job board</title>

しかし求人ページでは会社名や役職のようなもっと役に立つ情報を提供したいと考えます。

symfony ではレイアウトの領域が表示されるテンプレートに依存するとき、スロットを定義する必要があります:

スロット

動的にタイトルを変更するために、スロットをレイアウトに追加します:

[php]
// apps/frontend/templates/layout.php
<title><?php include_slot('title') ?></title>

各スロットは (title) という名前で定義され、~include_slot()~ ヘルパーで表示されます。今から showSuccess.php テンプレートの冒頭部分で、求人ページのコンテンツについて定義した slot() ヘルパーを使うようにします:

[php]
// apps/frontend/modules/job/templates/showSuccess.php
<?php slot(
  'title',
  sprintf('%s is looking for a %s', $job->getCompany(), $job->getPosition()))
?>

タイトルが複雑であるなら、slot() ヘルパーをコードブロックで使うこともできます:

[php]
// 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() ?>

(ホームページのように)ページのなかには、一般的なタイトルが必要となる場合があります。テンプレートのなかで同じタイトルを何度も繰り返す代わりに、レイアウトのなかでデフォルトのタイトルを定義します:

[php]
// apps/frontend/templates/layout.php
<title>
  <?php include_slot('title', 'Jobeet - Your best job board') ?>
</title>

include_slot() メソッドの第2引数はスロットが定義されていない場合のデフォルト値です。デフォルトの値が長いもしくは HTML タグをもつ場合、次のコードのように定義できます:

[php]
// apps/frontend/templates/layout.php
<title>
  <?php if (!include_slot('title')): ?>
    Jobeet - Your best job board
  <?php endif ?>
</title>

include_slot() ヘルパーはスロットが定義されていれば true を返します。よって、テンプレートコンテンツのなかで title スロットが定義されていれば、それを使い、デフォルトタイトルを使うようになります:

TIP ごくわずかですが include_ で始まるヘルパーを見てきました。これらのヘルパーは HTML を出力し、たいていの場合、内容を返すためだけに get_ ヘルパーに対応するものがあります:

[php]
<?php include_slot('title') ?>
<?php echo get_slot('title') ?>

<?php include_stylesheets() ?>
<?php echo get_stylesheets() ?>

求人ページのアクション

求人のページは job モジュールの executeShow() メソッドで定義される show アクションで生成されます:

[php]
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 の主キーを返す理由を説明します。

TIP 生成モデルクラスには、プロジェクトのオブジェクトとやりとりするためにたくさんの便利なコードが入っています。lib/om/ ディレクトリに設置されるコードを見る時間をかけ、これらのクラスに埋め込まれているすべての力を見つけ出してください。

求人データがデータベースに存在しない場合に、ユーザーを~404エラー~のページに転送させることを考えます。この役目はまさに forward404Unless() メソッドが担うことです。最初の引数のブール値をチェックして、true でなければ現在実行中のフローを中止します。forward メソッドは sfError404Exception に投げて実行中のアクションをすぐに停止させるので、その後で return 文は必要ありません。

~例外|例外処理~に関して、ユーザーに表示されるページは prod ~環境~と dev 環境で異なります:

dev 環境の404エラー

prod 環境の404エラー

NOTE Jobeet の Web サイトを運用サーバーにデプロイする前に、デフォルトの404エラーページをカスタマイズする方法を学びます。

-

SIDEBAR ~「forward」|アクションのフォワード~メソッドファミリ

forward404Unless 呼び出しは実際には次のコードと同じです:

[php]
$this->forward404If(!$this->job);

また次のコードとも同じです:

[php]
if (!$this->job)
{
  $this->forward404();
}

forward404() メソッド自身は次のコードのショートカットにすぎません:

[php]
$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 関数やグローバル変数から情報を受け取るために便利なメソッドをたくさん提供します。

NOTE なぜ 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() メソッドを利用する必要があります。

NOTE 特定の ~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() メソッドを使います。

TIP sfActionsfRequestsfResponse クラスもたくさんの有用なメソッドを提供します。API ドキュメントを読んで symfony の内部クラスをもっと学習しましょう。

また明日

今日は、symfony で使われているいくつかのデザインパターンを説明しました。プロジェクトのディレクトリ構造の理解が進むことを願っております。レイアウトとテンプレートファイルを操作することでテンプレートで遊びました。スロットとアクションのおかげでこれらを少し動的なものに変えることもしました。

明日は、今日使った url_for() ヘルパーとルーティングサブフレームワークについて学びます。

ORM