Symfony 6 Certification New exam with updated questions 100% online Show your expertise
Caution: You are browsing the legacy symfony 1.x part of this website.

第3章 - Webデザイナーのためのフォーム

1.1
Symfony version Language

1章と2章でウィジェットとバリデーションのルールを作る方法を見ました。これらを表示するために<?php echo $form ?>ステートメントを使いました。このステートメントのおかげで開発者は最終的にどのように表示されるのか考えなくてもアプリケーションのコードを書けます。フィールド(名前、ウィジェット...)を変更もしくは追加するたびにテンプレートを変更する必要はありません。開発者がモデルとビジネスロジックに重点的に取り組まなければならないとき、このステートメントは試作と初期の開発フェーズに適しています。

いったんオブジェクトモデルが安定してスタイルのガイドラインの準備ができれば、Webデザイナーはさまざまなアプリケーションのフォームに戻り整えることができます。

この章を始める前に、symfonyのテンプレートシステムとビューレイヤーに精通しなければならなりません。そのためには、"The Definitive Guide to symfony"のビューレイヤーの内側の章をご覧ください。

note

symfonyのフォームシステムはMVCモデルに従って開発されます。MVCパターンの助けによって開発チームのすべてのタスクは分離されます: 開発者はフォームを作りそれらのライフサイクルに対処し、Webデザイナーはそれらを整え飾ります。関心の分離(separation of concerns)はプロジェクトチーム内のコミュニケーションの置き換えになることはありません。

始める前に

1章と2章で作り込んだコンタクトフォームを復習します(図3-1)。この章だけを読むWebデザイナーのための技術情報の要約は下記の通りです:

  • フォームは4つのフィールド: nameemailsubject、とmessageで構成される

  • フォームはcontactモジュールによって処理される。

  • indexアクションはテンプレートのフォームを表すform変数に伝える

この章ではフォームを表示するために使用したプロトタイプのテンプレートをカスタマイズする方法をできる限り多く示すことを目的とします(リスト3-1)。

図3-1 - コンタクトフォーム

コンタクトフォーム

リスト3-1 - コンタクトフォームを表示するプロトタイプのテンプレート

// apps/frontend/modules/contact/templates/indexSuccess.php
<form action="<?php echo url_for('contact/index') ?>" method="POST">
  <table>
    <?php echo $form ?>
    <tr>
      <td colspan="2">
        <input type="submit" />
      </td>
    </tr>
  </table>
</form>

sidebar

ファイルのアップロード

フォームでファイルをアップロードするためにフィールドを利用するときは、enctype属性をformタグに追加しなければなりません:

<Form action="<?php echo url_for('contact/index') ?>" method="POST" enctype="multipart/data">

この属性を必要とする場合formオブジェクトのisMultipart()メソッドはフォームがtrueを返します:

<Form action="<?php echo url_for('contact/index') ?>" method="POST" <?php $form->isMultipart() and print 'enctype="multipart/form-data"' ?>>

プロトタイプのテンプレート

今のところ、フォームの表示に必要なHTMLを自動的に生成するためにプロトタイプのテンプレートで<?php echo $form ?>ステートメントを使いました。

フォームはフィールドで構成されます。テンプレートレベルでは、それぞれのフィールドは3つの要素で構成されます:

  • ラベル

  • フォームタグ

  • 内在するエラーメッセージ

リスト3-2で示されるように、無効な投稿の場合<?php echo $form ?>ステートメントは自動的にこれらすべての要素を生成します。

リスト3-2 - 無効な投稿の場合に生成されるテンプレート

