Caution: You are browsing the legacy symfony 1.x part of this website.
Cover of the book Symfony 5: The Fast Track

Symfony 5: The Fast Track is the best book to learn modern Symfony development, from zero to production. +300 pages showcasing Symfony with Docker, APIs, queues & async tasks, Webpack, SPAs, etc.

4日目: ControllerとView

昨日はsymfonyがどうやってデータベースエンジン間の違いを吸収していたり、オブジェクト指向クラスに変換しているかを見ました。 そしてPropelでデータベーススキーマを記述したりテーブルを作成したり初期データをデータベースに投入したりしました。

今日は昨日作った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のデフォルトレイアウトを下記コードに置き換えましょう:

<!-- 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="/legacy/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="/legacy/images/jobeet-mini.png" />
            powered by <a href="/">
            <img src="/legacy/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/legacy/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は次のとおりです:

# 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~/がつけられるので両方の定義は同等です。

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

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

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

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

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

<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だけを持つように変更します:

# 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]

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

sidebar

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/indexSuccess.php -->
<?php use_stylesheet('jobs.css') ?>
 
<!-- apps/frontend/modules/job/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の末尾にアクション名をつけたもの)メソッドが使われます。 データベースから全ての求人情報を取得します:

// apps/frontend/modules/job/actions/actions.class.php
class jobActions extends sfActions
{
  public function executeIndex(sfWebRequest $request)
  {
    $this->jobeet_job_list = JobeetJobPeer::doSelect(new Criteria());
  }
 
  // ...
}

コードをよく見てみましょう: executeIndex()メソッド(Controller)はすべての求人情報を検索するためにModelのJobeetJobPeerを呼び出します(new Criteria())。 これはjobeet_job_listオブジェクトプロパティに割り当てられたJobeetJobオブジェクトの配列を返します。 このような全てオブジェクトプロパティは自動的にテンプレート(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_job_list 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_job_list)のリストを繰り返し取得して、各求人ごとのカラムごとに出力させます。 覚えておいて欲しいのは、カラムの値はgetから始まりカラム名のキャメルケース(camelCase)になっているアクセサーメソッドが呼び出されていることです(たとえば、getCreatedAt()メソッドはcreate_atカラムからデータを取得します)。

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

<!-- apps/frontend/modules/job/templates/indexSuccess.php -->
<?php use_stylesheet('jobs.css') ?>
 
<div id="jobs">
  <table class="jobs">
    <?php foreach ($jobeet_job_list 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">
    <small>posted on <?php echo $job->getCreatedAt('m/d/Y') ?></small>
  </div>
 
  <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->forward404Unless($this->job);
}

Propelアクセサーの中には引数を受け取るものがあることに注意してください。 created_atカラムをタイムスタンプとして定義したので、getCreatedAt()アクセサーは最初の引数として日付の整形パターンを受け取ります:

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

note

仕事の説明文で使われている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 if (!include_slot('title')): ?>
    Jobeet - Your best job board
  <?php endif; ?>
</title>

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

tip

ごくわずかですが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->forward404Unless($this->job);
  }
 
  // ...
}

indexアクションに関しては、今回の場合、JobeetJobPeerクラスはretrieveByPk()メソッドを利用して求人情報を読み取るために使われます。 このメソッドのパラメーターはjob、主キーの一意的な識別子です。 次のセクションでは$request->getParameter('id')ステートメントがjobの主キーを返す理由を説明します。

tip

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

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

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

dev環境の404エラー

prod環境の404エラー

note

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

sidebar

"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サーバーの間を往復し始めます。ブラウザーはリクエスト(request)を送り、サーバーはレスポンス(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()ヘルパーとルーティングサブフレームワークについて学びます。