フォームのレンダリングのカスタマイズ方法

Symfony は、フォームのレンダリングをカスタマイズする方法をいくつか用意しています。この記事では、テンプレートエンジンに Twig, PHP のどちらを使用しても、最小の努力で全てのフォームのパーツをカスタマイズする方法を学びます。

フォームレンダリングの基本

次のように form_row を使用することで、フォームフィールドのラベル、エラー、 HTML ウィジェットを簡単に表示することができるのを覚えてますでしょうか?

  • Twig
    {{ form_row(form.age) }}
    
  • PHP
    <?php echo $view['form']->row($form['age']) }} ?>
    

また個々のフィールドを3つのパーツにしてそれぞれ表示することもできます。

  • Twig
    <div>
        {{ form_label(form.age) }}
        {{ form_errors(form.age) }}
        {{ form_widget(form.age) }}
    </div>
    
  • PHP
    <div>
        <?php echo $view['form']->label($form['age']) }} ?>
        <?php echo $view['form']->errors($form['age']) }} ?>
        <?php echo $view['form']->widget($form['age']) }} ?>
    </div>
    

両方のケースで、 Symfony Standard Edition に付いてくるマークアップを使用して、フォームラベル、エラー、 HTML ウィジェットを表示します。例えば、上記の双方ともが以下のように表示されることになります。

<div>
    <label for="form_age">Age</label>
    <ul>
        <li>This field is required</li>
    </ul>
    <input type="number" id="form_age" name="form[age]" />
</div>

素早くプロトタイプして、フォームをテストするには、全てのフォームを一行で表示することができます。

  • Twig
    {{ form_widget(form) }}
    
  • PHP
    <?php echo $view['form']->widget($form) }} ?>
    

このレシピの残りでは、フォームのマークアップの全てのパーツを、いろんな異なるレベルでどうやって変更するかについて説明します。一般的なフォームレンダリングに関しての詳細は、 form-rendering-templates を参照してください。

フォームテーマとは何か?

Symfony はフォームのフラグメントを使用します。フォームのフラグメントとは、フィールドのラベルやエラーや input 入力テキストフィールド、 select 選択リストタグなどのフォームの各部を構成するパーツ1つのみの小さなテンプレートです。

フラグメントは Twig では、ブロックとして、 PHP ではテンプレートファイルとして定義されます。

theme はフォームを表示する際に使用するフラグメントのセットに他なりません。つまり、フォームの表示の一部をカスタマイズするには、適切なフォームフラグメントのカスタマイズを含んだ theme をインポートすることになります。

Symfony は、デフォルトのテーマ (Twig の際は form_div_layout.html.twig 、 PHP の際は FrameworkBundle:Form ) が付いてきます。このデフォルトのテーマは、フォームの全てのパーツとして表示されるべき全てのフラグメントを定義します。

次のセクションでは、このフラグメントの一部、もしくは全部をどうやってオーバーライドしてテーマをカスタマイズするかを学びます。

例えば、 integer タイプフィールドのウィジェットが表示されると、 input number フィールドが生成されます。

  • Twig
    {{ form_widget(form.age) }}
    
  • PHP
    <?php echo $view['form']->widget($form['age']) ?>
    

は次のように表示されます。

<input type="number" id="form_age" name="form[age]" required="required" value="33" />

内部的に、 Symfony はフィールドを表示するために integer_widget フラグメントを使用します。それは、フィールドタイプが integer で、 labelerrors ではなく、この widget を表示しているからです。

Twig では、 form_div_layout.html.twig テンプレートの integer_widget ブロックをデフォルトとして使用します。

PHP では、 FrameworkBundle/Resources/views/Form フォルダの integer_widget.html.php ファイルを使用します。

integer_widget フラグメントのデフォルトの実装は以下のようになっています。

  • Twig
    {% block integer_widget %}
        {% set type = type|default('number') %}
        {{ block('field_widget') }}
    {% endblock integer_widget %}
    
  • PHP
    <!-- integer_widget.html.php -->
    
    <?php echo $view['form']->renderBlock('field_widget', array('type' => isset($type) ? $type : "number")) ?>
    