<form action="/frontend_dev.php/contact" method="POST">
  <table>
    <tr>
      <th><label for="contact_name">Name</label></th>
      <td><input type="text" name="contact[name]" id="contact_name" /></td>
    </tr>
    <tr>
      <th><label for="contact_email">Email</label></th>
      <td>
        <ul class="error_list">
          <li>This email address is invalid.</li>
        </ul>
        <input type="text" name="contact[email]" value="fabien" id="contact_email" />
      </td>
    </tr>
    <tr>
      <th><label for="contact_subject">Subject</label></th>
      <td>
        <select name="contact[subject]" id="contact_subject">
          <option value="0" selected="selected">Subject A</option>
          <option value="1">Subject B</option>
          <option value="2">Subject C</option>
        </select>
      </td>
    </tr>
    <tr>
      <th><label for="contact_message">Message</label></th>
      <td>
        <ul class="error_list">
          <li>The message "foo" is too short. It must be of 4 characters at least.</li>
        </ul>
        <textarea rows="4" cols="30" name="contact[message]" id="contact_message">foo</textarea>
      </td>
    </tr>
    <tr>
      <td colspan="2">
        <input type="submit" />
      </td>
    </tr>
  </table>
</form>

このコードを分解してみましょう. 図3-2はそれぞれのフィールドに対して生み出された<tr>の列を強調しています。

図3-2 - フィールドによるフォームの分割

フィールドによるフォームの分割

HTMLコードの3つのピースはそれぞれのフィールドに対して生成され(図3-3)、フィールドの3つの要素に対応しています。emailフィールドに対して生成されたHTMLコードは下記の通りです:

  • ラベル

    <label for="contact_email">Email</label>
  • フォームタグ

    <input type="text" name="contact[email]" value="fabien" id="contact_email" />
  • エラーメッセージ

    <ul class="error_list">
      <li>The email address is invalid.</li>
    </ul>

図3-3 - emailフィールドの分解

<code>email</code>フィールドの分解

tip

すべてのフィールドはid生成属性を持ち、これによって開発者がCSSもしくはjavaScriptのふるまいを簡単に追加できます。

プロトタイプのテンプレートのカスタマイズ

コンタクトフォームのようなシンプルなフォームに対しては<?php echo $form ?>ステートメントで十分です。そして、当然ながら、これは<?php echo $form->render() ?>ステートメントの単なるショートカットです。

render()メソッドを利用することでそれぞれのフィールドに対してHTML属性を引数として渡すことができます。リスト3-3はemailフィールドにクラスを追加する方法を表示します。

リスト3-3 - render()メソッドを利用したHTML属性のカスタマイズ

<?php echo $form->render(array('email' => array('class' => 'email'))) ?>
 
// 生成されたHTML
<input type="text" name="contact[email]" value="" id="contact_email" class="email" />

これによってフォームスタイルをカスタマイズできますがページ内のフィールドの編成をカスタマイズするために必要なレベルの柔軟性は提供されません。

表示のカスタマイズ

render()メソッドによって可能なグローバルなカスタマイズを越えて、柔軟性を得るためにそれぞれのフィールドの表示を乗り越える方法を見てみましょう。

フィールド上でrenderRow()メソッドを利用する

これを行う最初の方法はすべてのフィールドを個別に生成することです。実際、リスト3-4で示されるように、<?php echo $form ?>ステートメントはフォーム上でrenderRow()メソッドを4倍呼び出すことと同等です。

リスト3-4 - renderRow()の使い方

<form action="<?php echo url_for('contact/index') ?>" method="POST">
  <table>
    <?php echo $form['name']->renderRow() ?>
    <?php echo $form['email']->renderRow() ?>
    <?php echo $form['subject']->renderRow() ?>
    <?php echo $form['message']->renderRow() ?>
    <tr>
      <td colspan="2">
        <input type="submit" />
      </td>
    </tr>
  </table>
</form>

formオブジェクトをPHP配列として利用してそれぞれのフィールドにアクセスします。emailフィールドは$form['email']を通してアクセスできます。renderRow()メソッドはフィールドをHTMLテーブルの列として表示します。$form['email']->renderRow()の表記はemailフィールド用の列を生成します。subjectemail、とmessageの3つの他のフィールドに対して同じ種類のコードを繰り返すことで、フォームの表示を完成させます。

sidebar

オブジェクトを配列のように振る舞わせるには?

PHP5以降では、オブジェクトはPHPの配列以外のふるまいを行うことができます。sfFormクラスはシンプルで短い構文を利用してそれぞれのフィールドに対してアクセスする権限を与えるArrayAccessのふるまいを実装します。配列のキーはフィールド名で戻り値は関連ウィジェットのオブジェクトです:

<?php echo $form['email'] ?>
 
// sfFormがArrayAccessインターフェイスを実装しなかった場合に使われてきた構文。
<?php echo $form->getField('email') ?>

しかしながら、すべての変数はテンプレート内で読み込みのみでなければならないので、フィールドを修正しようとするとLogicExceptionの例外が投げられます:

<?php $form['email'] = ... ?>
<?php unset($form['email']) ?>

使い始めたこの現在のテンプレートとオリジナルのテンプレートは機能の面では理想的です。しかしながら、表示が同じ場合、カスタマイズはより簡単です。renderRow()メソッドは2つの引数: HTML属性の配列とラベル名を受け取ります。リスト3-5のコードはフォームをカスタマイズするためにこれら2つの引数を使います(図3-4はレンダリングの結果を表示します)。

リスト3-5 - 表示をカスタマイズするためにrenderRow()メソッドの引数を使う

<form action="<?php echo url_for('contact/index') ?>" method="POST">
  <table>
    <?php echo $form['name']->renderRow() ?>
    <?php echo $form['email']->renderRow(array('class' => 'email')) ?>
    <?php echo $form['subject']->renderRow() ?>
    <?php echo $form['message']->renderRow(array(), 'Your message') ?>
    <tr>
      <td colspan="2">
        <input type="submit" />
      </td>
    </tr>
  </table>
</form>

図3-4 - renderRow()メソッドを利用したフォーム表示のカスタマイズ

<code>renderRow()</code>メソッドを利用したフォーム表示のカスタマイズ

emailフィールドを生成するためにrenderRow()に送り出される引数をじっくり見てみましょう:

  • array('class' => 'email')emailクラスを<input>タグに追加する

これはmessageフィールドメッセージで同じように機能します:

  • array()はHTML属性を<textarea>タグに追加したくないことを意味する
  • 'Your message'はデフォルトのラベル名に置き換えられる

すべてのrenderRow()メソッドの引数はオプションで、namesubjectフィールドに対して行ったようなことは必要としません。

renderRow()メソッドがそれぞれのフィールド要素のカスタマイズの手助けになりますが、図3-5で示されるように、レンダリングはこれらの要素を装飾するHTMLコードによって制限されます。

図3-5 - renderRow()render()によって使用されるHTMLの構造

renderRow()とrender()によって使用されるHTMLの構造

sidebar

プロトタイプで使われた構造のフォーマットを変更するには?

デフォルトでは、symfonyはフォームを表示するためにHTMLの配列を使います。このふるまいは特定のフォーマッター(formatter)によって変更できます。これらは組み込みもしくはプロジェクトに適合させるために特別に開発されます。フォーマッターを作るには、5章で説明されているクラスを作る必要があります。

この構造から自由になるために、図3-6で示されるように、それぞれのフィールドは要素を生成するメソッドを持ちます:

  • renderLabel() : ラベル(フィールドに結びつけられた<label>タグ)
  • render() : フィールドタグそのもの(例えば<input>タグ)
  • renderError() : エラーメッセージ(<ul class="error_list">リストとして)

図3-6 - フィールドをカスタマイズするために利用できるメソッド

フィールドをカスタマイズするために利用できるメソッド

これらのメソッドはこの章の終わりの方で説明されます。

フィールド上でrender()メソッドを利用する

2つのカラムを持つフォームを表示することを考えてみましょう。図3-7で示されるように、subjectmessageフィールドがそれら独自の列を表すとき、nameemailフィールドはそれら独自の列を表します。

図3-7 - さまざまな列でフォームを表示する

さまざまな列でフォームを表示する

これを行うにはフィールドのそれぞれの要素を個別に生成できるようにしなければなりません。フィールドにアクセスするために、formオブジェクトを連想配列として使用し、フィールド名をキーとして使用できることをすでに見ました。例えば、emailフィールドは$form['email']でアクセスできます。リスト3-6は2つの列を持つフォームを実装する方法を示しています。

リスト3-6 - 2つのカラムで表示をカスタマイズする

