跳到内容

字段

编辑此页

字段允许在每个 CRUD 页面上显示 Doctrine 实体的内容。EasyAdmin 提供了内置字段来显示所有常见数据类型,但你也可以创建你自己的字段

配置要显示的字段

如果你的 CRUD 控制器继承自 EasyAdmin 提供的 AbstractCrudController,字段将自动配置。在 index 页面,你将看到一些字段,在其余页面中,你将看到尽可能多的字段,以显示你的 Doctrine 实体的所有属性。

在你的 CRUD 控制器中实现 configureFields() 方法来自定义要显示的字段列表

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
namespace App\Controller\Admin;

use App\Entity\Product;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;

class ProductCrudController extends AbstractCrudController
{
    public static function getEntityFqcn(): string
    {
        return Product::class;
    }

    public function configureFields(string $pageName): iterable
    {
        // ...
    }

    // ...
}

有几种方法可以定义要显示的字段列表。

选项 1. 返回带有要显示属性名称的字符串。EasyAdmin 会自动为它们创建字段并应用默认配置选项

1
2
3
4
5
6
7
8
9
10
public function configureFields(string $pageName): iterable
{
    return [
        'title',
        'description',
        'price',
        'stock',
        'publishedAt',
    ];
}

选项 2. 返回为 Doctrine 实体属性创建的 Field 对象。EasyAdmin 将这些通用的 Field 对象转换为用于显示每种属性类型的特定对象

1
2
3
4
5
6
7
8
9
10
11
12
use EasyCorp\Bundle\EasyAdminBundle\Field\Field;

public function configureFields(string $pageName): iterable
{
    return [
        Field::new('title'),
        Field::new('description'),
        Field::new('price'),
        Field::new('stock'),
        Field::new('publishedAt'),
    ];
}

选项 3. 返回适当的字段对象来显示每个属性

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
use EasyCorp\Bundle\EasyAdminBundle\Field\MoneyField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;

public function configureFields(string $pageName): iterable
{
    return [
        TextField::new('title'),
        TextEditorField::new('description'),
        MoneyField::new('price')->setCurrency('EUR'),
        IntegerField::new('stock'),
        DateTimeField::new('publishedAt'),
    ];
}

字段构造函数的唯一强制参数是由此字段管理的 Doctrine 实体属性的名称。EasyAdmin 使用 PropertyAccess 组件来获取属性的值,因此实体可以将它们的访问定义为公共属性(例如 public $firstName)或公共方法(例如 public function getFirstName(), public function firstName())。

注意

EasyAdmin 使用 Symfony 表单来创建和编辑 Doctrine 实体。这就是为什么所有实体属性都必须是可为空的:它们的 setter 需要接受 null 值,并且它们的 getter 必须允许返回 null。在数据库中,关联的字段不必是可为空的。

未映射字段

字段通常引用相关 Doctrine 实体的属性。但是,它们也可以引用实体的未与任何属性关联的方法。例如,如果你的 Customer 实体定义了 firstNamelastName 属性,你可能想要显示一个包含合并后的两个值的“全名”字段。

为此,将以下方法添加到实体

1
2
3
4
5
6
7
8
9
10
11
12
use Doctrine\ORM\Mapping as ORM;

#[ORM\Entity]
class Customer
{
    // ...

    public function getFullName()
    {
        return $this->getFirstName().' '.$this->getLastName();
    }
}

