跳到內容

表單主題的使用方式

編輯此頁面

本文說明如何在您的應用程式中使用 Symfony 提供的任何表單主題,以及如何建立您自己的自訂表單主題。

Symfony 內建表單主題

Symfony 內建了幾個表單主題,當使用一些最受歡迎的 CSS 框架時,它們可以讓您的表單看起來很棒。每個主題都在單一 Twig 範本中定義,並且在 twig.form_themes 選項中啟用

提示

閱讀關於 Bootstrap 4 Symfony 表單主題Bootstrap 5 Symfony 表單主題 的文章,以了解更多相關資訊。

將主題套用到所有表單

Symfony 表單預設使用 form_div_layout.html.twig 主題。如果您想為應用程式的所有表單使用另一個主題,請在 twig.form_themes 選項中設定它

1
2
3
4
# config/packages/twig.yaml
twig:
    form_themes: ['bootstrap_5_horizontal_layout.html.twig']
    # ...

您可以將多個主題傳遞給此選項,因為有時表單主題只重新定義一些元素。這樣,如果某些主題沒有覆寫某些元素,Symfony 會在其他主題中尋找。

twig.form_themes 選項中主題的順序很重要。每個主題都會覆寫所有先前的主題,因此您必須將最重要的主題放在列表的末尾。

將主題套用到單一表單

雖然大多數時候您會全域套用表單主題,但您可能需要僅將主題套用到某些特定表單。您可以使用 form_theme Twig 標籤 來執行此操作