<form action="<?php echo url_for('contact/index') ?>" method="POST">
  <table>
    <tr>
      <th>Name:</th>
      <td><?php echo $form['name']->render() ?></td>
      <th>Email:</th>
      <td><?php echo $form['email']->render() ?></td>
    </tr>
    <tr>
      <th>Subject:</th>
      <td colspan="3"><?php echo $form['subject']->render() ?></td>
    </tr>
    <tr>
      <th>Message:</th>
      <td colspan="3"><?php echo $form['message']->render() ?></td>
    </tr>
    <tr>
      <td colspan="4">
        <input type="submit" />
      </td>
    </tr>
  </table>
</form>

<?php echo $form ?>を利用する際に必須ではないフィールド上でrender()メソッドを明示的に使うこととまったく同じように、リスト3-7のようにテンプレートを書き換えることができます。

リスト3-7 - 2つのカラムのカスタマイズ作業を簡略化する

<form action="<?php echo url_for('contact/index') ?>" method="POST">
  <table>
    <tr>
      <th>Name:</th>
      <td><?php echo $form['name'] ?></td>
      <th>Email:</th>
      <td><?php echo $form['email'] ?></td>
    </tr>
    <tr>
      <th>Subject:</th>
      <td colspan="3"><?php echo $form['subject'] ?></td>
    </tr>
    <tr>
      <th>Message:</th>
      <td colspan="3"><?php echo $form['message'] ?></td>
    </tr>
    <tr>
      <td colspan="4">
        <input type="submit" />
      </td>
    </tr>
  </table>
</form>

フォームのように、それぞれのフィールドはrender()メソッドにHTML属性の配列を渡すことでカスタマイズできます。リスト3-8はemailフィールドのHTML属性を修正する方法を示しています。

リスト3-8 - render()メソッドを利用してHTML属性を修正する

<?php echo $form['email']->render(array('class' => 'email')) ?>
 
// 生成されたHTML
<input type="text" name="contact[email]" class="email" id="contact_email" />

フィールド上でrenderLabel()メソッドを利用する

以前のパラグラフでカスタマイズしている間ラベルを生成しませんでした。リスト3-9はそれぞれのフィールドに対応するラベルを生成するためにrenderLabel()メソッドを使用します。

リスト3-9 - renderLabel()を使用する

<form action="<?php echo url_for('contact/index') ?>" method="POST">
  <table>
    <tr>
      <th><?php echo $form['name']->renderLabel() ?>:</th>
      <td><?php echo $form['name'] ?></td>
      <th><?php echo $form['email']->renderLabel() ?>:</th>
      <td><?php echo $form['email'] ?></td>
    </tr>
    <tr>
      <th><?php echo $form['subject']->renderLabel() ?>:</th>
      <td colspan="3"><?php echo $form['subject'] ?></td>
    </tr>
    <tr>
      <th><?php echo $form['message']->renderLabel() ?>:</th>
      <td colspan="3"><?php echo $form['message'] ?></td>
    </tr>
    <tr>
      <td colspan="4">
        <input type="submit" />
      </td>
    </tr>
  </table>
</form>

ラベルの名前フィールド名から自動的に生成されます。リスト3-10で示されるようにrenderLabel()メソッドに引数を渡すことでカスタマイズできます。

リスト3-10 - ラベル名を修正する

<?php echo $form['message']->renderLabel('Your message') ?>
 
// 生成されたHTML
<label for="contact_message">Your message</label>

ラベルを引数として送るときrenderLabel()メソッドの大事な点は何でしょうか?なぜ単純にHTMLのlabelタグを使わないのでしょうか?これはrenderLabel()メソッドはlabelタグを生成してリンクされたフィールド(id)の識別子にfor属性のセットを自動的に追加します。これはフィールドがアクセス可能であることを保証します; ラベルをクリックすると、自動的にフィールドに焦点が当てられます:

<label for="contact_email">Email</label>
<input type="text" name="contact[email]" id="contact_email" />

さらに、renderLabel()メソッドに2番目の引数を渡すことでHTML属性を追加できます:

<?php echo $form['send_notification']->renderLabel(null, array('class' => 'inline')) ?>
 