上記を見ればわかるように、このフラグメント自体は、他のフラグメント field_widget を表示しています。

  • Twig
    {% block field_widget %}
        {% set type = type|default('text') %}
        <input type="{{ type }}" {{ block('widget_attributes') }} value="{{ value }}" />
    {% endblock field_widget %}
    
  • PHP
    <!-- FrameworkBundle/Resources/views/Form/field_widget.html.php -->
    
    <input
        type="<?php echo isset($type) ? $view->escape($type) : "text" ?>"
        value="<?php echo $view->escape($value) ?>"
        <?php echo $view['form']->renderBlock('attributes') ?>
    />
    

ポイントは、フラグメントがフォームのそれぞれの部分の HTML 出力を担っていることです。フォームの出力をカスタマイズするには、正しいフラグメントを確認して、オーバーライドするだけです。フォームフラグメントのセットのカスタマイズは、フォーム “theme” となります。フォームを表示する際に、適用したいテーマを選択することができます。

Twig では、テーマは、1つのテンプレートファイルになり、フラグメントは、そのファイルで定義されたブロックになります。

PHP では、テーマは、1つのフォルダになり、フラグメントは、そのフォルダ内の個々のテンプレートファイルになります。

フォームをテーマ化する

フォームのテーマ化のパワーを見るために、全ての入力 number フィールドを div タグでラップする例を見てみましょう。このためのポイントは、 integer_widget フラグメントのカスタマイズです。

Twig でフォームをテーマ化する

Twig でフォームフィールドのブロックをカスタマイズする際に、カスタマイズしたフォームブロックを置く 場所 に関して2つのオプションがあります。

方法 メリット デメリット
フォームと同じテンプレートの中 速く簡単に可能 他のテンプレートで再利用できない
別のテンプレートの中 多くのテンプレートで再利用可能 専用のテンプレートを作成しなければならない

両方の方法で、同じことが可能ですが、シチュエーションによってどちらが適切か異なります。

方法 1: フォームと同じテンプレートの中

integer_widget ブロックをカスタマイズする最も簡単な方法は、実際にフォームを表示するテンプレートを直接カスタマイズすることです。

{% extends '::base.html.twig' %}

{% form_theme form _self %}

{% block integer_widget %}
    <div class="integer_widget">
        {% set type = type|default('number') %}
        {{ block('field_widget') }}
    </div>
{% endblock %}