1
2
3
4
5
6
{# this form theme will be applied only to the form of this template #}
{% form_theme form 'foundation_5_layout.html.twig' %}

{{ form_start(form) }}
    {# ... #}
{{ form_end(form) }}

form_theme 標籤的第一個引數 (本例中為 form) 是儲存表單視圖物件的變數名稱。第二個引數是定義表單主題的 Twig 範本的路徑。

將多個主題套用到單一表單

也可以透過套用多個主題來自訂表單。若要執行此操作,請使用 with 關鍵字傳遞所有 Twig 範本的路徑作為陣列 (它們的順序很重要,因為每個主題都會覆寫所有先前的主題)

1
2
3
4
5
6
7
{# apply multiple form themes but only to the form of this template #}
{% form_theme form with [
    'foundation_5_layout.html.twig',
    'form/my_custom_theme.html.twig'
] %}

{# ... #}

將不同主題套用到子表單

您也可以將表單主題套用到表單的特定子表單

1
{% form_theme form.a_child_form 'form/my_custom_theme.html.twig' %}

當您想要為巢狀表單使用與主要表單不同的自訂主題時,這非常有用。指定您的兩個主題

1
2
{% form_theme form 'form/my_custom_theme.html.twig' %}
{% form_theme form.a_child_form 'form/my_other_theme.html.twig' %}

停用單一表單的全域主題

在應用程式中定義的全域表單主題始終套用於所有表單,即使是那些使用 form_theme 標籤來套用其自身主題的表單也是如此。例如,當為可以在不同 Symfony 應用程式上安裝的套件建立管理介面時,您可能想要停用此功能 (因此您無法控制全域啟用了哪些主題)。若要執行此操作,請在表單主題列表後新增 only 關鍵字

1
2
3
{% form_theme form with ['foundation_5_layout.html.twig'] only %}

{# ... #}

警告

當使用 only 關鍵字時,Symfony 的任何內建表單主題 (form_div_layout.html.twig 等) 都將不會套用。為了正確呈現您的表單,您需要自己提供功能完整的表單主題,或者使用 Twig 的 use 關鍵字而不是 extends 擴充其中一個內建表單主題,以重複使用原始主題內容。

1
2
3
4
{# templates/form/common.html.twig #}
{% use "form_div_layout.html.twig" %}

{# ... #}

建立您自己的表單主題

Symfony 使用 Twig 區塊來呈現表單的每個部分 - 欄位標籤、錯誤、<input> 文字欄位、<select> 標籤等。主題是一個 Twig 範本,其中包含您想要在呈現表單時使用的一個或多個區塊。

例如,考慮一個表單欄位,它代表名為 age 的整數屬性。如果您將此新增到範本中

1
{{ form_widget(form.age) }}

產生的 HTML 內容將如下所示 (它會因應用程式中啟用的表單主題而異)

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

Symfony 使用名為 integer_widget 的 Twig 區塊來呈現該欄位。這是因為欄位類型為 integer,並且您正在呈現其 widget (而不是其 labelerrorshelp)。建立表單主題的第一步是知道要覆寫哪個 Twig 區塊,如下節所述。

表單片段命名

表單片段的命名會根據您的需求而有所不同

  • 如果您想自訂相同類型的所有欄位 (例如,所有 <textarea>),請使用 field-type_field-part 模式 (例如,textarea_widget)。
  • 如果您只想自訂一個特定欄位 (例如,用於編輯產品的表單的 description 欄位的 <textarea>),請使用 _field-id_field-part 模式 (例如,_product_description_widget)。

在這兩種情況下,field-part 可以是以下任何有效的表單欄位部分

相同類型所有欄位的片段命名

這些片段名稱遵循 type_part 模式,其中 type 對應於正在呈現的欄位類型 (例如,textareacheckboxdate 等),而 part 對應於正在呈現的內容 (例如,labelwidget 等)

片段名稱的一些範例包括

  • form_row - 由 form_row() 用於呈現大多數欄位;
  • textarea_widget - 由 form_widget() 用於呈現 textarea 欄位類型;
  • form_errors - 由 form_errors() 用於呈現欄位的錯誤;

個別欄位的片段命名

這些片段名稱遵循 _id_part 模式,其中 id 對應於欄位 id 屬性 (例如,product_descriptionuser_age 等),而 part 對應於正在呈現的內容 (例如,labelwidget 等)

id 屬性同時包含表單名稱和欄位名稱 (例如,product_price)。表單名稱可以手動設定,也可以根據您的表單類型名稱自動產生 (例如,ProductType 等於 product)。如果您不確定您的表單名稱是什麼,請查看為您的表單呈現的 HTML 程式碼。您也可以使用 block_name 選項明確定義此值

1
2
3
4
5
6
7
8
9
10
11
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

public function buildForm(FormBuilderInterface $builder, array $options): void
{
    // ...

    $builder->add('name', TextType::class, [
        'block_name' => 'custom_name',
    ]);
}

在此範例中,片段名稱將為 _product_custom_name_widget,而不是預設的 _product_name_widget

個別欄位的自訂片段命名

block_prefix 選項允許表單欄位定義自己的自訂片段名稱。這對於自訂相同欄位的一些實例非常有用,而無需 建立自訂表單類型

1
2
3
4
5
6
7
8
9
use Symfony\Component\Form\Extension\Core\Type\TextType;
use Symfony\Component\Form\FormBuilderInterface;

public function buildForm(FormBuilderInterface $builder, array $options): void
{
    $builder->add('name', TextType::class, [
        'block_prefix' => 'wrapped_text',
    ]);
}

現在您可以使用 wrapped_text_rowwrapped_text_widget 等作為區塊名稱。

集合的片段命名

當使用 表單集合時,您有幾種自訂集合及其每個條目的方式。首先,使用以下區塊來自訂所有表單集合的每個部分

1
2
3
4
5
{% block collection_row %} ... {% endblock %}
{% block collection_label %} ... {% endblock %}
{% block collection_widget %} ... {% endblock %}
{% block collection_help %} ... {% endblock %}
{% block collection_errors %} ... {% endblock %}

您也可以使用以下區塊來自訂所有集合的每個條目

1
2
3
4
5
{% block collection_entry_row %} ... {% endblock %}
{% block collection_entry_label %} ... {% endblock %}
{% block collection_entry_widget %} ... {% endblock %}
{% block collection_entry_help %} ... {% endblock %}
{% block collection_entry_errors %} ... {% endblock %}

最後,您可以自訂特定的表單集合,而不是所有表單集合。例如,考慮以下複雜範例,其中 TaskManagerType 具有 TaskListType 的集合,而 TaskListType 又具有 TaskType 的集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
class TaskManagerType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options = []): void
    {
        // ...
        $builder->add('taskLists', CollectionType::class, [
            'entry_type' => TaskListType::class,
            'block_name' => 'task_lists',
        ]);
    }
}

class TaskListType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options = []): void
    {
        // ...
        $builder->add('tasks', CollectionType::class, [
            'entry_type' => TaskType::class,
        ]);
    }
}

class TaskType extends AbstractType
{
    public function buildForm(FormBuilderInterface $builder, array $options = []): void
    {
        $builder->add('name');
        // ...
    }
}

然後,您會獲得所有以下可自訂的區塊 (其中 * 可以替換為 rowwidgetlabelhelp)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
{% block _task_manager_task_lists_* %}
    {# the collection field of TaskManager #}
{% endblock %}

{% block _task_manager_task_lists_entry_* %}
    {# the inner TaskListType #}
{% endblock %}

{% block _task_manager_task_lists_entry_tasks_* %}
    {# the collection field of TaskListType #}
{% endblock %}

{% block _task_manager_task_lists_entry_tasks_entry_* %}
    {# the inner TaskType #}
{% endblock %}

{% block _task_manager_task_lists_entry_tasks_entry_name_* %}
    {# the field of TaskType #}
{% endblock %}

範本片段繼承

每個欄位類型都有一個類型 (例如,textarea 的父類型是 text,而 text 的父類型是 form),如果基本片段不存在,Symfony 會使用父類型的片段。

例如,當 Symfony 呈現 textarea 類型的錯誤時,它會先尋找 textarea_errors 片段,然後再回退到 text_errorsform_errors 片段。

提示

每個欄位類型的「父」類型在每個欄位類型的 表單類型參考中提供。

在與表單相同的範本中建立表單主題

當在應用程式中對單一表單進行特定自訂時,建議使用此方法,例如變更表單的所有 <textarea> 元素,或自訂將使用 JavaScript 處理的非常特殊的表單欄位。

您只需要將特殊的 {% form_theme form _self %} 標籤新增到呈現表單的同一範本中。這會導致 Twig 在範本內尋找任何覆寫的表單區塊

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
{% extends 'base.html.twig' %}

{% form_theme form _self %}

{# this overrides the widget of any field of type integer, but only in the
   forms rendered inside this template #}
{% block integer_widget %}
    <div class="...">
        {# ... render the HTML element to display this field ... #}
    </div>
{% endblock %}

{# this overrides the entire row of the field whose "id" = "product_stock" (and whose
   "name" = "product[stock]") but only in the forms rendered inside this template #}
{% block _product_stock_row %}
    <div class="..." id="...">
        {# ... render the entire field contents, including its errors ... #}
    </div>
{% endblock %}

{# ... render the form ... #}

此方法的主要缺點是,它僅在您的範本擴充另一個範本時有效 (上一個範例中的 'base.html.twig')。如果您的範本沒有擴充其他範本,您必須將 form_theme 指向個別範本,如下節所述。

另一個缺點是,當在其他範本中呈現其他表單時,無法重複使用自訂的表單區塊。如果那是您需要的,請在個別範本中建立表單主題,如下節所述。

在個別範本中建立表單主題

當建立在整個應用程式中使用,甚至在不同的 Symfony 應用程式中重複使用的表單主題時,建議使用此方法。您只需要在某處建立一個 Twig 範本,並遵循 表單片段命名規則,即可知道要定義哪些 Twig 區塊。

例如,如果您的表單主題很簡單,並且您只想覆寫 <input type="integer"> 元素,請建立此範本

1
2
3
4
5
6
{# templates/form/my_theme.html.twig #}
{% block integer_widget %}

    {# ... add all the HTML, CSS and JavaScript needed to render this field #}

{% endblock %}

現在您需要告訴 Symfony 使用此表單主題來取代 (或除了) 預設主題。如本文前幾節所述,如果您想將主題全域套用到所有表單,請定義 twig.form_themes 選項

1
2
3
4
# config/packages/twig.yaml
twig:
    form_themes: ['form/my_theme.html.twig']
    # ...

如果您只想將其套用到某些特定表單,請使用 form_theme 標籤

1
2
3
4
5
{% form_theme form 'form/my_theme.html.twig' %}

{{ form_start(form) }}
    {# ... #}
{{ form_end(form) }}

重複使用內建表單主題的部分

建立完整的表單主題需要大量工作,因為有太多不同的表單欄位類型。您可以只定義您感興趣的區塊,然後在您的應用程式或範本中設定多個表單主題,而不是定義所有這些 Twig 區塊。這樣做是可行的,因為當呈現自訂主題中未覆寫的區塊時,Symfony 會回退到其他主題。

另一個解決方案是讓您的表單主題範本使用 Twig "use" 標籤 而不是 extends 標籤從其中一個內建主題擴充,這樣您就可以繼承其所有區塊 (如果您不確定,請從預設的 form_div_layout.html.twig 主題擴充)

1
2
3
4
{# templates/form/my_theme.html.twig #}
{% use 'form_div_layout.html.twig' %}

{# ... override only the blocks you are interested in #}

最後,您也可以使用 Twig parent() 函數 來重複使用內建主題的原始內容。當您只想進行細微的變更 (例如,使用某些元素包裝產生的 HTML) 時,這非常有用

1
2
3
4
5
6
7
8
{# templates/form/my_theme.html.twig #}
{% use 'form_div_layout.html.twig' %}

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

當在呈現表單的同一範本中定義表單主題時,此技術也適用。但是,從內建主題匯入區塊會稍微複雜一些

1
2
3
4
5
6
7
8
9
10
11
12
13
{% form_theme form _self %}

{# import a block from the built-in theme and rename it so it doesn't
   conflict with the same block defined in this template #}
{% use 'form_div_layout.html.twig' with integer_widget as base_integer_widget %}

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

{# ... render the form ... #}

自訂表單驗證錯誤

如果您為物件定義了驗證規則,當提交的資料無效時,您會看到一些驗證錯誤訊息。這些訊息會使用 form_errors() 函數顯示,並且可以使用任何表單主題中的 form_errors Twig 區塊進行自訂,如前幾節所述。

需要考慮的重要事項是,某些錯誤與整個表單相關聯,而不是與特定欄位相關聯。為了區分全域錯誤和本機錯誤,請使用 表單中可用的變數 之一,稱為 compound。如果它是 true,則表示目前呈現的是欄位集合 (例如,整個表單),而不僅僅是個別欄位

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{# templates/form/my_theme.html.twig #}
{% block form_errors %}
    {% if errors|length > 0 %}
        {% if compound %}
            {# ... display the global form errors #}
            <ul>
                {% for error in errors %}
                    <li>{{ error.message }}</li>
                {% endfor %}
            </ul>
        {% else %}
            {# ... display the errors for a single field #}
        {% endif %}
    {% endif %}
{% endblock form_errors %}
本作品,包括程式碼範例,依據 Creative Commons BY-SA 3.0 授權條款授權。
目錄
    版本