// 生成されたHTML
<label for="contact_send_notification" class="inline">Send notification</label>

この例では、最初の引数はnullなのでラベルテキストの自動生成は保存されます。

フィールド上でrenderError()メソッドを利用する

現在のテンプレートはエラーメッセージを処理しません。リスト3-11はrenderError()メソッドを利用してそれらのメッセージを復元しています。

リスト3-11 - renderError()メソッドを利用してエラーメッセージを表示する

<form action="<?php echo url_for('contact/index') ?>" method="POST">
  <table>
    <tr>
      <th><?php echo $form['name']->renderLabel() ?>:</th>
      <td>
        <?php echo $form['name']->renderError() ?>
        <?php echo $form['name'] ?>
      </td>
      <th><?php echo $form['email']->renderLabel() ?>:</th>
      <td>
        <?php echo $form['email']->renderError() ?>
        <?php echo $form['email'] ?>
      </td>
    </tr>
    <tr>
      <th><?php echo $form['subject']->renderLabel() ?>:</th>
      <td colspan="3">
        <?php echo $form['subject']->renderError() ?>
        <?php echo $form['subject'] ?>
      </td>
    </tr>
    <tr>
      <th><?php echo $form['message']->renderLabel() ?>:</th>
      <td colspan="3">
        <?php echo $form['message']->renderError() ?>
        <?php echo $form['message'] ?>
      </td>
    </tr>
    <tr>
      <td colspan="4">
        <input type="submit" />
      </td>
    </tr>
  </table>
</form>

エラーメッセージのきめ細かいカスタマイズ

renderError()メソッドはフィールドに関連するエラーのリストを生成します。フィールドが何かのエラーを持つ場合のみこのメソッドはHTMLコードを生成します。デフォルトでは、リストは並べ替えられていないHTMLリスト(<ul>)として生成されます。

これは一般的なほとんどの問題に適していますが、hasError()getError()メソッドを使うことによってエラーに直接アクセスできます。リスト3-12はemailフィールド用のエラーメッセージをカスタマイズする方法を示しています。

リスト3-12 - エラーメッセージにアクセスする

<?php if ($form['email']->hasError()): ?>
  <ul class="error_list">
    <?php foreach ($form['email']->getError() as $error): ?>
      <li><?php echo $error ?></li>
    <?php endforeach; ?>
  </ul>
<?php endif; ?>

この例では、生成されたコードはrenderError()メソッドによって生成されたコードとまったく同じです。

隠しフィールドを扱う

referrerhiddenフィールドがフォームの必須フィールドとして存在すると仮定します。フォームにアクセスするときにこのフィールドはユーザーページのリファラを保存します。リスト3-13で示されるように、最後の可視フィールドが生成されるときに <?php echo $form ?>ステートメントは隠しフィールド用のHTMLコードを生成し追加します。

リスト3-13 - 隠しフィールドのコードを生成する

<tr>
  <th><label for="contact_message">Message</label></th>
  <td>
    <textarea rows="4" cols="30" name="contact[message]" id="contact_message"></textarea>
    <input type="hidden" name="contact[referrer]" id="contact_referrer" />
  </td>
</tr>

referrer隠しフィールドに対して生成されたコードでお気づきのように、タグ要素だけが出力に追加されました。ラベルを生成しないために役立ちます。このフィールドで起きる可能性のある潜在的なエラーはどうでしょうか?隠しフィールドであったとしても、故意にもしくはコード内にエラーが存在するため、このフィールドが処理の間に汚染される可能性があります。これらのエラーはreferrerフィールドに直接接続されていませんが、グローバルエラーに集約されます。5章でグローバルエラーの概念が他の問題にも拡張されることを見ることになります。図3-8はreferrerフィールド上でエラーが発生したときにエラーメッセージが表示される方法を示し、リスト3-14はこれらのエラーに対して生成されたコードを示しています。

図3-8 - グローバルエラーメッセージを表示する

グローバルエラーメッセージを表示する

リスト3-14 - グローバルエラーメッセージを生成する

<tr>
  <td colspan="2">
    <ul class="error_list">
      <li>Referrer: Required.</li>
    </ul>
  </td>