现在,添加一个引用 getFullName() 方法的 fullName 字段。字段名称和方法之间的转换必须符合 PropertyAccess 组件 的规则(例如 foo_bar -> getFooBar()fooBar()

1
2
3
4
5
6
7
public function configureFields(string $pageName): iterable
{
    return [
        TextField::new('fullName'),
        // ...
    ];
}

请注意,未映射字段是不可排序的,因为它们不存在作为数据库表列,因此无法包含在 Doctrine 查询中。在某些情况下,你可以通过使用 SQL 计算未映射字段的内容来自行克服此限制。为此,请覆盖你的 CRUD 控制器 中使用的 createIndexQueryBuilder() 方法

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
namespace App\Controller\Admin;

use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;

class UserCrudController extends AbstractCrudController
{
    // ...

    public function configureFields(string $pageName): iterable
    {
        return [
            TextField::new('fullName'),
            // ...
        ];
    }

    public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder
    {
        $queryBuilder = parent::createIndexQueryBuilder($searchDto, $entityDto, $fields, $filters);

        // if user defined sort is not set
        if (0 === count($searchDto->getSort())) {
            $queryBuilder
                ->addSelect('CONCAT(entity.first_name, \' \', entity.last_name) AS HIDDEN full_name')
                ->addOrderBy('full_name', 'DESC');
        }

        return $queryBuilder;
    }
}

每页显示不同的字段

有几种方法可以根据当前页面有条件地显示字段

1
2
3
4
5
6
7
8
9
10
11
public function configureFields(string $pageName): iterable
{
    return [
        IdField::new('id')->hideOnForm(),
        TextField::new('firstName'),
        TextField::new('lastName'),
        TextField::new('phone'),
        EmailField::new('email')->hideOnIndex(),
        DateTimeField::new('createdAt')->onlyOnDetail(),
    ];
}

以下是所有可用的方法

  • hideOnIndex()
  • hideOnDetail()
  • hideOnForm() (在 editnew 页面中都隐藏字段)
  • hideWhenCreating()
  • hideWhenUpdating()
  • onlyOnIndex()
  • onlyOnDetail()
  • onlyOnForms() (在除 editnew 之外的所有页面中隐藏字段)
  • onlyWhenCreating()
  • onlyWhenUpdating()

如果每页要显示的字段完全不同,请使用给定的 $pageName 参数来区分它们

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;

public function configureFields(string $pageName): iterable
{
    $id = IdField::new('id');
    $firstName = TextField::new('firstName');
    $lastName = TextField::new('lastName');
    $phone = TextField::new('phone');
    $email = EmailField::new('email');
    $createdAt = DateTimeField::new('createdAt');

    if (Crud::PAGE_INDEX === $pageName) {
        return [$id, $firstName, $lastName, $phone];
    } elseif(Crud::PAGE_DETAIL === $pageName) {
        return ['...'];
    } else {
        return ['...'];
    }
}

如果你需要更大的控制,请考虑使用以下使用 PHP 生成器定义字段的方式

1
2
3
4
5
6
7
8
9
10
11
12
13
public function configureFields(string $pageName): iterable
{
    yield IdField::new('id')->hideOnForm();

    if ('... some expression ...') {
        yield TextField::new('firstName');
        yield TextField::new('lastName');
    }

    yield TextField::new('phone');
    yield EmailField::new('email')->hideOnIndex();
    yield DateTimeField::new('createdAt')->onlyOnDetail();
}

字段布局

默认情况下,EasyAdmin 表单每行显示一个字段。在每一行内,字段会根据其类型显示不同的宽度(例如,整数字段较窄,而代码编辑器字段非常宽)。

在本节中,你将学习如何自定义每个字段的宽度,以及如何借助标签页、列、字段集和行等元素自定义整个表单布局。

表单标签页

此元素旨在使非常长/复杂的表单更易于使用。它允许将字段分组到单独的标签页中,这些标签页一次可见一个。它看起来像这样

EasyAdmin form that uses tabs to group fields

使用特殊 FormField 对象的 addTab() 方法将标签页添加到你的表单中

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use EasyCorp\Bundle\EasyAdminBundle\Field\FormField;

public function configureFields(string $pageName): iterable
{
    return [
        // Creates a tab: all fields following it will belong to that tab
        // (until the end of the form or until you create another tab)
        FormField::addTab('First Tab'),
        TextField::new('firstName'),
        TextField::new('lastName'),

        // Creates a second tab and customizes some of its properties, such
        // as its icon, CSS class and help message
        FormField::addTab('Contact Information Tab')
            ->setIcon('phone')->addCssClass('optional')
            ->setHelp('Phone number is preferred'),

        TextField::new('phone'),
        // ...
    ];
}

addTab() 方法的参数是

  • $label: (类型: TranslatableInterface|string|false|null) 此标签页在可点击的标签页列表中显示的文本;如果你将其设置为 falsenull 或空字符串,则不会显示任何文本(请确保为标签页显示图标,否则用户将无法点击它);你还可以传递 stringTranslatableInterface 变量。在这两种情况下,如果它们包含 HTML 标签,它们将被渲染而不是转义;
  • $icon: (类型: ?string) FontAwesome 图标的完整 CSS 类(例如 far fa-folder-open);如果你不显示标签页的文本标签,请确保显示图标,否则用户将无法点击该标签页。

注意

默认情况下,EasyAdmin 假定图标名称对应于 FontAwesome CSS 类。必要的 CSS 样式和 Web 字体默认也包含在内,因此你无需采取任何其他步骤即可使用 FontAwesome 图标。或者,你可以使用你自己的图标集而不是 FontAwesome。

在标签页内部,你不仅可以包含表单字段,还可以包含以下部分中解释的所有其他表单布局字段:列、字段集和行。以下是使用所有这些元素的表单的外观

EasyAdmin form that uses tabs, columns, fieldsets and rows

默认情况下,标签页使用特殊的 Symfony 表单类型渲染。此类型的名称是 ea_form_tab + 一个随机 ULID 值。这使得无法使用表单主题覆盖其模板。要自定义它,请使用 addTab() 方法的 propertySuffix 可选参数

1
FormField::addTab('Contact Information Tab', propertySuffix: 'contact');

按照此示例,你可以定义以下块来覆盖此标签页的设计

{% endblock _MyEntity_ea_form_tab_contact_row %}

{% block _MyEntity_ea_form_tab_close_contact_row %}
{# ... #} {{ block('ea_form_tab_close_row') }} {# ... #}

{% endblock _MyEntity_ea_form_tab_close_contact_row %}

4.20

propertySuffix 参数是在 EasyAdmin 4.20.0 中引入的。

表单列

4.8.0

表单列是在 EasyAdmin 4.8.0 中引入的。

在使用此选项之前,你必须熟悉 Bootstrap 网格系统,它将每一行分为 12 个等宽列,以及 Bootstrap 断点,它们是 xs (设备宽度 < 576px), sm (>= 576px), md (>= 768px), lg (>= 992px), xl (>= 1,200px) 和 xxl (>= 1,400px)。

表单列允许将复杂的表单分解为两列或更多列字段。除了增加信息密度外,列还可以更好地根据其功能分隔字段。以下是三列表单的外观

EasyAdmin form that uses three columns to group fields

以下是一个简单的示例,它将表单分为两列(第一列跨越 12 个可用的 Bootstrap 列中的 8 个,第二列跨越其他 4 个 Bootstrap 列)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use EasyCorp\Bundle\EasyAdminBundle\Field\FormField;

public function configureFields(string $pageName): iterable
{
    return [
        FormField::addColumn(8),
        TextField::new('firstName'),
        TextField::new('lastName'),

        FormField::addColumn(4),
        TextField::new('phone'),
        TextField::new('email')->hideOnIndex(),
    ];
}

addColumn() 方法的参数是

  • $cols: (类型: int|string) 列的宽度,定义为与 Bootstrap 网格系统 兼容的任何值(例如 'col-6', 'col-md-6 col-xl-4' 等)。整数值按如下方式转换:N -> 'col-N'(例如 8 转换为 col-8);
  • $label: (类型: TranslatableInterface|string|false|null) 显示在列顶部的可选标题。如果你传递 falsenull 或空字符串,则不显示标题。你还可以传递 stringTranslatableInterface 变量。在这两种情况下,如果它们包含 HTML 标签,它们将被渲染而不是转义;
  • $icon: (类型: ?string) FontAwesome 图标的完整 CSS 类(例如 far fa-folder-open),它显示在列标签旁边;
  • $help: (类型: ?string) 显示在列标签下方的可选内容;它主要用于描述列内容或提供进一步的说明或帮助内容。你可以包含 HTML 标签,它们将被渲染而不是转义。

注意

默认情况下,EasyAdmin 假定图标名称对应于 FontAwesome CSS 类。必要的 CSS 样式和 Web 字体默认也包含在内,因此你无需采取任何其他步骤即可使用 FontAwesome 图标。或者,你可以使用你自己的图标集而不是 FontAwesome。

借助 Bootstrap 响应式类,你可以拥有不同尺寸的列,甚至完全没有列,具体取决于浏览器窗口大小。在以下示例中,低于 lg 的断点不显示列。此外,两列的总和不等于 12;这是允许的,以创建比可用总空间更短的列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
use EasyCorp\Bundle\EasyAdminBundle\Field\FormField;

public function configureFields(string $pageName): iterable
{
    return [
        FormField::addColumn('col-lg-8 col-xl-6'),
        TextField::new('firstName'),
        TextField::new('lastName'),

        FormField::addColumn('col-lg-3 col-xl-2'),
        TextField::new('phone'),
        TextField::new('email')->hideOnIndex(),
    ];
}

你还可以在标签页内使用列,以进一步组织非常复杂布局的内容

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
use EasyCorp\Bundle\EasyAdminBundle\Field\FormField;

public function configureFields(string $pageName): iterable
{
    return [
        FormField::addTab('User Data'),

        FormField::addColumn('col-lg-8 col-xl-6'),
        TextField::new('firstName'),
        TextField::new('lastName'),

        FormField::addColumn('col-lg-3 col-xl-2'),
        TextField::new('phone'),
        TextField::new('email')->hideOnIndex(),

        FormField::addTab('Financial Information'),

        // ...
    ];
}

注意

默认情况下,列内的所有字段都与其包含列一样宽。使用表单行(如下所述)来自定义字段宽度和/或在同一行上显示多个字段。

默认情况下,列使用特殊的 Symfony 表单类型渲染。此类型的名称是 ea_form_column + 一个随机 ULID 值。这使得无法使用表单主题覆盖其模板。要自定义它,请使用 addColumn() 方法的 propertySuffix 可选参数

1
FormField::addColumn('col-lg-8 col-xl-6', propertySuffix: 'main');

按照此示例,你可以定义以下块来覆盖此列的设计

{% endblock _MyEntity_ea_form_column_main_row %}

{% block _MyEntity_ea_form_column_close_main_row %}
{# ... #} {{ block('ea_form_column_close_row') }} {# ... #}

{% endblock _MyEntity_ea_form_column_close_main_row %}

4.20

propertySuffix 参数是在 EasyAdmin 4.20.0 中引入的。

表单字段集

4.8.0

表单字段集是在 EasyAdmin 4.8.0 中引入的。在以前的版本中,此功能称为“表单面板”。

在显示大量字段的页面中,你可以使用字段集将它们分组。以下是它们的外观

EasyAdmin form that uses fieldsets to group fields into different sections

使用特殊 FormField 对象的 addFieldset() 方法创建字段集

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
use EasyCorp\Bundle\EasyAdminBundle\Field\FormField;

public function configureFields(string $pageName): iterable
{
    return [
        // fieldsets usually display only a title
        FormField::addFieldset('User Details'),
        TextField::new('firstName'),
        TextField::new('lastName'),

        // fieldsets without titles only display a separation between fields
        FormField::addFieldset(),
        DateTimeField::new('createdAt')->onlyOnDetail(),

        // fieldsets can also define their icon, CSS class and help message
        FormField::addFieldset('Contact information')
            ->setIcon('phone')->addCssClass('optional')
            ->setHelp('Phone number is preferred'),
        TextField::new('phone'),
        TextField::new('email')->hideOnIndex(),

        // fieldsets can be collapsible too (useful if your forms are long)
        // this makes the fieldset collapsible but renders it expanded by default
        FormField::addFieldset('Contact information')->collapsible(),
        // this makes the fieldset collapsible and renders it collapsed by default
        FormField::addFieldset('Contact information')->renderCollapsed(),
    ];
}

addFieldset() 方法的参数是

  • $label: (类型: TranslatableInterface|string|false|null) 显示在字段集顶部的可选标题。如果你传递 falsenull 或空字符串,则不显示标题。你还可以传递 stringTranslatableInterface 变量。在这两种情况下,如果它们包含 HTML 标签,它们将被渲染而不是转义;
  • $icon: (类型: ?string) FontAwesome 图标的完整 CSS 类 (例如 far fa-folder-open),它显示在 fieldset 标签旁边。

注意

默认情况下,EasyAdmin 假定图标名称对应于 FontAwesome CSS 类。必要的 CSS 样式和 Web 字体默认也包含在内,因此你无需采取任何其他步骤即可使用 FontAwesome 图标。或者,你可以使用你自己的图标集而不是 FontAwesome。

当使用表单列时,其中的 fieldset 会显示略有不同的设计,以更好地对不同的字段进行分组。因此,建议在您使用列时始终使用 fieldset。 它的外观如下所示

EasyAdmin form that uses three columns and several fieldsets to group fields

默认情况下,fieldset 使用特殊的 Symfony 表单类型进行渲染。 此类型的名称是 ea_form_fieldset + 随机 ULID 值。 这使得无法使用表单主题覆盖其模板。 要自定义它,请使用 addFieldset() 方法的 propertySuffix 可选参数

1
FormField::addFieldset('Contact information', propertySuffix: 'contact');

按照此示例,您可以定义以下块来覆盖此 fieldset 的设计

{% endblock _MyEntity_ea_form_fieldset_contact_row %}

{% block _MyEntity_ea_form_fieldset_close_contact_row %}
{# ... #} {{ block('ea_form_fieldset_close_row') }} {# ... #}

{% endblock _MyEntity_ea_form_fieldset_close_contact_row %}

4.20

propertySuffix 参数是在 EasyAdmin 4.20.0 中引入的。

表单行

在使用此选项之前,你必须熟悉 Bootstrap 网格系统,它将每一行分为 12 个等宽列,以及 Bootstrap 断点,它们是 xs (设备宽度 < 576px), sm (>= 576px), md (>= 768px), lg (>= 992px), xl (>= 1,200px) 和 xxl (>= 1,400px)。

表单行允许在同一行上显示两个或多个字段。 它的外观如下所示

EasyAdmin form that uses rows to display several fields on the same row

假设您想在同一行上显示两个名为 startsAtendsAt 的字段,每个字段跨越该行的 6 列。 这是您配置该布局的方式

1
2
3
4
5
6
7
8
9
10
11
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;

public function configureFields(string $pageName): iterable
{
    return [
        // ...,

        DateTimeField::new('startsAt')->setColumns(6),
        DateTimeField::new('endsAt')->setColumns(6),
    ];
}

此示例在同一行上渲染两个字段,但在 xssm 断点中除外,在这些断点中,每个字段都占据整行(因为设备宽度太小)。

如果您需要更好地控制取决于设备宽度的设计,则可以传递一个字符串,其中包含响应式 CSS 类,这些类定义了字段在不同断点处的宽度

1
2
3
4
5
6
7
8
9
10
11
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;

public function configureFields(string $pageName): iterable
{
    return [
        // ...,

        DateTimeField::new('startsAt')->setColumns('col-sm-6 col-lg-5 col-xxl-3'),
        DateTimeField::new('endsAt')->setColumns('col-sm-6 col-lg-5 col-xxl-3'),
    ];
}

此示例添加了 col-sm-6 以覆盖默认的 EasyAdmin 行为,并在 sm 断点中也在同一行上显示这两个字段。 此外,它减少了较大断点(lgxxl)中的列数,以改善这些字段的渲染效果。

提示

您还可以使用与重新排序和偏移列相关的 CSS 类

1
yield DateTimeField::new('endsAt')->setColumns('col-sm-6 col-xxl-3 offset-lg-1 order-3');

由于 Bootstrap 网格的工作方式,当您手动配置字段列时,每行将包含尽可能多的字段。 如果一个字段占用 4 列,而下一个字段占用 3 列,则该行仍然有 12 - 4 - 3 = 5 列来渲染其他字段。 如果下一个字段占用超过 5 列,则它将在下一行上渲染。

有时您需要更好地控制此自动布局。 例如,您可能希望在同一行上显示两个或多个字段,并确保即使有足够的空间,该行上也不会显示其他字段。 为此,请使用特殊 FormField 字段的 addRow() 方法来强制创建新行(下一个字段将强制在新行上渲染)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\FormField;

public function configureFields(string $pageName): iterable
{
    return [
        // ...,

        DateTimeField::new('startsAt')->setColumns('col-sm-6 col-lg-5 col-xxl-3'),
        DateTimeField::new('endsAt')->setColumns('col-sm-6 col-lg-5 col-xxl-3'),
        FormField::addRow(),

        // you can pass the name of the breakpoint to add a row only on certain widths
        // FormField::addRow('xl'),

        // this field will always render on its own row, even if there's
        // enough space for it in the previous row in `lg`, `xl` and `xxl` breakpoints
        BooleanField::new('published')->setColumns(2),
    ];
}

默认情况下,行使用特殊的 Symfony 表单类型进行渲染。 此类型的名称是 ea_form_row + 随机 ULID 值。 这使得无法使用表单主题覆盖其模板。 要自定义它,请使用 addRow() 方法的 propertySuffix 可选参数

1
FormField::addRow('xl', propertySuffix: 'main');

按照此示例,您可以定义以下块来覆盖此行的设计

{% endblock _MyEntity_ea_form_row_main_row %}

{% block _MyEntity_ea_form_row_close_main_row %}
{# ... #} {{ block('ea_form_row_close_row') }} {# ... #}

{% endblock _MyEntity_ea_form_row_close_main_row %}

4.20

propertySuffix 参数是在 EasyAdmin 4.20.0 中引入的。

字段类型

以下是 EasyAdmin 提供的所有内置字段

Doctrine 类型和 EasyAdmin 字段之间的映射

下表显示了根据实体属性的 Doctrine DBAL 类型 推荐使用的 EasyAdmin 字段

Doctrine 类型 推荐的 EasyAdmin 字段
array ArrayField
ascii_string TextField
bigint TextField
binary (不支持)
blob (不支持)
boolean BooleanField
date_immutable DateField
date DateField
datetime_immutable DateTimeField
datetime DateTimeField
datetimetz_immutable DateTimeField
datetimetz DateTimeField
datetinterval TextField
decimal NumberField
float NumberField
guid TextField
integer IntegerField
json_array ArrayField
json TextField, TextareaField, CodeEditorField
object TextField, TextareaField, CodeEditorField
simple_array ArrayField
smallint IntegerField
string TextField
text TextareaField, TextEditorField, CodeEditorField
time_immutable TimeField
time TimeField

除了这些,EasyAdmin 还包括用于特定值的其他字段类型

  • AvatarField, ColorField, CountryField, CurrencyField, EmailField, IdField, ImageField, LanguageField, LocaleField, SlugField, TelephoneField, TimezoneFieldUrlField 与 Doctrine 的 string 类型配合良好。
  • MoneyFieldPercentField 与 Doctrine 的 decimalfloatinteger 配合良好,具体取决于您如何存储数据。
  • AssociationFieldCollectionFieldChoiceField 是特殊字段,分别对应于 Symfony 的 EntityTypeCollectionTypeChoiceType

提示

如果您想使用 Doctrine 的 自定义映射类型 之一,则应创建 Symfony 的 自定义表单字段类型 之一和 EasyAdmin 的 自定义字段 之一。 请注意,对于某些自定义映射类型,如果您需要它们,您还需要自定义 EasyAdmin 的搜索和筛选功能。

字段配置

本节显示了所有字段类型可用的配置选项。 此外,某些字段定义了其他配置选项,如 字段参考 中所示。

标签选项

字段构造函数的第二个可选参数是标签,它可以采用许多不同的值

  • 如果您未显式设置标签,EasyAdmin 会根据字段名称自动生成标签(例如“firstName”->“First Name”);
  • null:EasyAdmin 会根据字段名称自动生成标签(例如“firstName”->“First Name”);
  • 空字符串:该字段不显示任何标签,但会渲染一个空的 <label> 元素,以免弄乱表单布局;
  • false:该字段不显示任何标签,也不会渲染 <label> 元素。 这对于显示特殊的全宽字段(例如使用自定义字段模板创建的地图或宽表)非常有用;
  • 如果您显式设置标签,EasyAdmin 将使用该值;内容可以包含 HTML 标签,它们将被渲染,而不是转义。 此外,您可以使用 Translatable 内容(例如 t('admin.form.labels.user')

以下是一些字段标签的实际示例

// 未定义标签:自动生成标签(label = 'First Name')TextField::new('firstName'), // 标签为 null:自动生成标签(label = 'First Name')TextField::new('firstName', null),

// 标签为 false:不显示标签,也不渲染 <label> 元素 TextField::new('firstName', false),

// 显式设置标签:渲染其内容,包括 HTML 标签 TextField::new('firstName', 'Customer <b>Name</b>'),

设计选项

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
TextField::new('firstName', 'Name')
    // use this method if your field needs a specific form theme to render properly
    ->addFormTheme('@FOSCKEditor/Form/ckeditor_widget.html.twig')
    // you can add more than one form theme using the same method
    ->addFormTheme('theme1.html.twig', 'theme2.html.twig', 'theme3.html.twig')

    // CSS class/classes are applied to the field contents (in the 'index' page)
    // or to the row that wraps the contents (in the 'detail', 'edit' and 'new' pages)

    // use this method to add new classes to the ones applied by EasyAdmin
    ->addCssClass('text-large text-bold')
    // use this other method if you want to remove any CSS class added by EasyAdmin
    ->setCssClass('text-large text-bold')

    // this defines the Twig template used to render this field in 'index' and 'detail' pages
    // (this is not used in the 'edit'/'new' pages because they use Symfony Forms themes)
    ->setTemplatePath('admin/fields/my_template.html.twig')

    // useful for example to right-align numbers/money values (this setting is ignored in 'detail' page)
    ->setTextAlign('right')
;

CRUD 设计选项 类似,字段也可以加载 CSS 文件、Javascript 文件和 Webpack Encore 条目,并将 HTML 内容添加到后端页面的 <head> 和/或 <body> 元素中

1
2
3
4
5
6
7
TextField::new('firstName', 'Name')
    ->addCssFiles('bundle/some-bundle/foo.css', 'some-custom-styles.css')
    ->addJsFiles('admin/some-custom-code.js')
    ->addWebpackEncoreEntry('admin-maps')
    ->addHtmlContentToHead('<link rel="dns-prefetch" href="https://assets.example.com">')
    ->addHtmlContentToBody('<!-- generated at '.time().' -->')
;

默认情况下,这些 Web 资源在所有后端页面中加载。 如果您需要更精确的控制,请使用 Asset 类来定义资源

1
2
3
4
5
6
7
8
9
10
use EasyCorp\Bundle\EasyAdminBundle\Config\Asset;
// ...

TextField::new('firstName', 'Name')
    ->addCssFiles(Asset::new('bundle/some-bundle/foo.css')->ignoreOnForm()->htmlAttr('media', 'print'))
    ->addJsFiles(Asset::new('admin/some-custom-code.js')->onlyOnIndex()->defer())
    ->addWebpackEncoreEntries(Asset::new('admin-maps')->onlyWhenCreating()->preload())
    // you can even define the Symfony Asset package which the asset belongs to
    ->addCssFiles(Asset::new('some-path/bar.css')->package('legacy_assets'))
;

格式化选项

formatValue() 方法允许在 indexdetail 页面中渲染值之前,将 PHP 可调用对象应用于该值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
IntegerField::new('stock', 'Stock')
    // callbacks usually take only the current value as argument
    ->formatValue(function ($value) {
        return $value < 10 ? sprintf('%d **LOW STOCK**', $value) : $value;
    });

TextEditorField::new('description')
    // callables also receives the entire entity instance as the second argument
    ->formatValue(function ($value, $entity) {
        return $entity->isPublished() ? $value : 'Coming soon...';
    });

// in PHP 7.4 and newer you can use arrow functions
// ->formatValue(fn ($value) => $value < 10 ? sprintf('%d **LOW STOCK**', $value) : $value);
// ->formatValue(fn ($value, $entity) => $entity->isPublished() ? $value : 'Coming soon...');

其他选项

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
TextField::new('firstName', 'Name')
    // if TRUE, listing can be sorted by this field (default: TRUE)
    // unmapped fields cannot be sorted
    ->setSortable(false)

    // help message displayed for this field in the 'detail', 'edit' and 'new' pages
    ->setHelp('...')

    // sets the value of the `empty_data` option in the Symfony form
    // see https://symfony.com.cn/doc/current/reference/forms/types/form.html#empty-data
    ->setEmptyData('Jane Doe')

    // the Symfony Form type used to render this field in 'edit'/'new' pages
    // (fields have good default values for this option, so you don't usually configure this)
    ->setFormType(TextType::class)

    // an array of parameters passed to the Symfony form type
    // (this only overrides the values of the passed form type options;
    // it leaves all the other existing type options unchanged)
    ->setFormTypeOptions(['option_name' => 'option_value'])

    // a custom HTML attribute added when rendering the field
    // e.g. setHtmlAttribute('data-foo', 'bar') renders a 'data-foo="bar"' attribute in HTML
    // On 'index' and 'detail' pages, the attribute is added to the field container:
    // <td> and div.field-group respectively
    // On 'new' and 'edit' pages, the attribute is added to the form field;
    // it's a shortcut for the equivalent setFormTypeOption('attr.data-foo', 'bar)
    ->setHtmlAttribute('attribute_name', 'attribute_value')

    // a key-value array of attributes to add to the HTML element
    ->setHtmlAttributes(['data-foo' => 'bar', 'autofocus' => 'autofocus'])

创建自定义字段

字段是一个实现 EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface 的类。 尽管该接口仅要求实现少量方法,但您可能希望添加内置字段中提供的所有方法,以配置所有常见的字段选项。 您可以使用 EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait 来实现此目的。

假设您要创建一个自定义 MapField,它为给定的邮政地址渲染一个完整的地图。 这是您可以为该字段创建的类

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
32
33
34
35
36
37
38
39
namespace App\Admin\Field;

use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use EasyCorp\Bundle\EasyAdminBundle\Field\FieldTrait;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;

final class MapField implements FieldInterface
{
    use FieldTrait;

    /**
     * @param TranslatableInterface|string|false|null $label
     */
    public static function new(string $propertyName, $label = null): self
    {
        return (new self())
            ->setProperty($propertyName)
            ->setLabel($label)

            // this template is used in 'index' and 'detail' pages
            ->setTemplatePath('admin/field/map.html.twig')

            // this is used in 'edit' and 'new' pages to edit the field contents
            // you can use your own form types too
            ->setFormType(TextareaType::class)
            ->addCssClass('field-map')

            // loads the CSS and JS assets associated to the given Webpack Encore entry
            // in any CRUD page (index/detail/edit/new). It's equivalent to calling
            // encore_entry_link_tags('...') and encore_entry_script_tags('...')
            ->addWebpackEncoreEntries('admin-field-map')

            // these methods allow to define the web assets loaded when the
            // field is displayed in any CRUD page (index/detail/edit/new)
            ->addCssFiles('js/admin/field-map.css')
            ->addJsFiles('js/admin/field-map.js')
        ;
    }
}

接下来,创建用于在 indexdetail CRUD 页面 中渲染字段的模板。 该模板可以使用任何 Twig 模板功能 和以下变量

  • ea,一个 EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext 实例,它存储 管理上下文,并且在所有后端模板中都可用;
  • field,一个 EasyCorp\Bundle\EasyAdminBundle\Dto\FieldDto 实例,它存储正在渲染的字段的配置和值;
  • entity,一个 EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto 实例,它存储字段所属实体的实例以及有关该 Doctrine 实体的其他有用数据。

注意

此模板不用于 editnew CRUD 页面,这些页面使用 Symfony 表单主题 来定义每个表单字段的显示方式。

就这样。 现在您可以在任何 CRUD 控制器中使用此字段

1
2
3
4
5
6
7
8
9
use App\Admin\MapField;

public function configureFields(string $pageName): iterable
{
    return [
        // ...
        MapField::new('shipAddress'),
    ];
}

自定义选项

如果您的字段在任何方面都是可配置的,则可以为其添加自定义选项。 添加选项的推荐方法是在字段对象中将它们的名称定义为公共常量,并使用 FieldTrait 中定义的 setCustomOption() 方法来设置它们的值。

假设上一节中定义的 MapField 允许使用 Google Maps 或 OpenStreetMap 来渲染地图。 您可以按如下所示添加该选项

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
32
33
namespace App\Admin\Field;

use EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldInterface;
use Symfony\Component\Form\Extension\Core\Type\TextareaType;

final class MapField implements FieldInterface
{
    use FieldTrait;

    public const OPTION_MAP_PROVIDER = 'mapProvider';

    public static function new(string $propertyName, ?string $label = null): self
    {
        return (new self())
            // ...
            ->setCustomOption(self::OPTION_MAP_PROVIDER, 'openstreetmap')
        ;
    }

    public function useGoogleMaps(): self
    {
        $this->setCustomOption(self::OPTION_MAP_PROVIDER, 'google');

        return $this;
    }

    public function useOpenStreetMap(): self
    {
        $this->setCustomOption(self::OPTION_MAP_PROVIDER, 'openstreetmap');

        return $this;
    }
}

稍后,您可以通过字段 DTO 的 getCustomOptions() 方法访问这些选项。 例如,在 Twig 模板中

1
2
3
4
5
6
7
8
9
10
11
{# admin/field/map.html.twig #}
{% if 'google' === field.customOptions.get('mapProvider') %}
    {# ... #}
{% endif %}

{# if you defined the field options as public constants, you can access
   them in the template too (although resulting code is a bit verbose) #}
{% set map_provider_option = constant('App\\Admin\\MapField::OPTION_MAP_PROVIDER') %}
{% if 'google' === field.customOptions.get(map_provider_option) %}
    {# ... #}
{% endif %}

字段配置器

某些字段的默认选项取决于实体属性的值,该值仅在运行时可用。 这就是为什么您可以选择定义字段配置器的原因,它是一个在渲染字段之前更新字段配置的类。

EasyAdmin 为其内置字段定义了许多配置器。 您也可以创建自己的配置器(用于配置您自己的字段和/或内置字段)。 字段配置器是实现 EasyCorp\Bundle\EasyAdminBundle\Contracts\Field\FieldConfiguratorInterface 的类。

实现后,为您的配置器定义一个 Symfony 服务,并使用 ea.field_configurator 标记对其进行标记。 您可以选择定义标记的 priority 属性,以便在内置配置器之前或之后运行您的配置器。

这项工作,包括代码示例,均根据 Creative Commons BY-SA 3.0 许可获得许可。
目录
    版本