{% block content %}
    {# render the form #}

    {{ form_row(form.age) }}
{% endblock %}

特別なタグの {% form_theme form _self %} を使えば、 Twig は同テンプレート中のオーバライドされたフォームブロックを探します。 form.age フィールドは integer タイプフィールドであると仮定すると、ウィジェットが表示される際に integer_widget ブロックが使用されます。

この方法のディスアドバンテージは、カスタマイズされたフォームブロックを他のテンプレートから再利用できないことです。つまり、この方法はアプリケーションで特別で単一なフォームのカスタマイズに便利になるのです。アプリケーションの他のフォームを横断してフォームのカスタマイズを再利用したい際には、次のセクションを読んでください。

方法 2: 別のテンプレートの中

全く別のテンプレートの中にカスタマイズした integer_widget フォームブロックを入れることを選択することもできます。コードと最終的な結果は同じになりますが、これで多くのテンプレートを横断してフォームのカスタマイズが再利用できるようになります。

{# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #}

{% block integer_widget %}
    <div class="integer_widget">
        {% set type = type|default('number') %}
        {{ block('field_widget') }}
    </div>
{% endblock %}

カスタマイズしたフォームブロックを作成したので、 Symfony からそれを呼ぶようにしなければなりません。実際にフォームを表示するテンプレートの中で、 form_theme タグを通してこのテンプレートを呼び出します。

{% form_theme form 'AcmeDemoBundle:Form:fields.html.twig' %}

{{ form_widget(form.age) }}

form.age ウィジェットが表示されるときに、 Symfony は、新しいテンプレートで integer_widget ブロックを使用します。そして、 input タグは、カスタマイズしたブロックで指定した div 要素で囲まれます。

PHP でフォームをテーマ化する

テンプレートエンジンとして、 PHP を使用する際に、フラグメントをカスタマイズする唯一の方法は、 Twig の2つ目の方法と同じように、新しくテンプレートファイルを作成することです。

テンプレートファイルは、フラグメントにちなんで名付ける必要があります。例えば、 integer_widget フラグメントをカスタマイズするには、 inter_widget.html.php を作成しなければなりません。

<!-- src/Acme/DemoBundle/Resources/views/Form/integer_widget.html.php -->

<div class="integer_widget">
    <?php echo $view['form']->renderBlock('field_widget', array('type' => isset($type) ? $type : "number")) ?>
</div>

これでカスタマイズされたフォームテンプレートを作成できましたので、 Symfony から使ってみましょう。実際にフォームを表示するテンプレートの中で、 setTheme ヘルパーメソッドを通してテーマを使用するようにします。

<?php $view['form']->setTheme($form, array('AcmeDemoBundle:Form')) ;?>

<?php $view['form']->widget($form['age']) ?>

form.age ウィジェットが表示されるときに、 Symfony はカスタマイズされた integer_widget.html.php テンプレートを使用し、 input タグは div 要素でラップされます。

ベースフォームブロックの参照(Twig のみ)

これまで、特定のフォームブロックをオーバーライドするのにベストな方法は、 `form_dev_layout.html.twig`_ のデフォルトブロックをコピーして、カスタマイズして異なるテンプレートにペーストすることでした。多くのケースでは、カスタマイズするときにベースブロックを参照してこれを避けることができます。

これは簡単にすることができますが、フォームブロックのカスタマイズがフォームと同じテンプレートにあるか、または別のテンプレートにあるかによって多少異なります。

フォームと同じテンプレートの中からブロックを参照する

フォームを表示しているテンプレートの中で use タグを追加してブロックをインポートします。

{% use 'form_div_layout.html.twig' with integer_widget as base_integer_widget %}

これで form_div_layout.html.twig のブロックがインポートされたら、 integer_widget ブロックを base_integer_widget として呼びます。これは、 integer_widget ブロックを再定義することになり、 base_integer_widget を通してデフォルトのマークアップを参照できます。

{% block integer_widget %}
    <div class="integer_widget">
        {{ block('base_integer_widget') }}
    </div>
{% endblock %}

外部のテンプレートからベースブロックを参照する

外部テンプレートにカスタマイズしたフォームを作成していれば、Twig の parent() 関数を使用してベースブロックを参照することができます。

{# src/Acme/DemoBundle/Resources/views/Form/fields.html.twig #}

{% extends 'form_div_layout.html.twig' %}

{% block integer_widget %}
    <div class="integer_widget">
        {{ parent() }}
    </div>
{% endblock %}

Note

テンプレートエンジンとして PHP を使用している際には、ベースブロックを参照することはできません。その際には、ベースブロックから手動でコピーして、新しいテンプレートファイルにペーストする必要があります。

アプリケーション全体のカスタマイズ

アプリケーション全体でグローバルにフォームをカスタマイズしたいときは、外部テンプレートとしてフォームカスタマイズを作成し、アプリケーションのコンフィギュレーション内でインポートすることによって、実現できます。

Twig

次のコンフィギュレーションを使用すれば、 AcmeDemoBundle:Form:fields.html.twig テンプレート内の全てのカスタマイズされたフォームブロックを、フォームが表示されるときにグローバルに使用することができます。

  • YAML
    # app/config/config.yml
    
    twig:
        form:
            resources:
                - 'AcmeDemoBundle:Form:fields.html.twig'
        # ...
    
  • XML
    <!-- app/config/config.xml -->
    
    <twig:config ...>
            <twig:form>
                <resource>AcmeDemoBundle:Form:fields.html.twig</resource>
            </twig:form>
            <!-- ... -->
    </twig:config>
  • PHP
    // app/config/config.php
    
    $container->loadFromExtension('twig', array(
        'form' => array('resources' => array(
            'AcmeDemoBundle:Form:fields.html.twig',
         ))
        // ...
    ));
    

デフォルトでは、 Twig はフォーム表示に div レイアウトを使用します。しかし、人によっては、 table レイアウトでのフォーム表示を好むかもしれません。そのときは、レイアウトに form_table_layout.html.twig リソースを使用してください。

  • YAML
    # app/config/config.yml
    
    twig:
        form:
            resources: ['form_table_layout.html.twig']
        # ...
    
  • XML
    <!-- app/config/config.xml -->
    
    <twig:config ...>
            <twig:form>
                <resource>form_table_layout.html.twig</resource>
            </twig:form>
            <!-- ... -->
    </twig:config>
  • PHP
    // app/config/config.php
    
    $container->loadFromExtension('twig', array(
        'form' => array('resources' => array(
            'form_table_layout.html.twig',
         ))
        // ...
    ));
    

テンプレートを1つだけ変更したい際には、リソースとしてテンプレートを追加するのではなく、次の行をテンプレートに追加してください。

{% form_theme form 'form_table_layout.html.twig' %}

上記のコードの form 変数は、テンプレートに渡すフォームビューの変数であること覚えておいてください。

PHP

次のコンフィギュレーションを使用すれば、フォームが表示されるときに src/Acme/DemoBundle/Resources/views/Form フォルダの内部のカスタマイズされたフォームフラグメントがグローバルに使用されます。

  • YAML
    # app/config/config.yml
    
    framework:
        templating:
            form:
                resources:
                    - 'AcmeDemoBundle:Form'
        # ...
    
  • XML
    <!-- app/config/config.xml -->
    
    <framework:config ...>
        <framework:templating>
            <framework:form>
                <resource>AcmeDemoBundle:Form</resource>
            </framework:form>
        </framework:templating>
        <!-- ... -->
    </framework:config>
  • PHP
    // app/config/config.php
    
    // PHP
    $container->loadFromExtension('framework', array(
        'templating' => array('form' =>
            array('resources' => array(
                'AcmeDemoBundle:Form',
         )))
        // ...
    ));
    

デフォルトでは、 PHP エンジンは、フォーム表示に div レイアウトを使用します。しかし、人によっては、 table レイアウトでのフォーム表示を好むかもしれません。そのときは、レイアウトに FrameworkBundle:FormTable リソースを使用してください。

  • YAML
    # app/config/config.yml
    
    framework:
        templating:
            form:
                resources:
                    - 'FrameworkBundle:FormTable'
    
  • XML
    <!-- app/config/config.xml -->
    
    <framework:config ...>
        <framework:templating>
            <framework:form>
                <resource>FrameworkBundle:FormTable</resource>
            </framework:form>
        </framework:templating>
        <!-- ... -->
    </framework:config>
  • PHP
    // app/config/config.php
    
    $container->loadFromExtension('framework', array(
        'templating' => array('form' =>
            array('resources' => array(
                'FrameworkBundle:FormTable',
         )))
        // ...
    ));
    

テンプレートを1つだけ変更したい際には、リソースとしてテンプレートを追加するのではなく、次の行をテンプレートに追加してください。

<?php $view['form']->setTheme($form, array('FrameworkBundle:FormTable')); ?>

上記のコードの $form 変数は、テンプレートに渡すフォームビューの変数であること覚えておいてください。

個々のフィールドのカスタマイズ

これまで、全てのテキストフィールドタイプのウィジェットの出力の異なるカスタマイズ方法を見ていました。個々のフィールドもカスタマイズすることができます。例えば、 first_namelast_name のように text フィールドが2つあるが、どちらかしかカスタマイズをしたくないときを想定します。これは、フィールドの id 属性とカスタマイズされるフィールドの部分を結合した名前のフラグメントをカスタマイズすることによってできます。

  • Twig
    {% form_theme form _self %}
    
    {% block _product_name_widget %}
        <div class="text_widget">
            {{ block('field_widget') }}
        </div>
    {% endblock %}
    
    {{ form_widget(form.name) }}
    
  • PHP
    <!-- Main template -->
    
    <?php echo $view['form']->setTheme($form, array('AcmeDemoBundle:Form')); ?>
    
    <?php echo $view['form']->widget($form['name']); ?>
    
    <!-- src/Acme/DemoBundle/Resources/views/Form/_product_name_widget.html.php -->
    
    <div class="text_widget">
          echo $view['form']->renderBlock('field_widget') ?>
    </div>
    

ここで、 _product_name_widget フラグメントが idproduct_name であるテンプレートを定義します(そして、 name 属性は product[name] になります)。

Tip

フィールドの product の部分は、フォームの名前になります。これは、フォームタイプ名に基づいて、手動で設定、もしくは自動生成によって付けられます( ProductTypeproduct になるように)。フォームの名前がわからなければ、生成されたフォームのソースを参照してください。

同じメソッドを使用してフィールド列全体のマークアップをオーバーライドすることもできます。

  • Twig
    {% form_theme form _self %}
    
    {% block _product_name_row %}
        <div class="name_row">
            {{ form_label(form) }}
            {{ form_errors(form) }}
            {{ form_widget(form) }}
        </div>
    {% endblock %}
    
  • PHP
    <!-- _product_name_row.html.php -->
    
    <div class="name_row">
        <?php echo $view['form']->label($form) ?>
        <?php echo $view['form']->errors($form) ?>
        <?php echo $view['form']->widget($form) ?>
    </div>
    

他の一般的なカスタマイズに関して

これまでのレシピで、フォームを表示する一部をカスタマイズするいくつかの異なる方法を見てきました。ポイントは、制御したいフォームの部分に対応するフラグメントをカスタマイズすることでした(naming form blocks を参照してください)。

次のセクションでは、いくつかの共通のフォームのカスタマイズについて見ていきます。これらのカスタマイズを適用するには、 フォームをテーマ化する セクションに記述されたメソッドを使用してください。

エラー出力をカスタマイズする

Note

フォームのコンポーネントは、 どうやって バリデーションエラーを表示するかのみを扱い、実際のバリデーションエラーメッセージに関しては決定権はありません。エラーメッセージは、オブジェクトに適用したバリデーション制約によって、決められます。詳細は、 validation を参照してください。

フォームがエラーを検知した際に、多くの異なる方法でエラーの表示をカスタマイズできます。フィールドのエラーメッセージは、 form_errors ヘルパーを使用することで、表示されます。

  • Twig
    {{ form_errors(form.age) }}
    
  • PHP
    <?php echo $view['form']->errors($form['age']); ?>
    

デフォルトでは、エラーは、順序の関係の無いリストで表示されます。

<ul>
    <li>This field is required</li>
</ul>

全て のフィールドでエラーの表示をオーバーライドするには、単に field_errors フラグメントをコピーアンドペーストして、カスタマイズします。

  • Twig
    {% block field_errors %}
    {% spaceless %}
        {% if errors|length > 0 %}
        <ul class="error_list">
            {% for error in errors %}
                <li>{{ error.messageTemplate|trans(error.messageParameters, 'validators') }}</li>
            {% endfor %}
        </ul>
        {% endif %}
    {% endspaceless %}
    {% endblock field_errors %}
    
  • PHP
    <!-- fields_errors.html.php -->
    
    <?php if ($errors): ?>
        <ul class="error_list">
            <?php foreach ($errors as $error): ?>
                <li><?php echo $view['translator']->trans(
                    $error->getMessageTemplate(),
                    $error->getMessageParameters(),
                    'validators'
                ) ?></li>
            <?php endforeach; ?>
        </ul>
    <?php endif ?>
    

Tip

このカスタマイズの適用方法の詳細は、 フォームをテーマ化する を参照してください。

特定のフィールドタイプのみのエラー出力をカスタマイズすることもできます。例えば、フォームのよりグローバルな特定のエラーは、デフォルトでは、フォームの一番上に表示されますが、別々に表示させることができます。

  • Twig
    {{ form_errors(form) }}
    
  • PHP
    <?php echo $view['form']->render($form); ?>
    

これらのエラーのマークアップ のみ をカスタマイズするには、上記のように同じ方法に従ってください。しかし、 Twig の際は form_errors ブロックを呼んで、 PHP の際は form_errors.html.php ファイルを呼ぶことになります。これで、 form タイプのエラーが表示されれば、カスタマイズされたフラグメントがデフォルトの field_errors の代わりに使用されます。

“Form Row” をカスタマイズする

可能であれば、フォームフィールドの表示の最も簡単な方法は、 form_row 関数を使用することです。 form_row 関数は、フィールドのラベル、エラー、 HTML ウィジェットを表示します。 全て のフォームフィールドの並びの表示のマークアップをカスタマイズするために、 field_row フラグメントをオーバーライドします。例えばそれぞれの並びを div 要素で囲みたいとします。

  • Twig
    {% block field_row %}
        <div class="form_row">
            {{ form_label(form) }}
            {{ form_errors(form) }}
            {{ form_widget(form) }}
        </div>
    {% endblock field_row %}
    
  • PHP
    <!-- field_row.html.php -->
    
    <div class="form_row">
        <?php echo $view['form']->label($form) ?>
        <?php echo $view['form']->errors($form) ?>
        <?php echo $view['form']->widget($form) ?>
    </div>
    

Tip

このカスタマイズの適用方法の詳細は、 フォームをテーマ化する を参照してください。

“Required” のアスタリスクをフィールドラベルに追加する

全ての入力必須なフィールドにアスタリスク(*)の印を付けるには、 field_label フラグメントをカスタマイズします。

Twig を使用した際に、フォームと同じテンプレート内でフォームのカスタマイズをするには、 use タグを変更して、次のように加えてください。

{% use 'form_div_layout.html.twig' with field_label as base_field_label %}

{% block field_label %}
    {{ block('base_field_label') }}

    {% if required %}
        <span class="required" title="This field is required">*</span>
    {% endif %}
{% endblock %}

Twig を使用した際に、別のテンプレート内でフォームのカスタマイズをする際には、次のようにしてください。

{% extends 'form_div_layout.html.twig' %}

{% block field_label %}
    {{ parent() }}

    {% if required %}
        <span class="required" title="This field is required">*</span>
    {% endif %}
{% endblock %}

テンプレートエンジンに PHP を使用している際は、オリジナルのテンプレートから内容をコピーしてこなければなりません。

<!-- field_label.html.php -->

<!-- original content -->
<label for="<?php echo $view->escape($id) ?>" <?php foreach($attr as $k => $v) { printf('%s="%s" ', $view->escape($k), $view->escape($v)); } ?>><?php echo $view->escape($view['translator']->trans($label)) ?></label>

<!-- customization -->
<?php if ($required) : ?>
    <span class="required" title="This field is required">*</span>
<?php endif ?>

Tip

このカスタマイズの適用方法の詳細は、 フォームをテーマ化する を参照してください。

“help” メッセージを追加する

フォームウィジェットのオプションの “help” メッセージもカスタマイズすることができます。

Twig を使用した際に、フォームと同じテンプレート内でフォームのカスタマイズをするには、 use タグを変更して、次のように加えてください。

{% use 'form_div_layout.html.twig' with field_widget as base_field_widget %}

{% block field_widget %}
    {{ block('base_field_widget') }}

    {% if help is defined %}
        <span class="help">{{ help }}</span>
    {% endif %}
{% endblock %}

Twig を使用した際に、別のテンプレート内でフォームのカスタマイズをする際には、次のようにしてください。

{% extends 'form_div_layout.html.twig' %}

{% block field_widget %}
    {{ parent() }}

    {% if help is defined %}
        <span class="help">{{ help }}</span>
    {% endif %}
{% endblock %}

テンプレートエンジンに PHP を使用した際は、オリジナルのテンプレートから内容をコピーしなければなりません。

<!-- field_widget.html.php -->

<!-- Original content -->
<input
    type="<?php echo isset($type) ? $view->escape($type) : "text" ?>"
    value="<?php echo $view->escape($value) ?>"
    <?php echo $view['form']->renderBlock('attributes') ?>
/>

<!-- Customization -->
<?php if (isset($help)) : ?>
    <span class="help"><?php echo $view->escape($help) ?></span>
<?php endif ?>

フィールドの下にヘルプメッセージを表示するには、 help 変数を渡してください。

  • Twig
    {{ form_widget(form.title, { 'help': 'foobar' }) }}
    
  • PHP
    <?php echo $view['form']->widget($form['title'], array('help' => 'foobar')) ?>
    

Tip

このカスタマイズの適用方法の詳細は、 フォームをテーマ化する を参照してください。