</tr>

caution

フォームをカスタマイズするとき、隠しフィールドとグローバルエラーメッセージを実装することを忘れないでください。

グローバルエラーを扱う

フォームに対して3種類のエラーが存在します:

  • 特定のフィールドに関連するエラー
  • グローバルエラー
  • hiddenフィールからのエラーもしくは実際にはフォームに表示されないフィールド。これらはグローバルエラーにまとめられる。

フィールドに関連するエラーメッセージの実装方法をすでに調べたので、リスト3-15はグローバルエラーメッセージの実装方法を示します。

リスト3-15 - グローバルエラーメッセージを実装する

<form action="<?php echo url_for('contact/index') ?>" method="POST">
  <table>
    <tr>
      <td colspan="4">
        <?php echo $form->renderGlobalErrors() ?>
      </td>
    </tr>
 
    // ...
  </table>

renderGlobalErrors()メソッドを呼び出すとグローバルエラーのリストを表示します。リスト3-16で示されるように、hasGlobalErrors()getGlobalErrors()メソッドを利用してグローバルエラーにアクセスすることも可能です。

リスト3-16 - hasGlobalErrors()getGlobalErrors()メソッドによるグローバルエラーのカスタマイズ

  [php]
  <?php if ($form->hasGlobalErrors()): ?>
    <tr>
      <td colspan="4">
        <ul class="error_list">
          <?php foreach ($form->getGlobalErrors() as $name => $error): ?>
            <li><?php echo $name.': '.$error ?></li>
          <?php endforeach; ?>
        </ul>
      </td>
    </tr>
  <?php endif; ?>

それぞれのグローバルエラーは名前(name)とメッセージ(error)を持ちます。"本物の"グローバルエラーが存在するときは名前は空ですが、hiddenフィールドに対するエラーもしくは表示されないフィールドが存在するとき、nameはフィールドのラベル名です。

テンプレートが先述のテンプレート(図3-8)と技術的に同等なものであったとしても、こちらはカスタマイズ可能です。

図3-8 - フィールドメソッドを利用してカスタマイズされたフォーム

フィールドメソッドを利用してカスタマイズされたフォーム

国際化

ラベルとエラーメッセージといった、すべてのフォーム要素はsymfonyの国際化システムによって自動的に取り扱われます。Webデザイナーはフォームを国際化したい、renderLabel()メソッドでラベルを明示的に上書きするときでも、特別なことを行う必要がないことを意味します。翻訳機能は自動的に考慮されます。国際化機能に関する詳細な情報に関しては、9章を参照してください。

開発者と交流する

symfonyを利用した代表的なフォームの開発の説明についてこの章を終わらせましょう:

  • 開発チームはフォームのクラスとアクションで実装を始めます。テンプレートは基本的にプロトタイプの<?php echo $form ?>ステートメントでしかありません。

  • その間、デザイナーはフォームに適用するスタイルのガイドラインと表示ルール: グローバル構造、ルールを表示するエラーメッセージ、...などを設計します。

  • ビジネスルールが定まりスタイルのガイドラインが固まったら、Webデザイナーのチームはフォームテンプレートを修正してカスタマイズできます。チームはフォームのライフサイクルに対処するために必要なフィールドとアクションの名前だけ知っていれば十分です。

最初のサイクルが過ぎたら、ビジネスルールのとテンプレートの両方の両方を同時に実行できます。

テンプレートに影響を与えずに、それゆえデザイナーチームに介入せずに、開発チームは次のことができます:

  • フォームのウィジェットを修正する
  • エラーメッセージをカスタマイズする
  • バリデーションルールを編集、追加、もしくは削除する

同じように、デザイナーチームは開発チームに頼らずに自由にエルゴノミックもしくはグラフィックな変更を実行できます。

次の活動はチーム間の調整を必要とします:

  • フィールドをリネームする
  • フィールドを追加もしくは削除する

ビジネスルールとフォームの表示で変更が必要な場合にこのルールは意味をなします、この章の最初で始めたように、フォームシステムがきれいにタスクを分割していても、チーム間のコミュニケーションに優ることはありません。