字段
字段允许在每个 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 Forms 来创建和编辑 Doctrine 实体。这就是为什么所有实体属性都必须是可为空的:它们的 setter 需要接受 null
值,并且它们的 getter 必须允许返回 null
。在数据库中,关联的字段不必是可为空的。
未映射字段
字段通常引用相关 Doctrine 实体的属性。但是,它们也可以引用实体的方法,这些方法不与任何属性关联。例如,如果你的 Customer
实体定义了 firstName
和 lastName
属性,你可能想要显示一个包含两个值合并的“全名”字段。
为此,将以下方法添加到实体
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 查询中。
每页显示不同的字段
有几种方法可以根据当前页面有条件地显示字段
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()
(在edit
和new
页面中都隐藏该字段)hideWhenCreating()
hideWhenUpdating()
onlyOnIndex()
onlyOnDetail()
onlyOnForms()
(在除edit
和new
之外的所有页面中隐藏该字段)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 每行显示一个表单字段。在行内,每种字段类型使用不同的默认宽度(例如,整数字段很窄,而代码编辑器字段非常宽)。你可以使用每个字段的 setColumns()
方法覆盖此行为。
在使用此选项之前,你必须熟悉 Bootstrap 网格系统,它将每行分为 12 个等宽列,以及 Bootstrap 断点,它们是 xs
(设备宽度 < 576px),sm
(>= 576px),md
(>= 768px),lg
(>= 992px),xl
(>= 1,200px)和 xxl
(>= 1,400px)。
假设你想在同一行上显示两个名为 startsAt
和 endsAt
的字段,它们各自跨越行的 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),
];
}
此示例在同一行上呈现两个字段,但在 xs
和 sm
断点中除外,其中每个字段都占据整行(因为设备宽度太小)。
如果你需要更好地控制取决于设备宽度的设计,你可以传递一个带有响应式 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
断点中也在同一行上显示这两个字段。此外,它减少了较大断点(lg
和 xxl
)中的列数,以改善这些字段的渲染效果。
提示
你还可以使用与重新排序和偏移列相关的 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),
];
}
表单面板
在显示大量字段的页面中,你可以使用用特殊 FormField
对象创建的“面板”将它们分组
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
use EasyCorp\Bundle\EasyAdminBundle\Field\FormField;
public function configureFields(string $pageName): iterable
{
return [
IdField::new('id')->hideOnForm(),
// panels usually display only a title
FormField::addPanel('User Details'),
TextField::new('firstName'),
TextField::new('lastName'),
// panels without titles only display a separation between fields
FormField::addPanel(),
DateTimeField::new('createdAt')->onlyOnDetail(),
// panels can also define their icon, CSS class and help message
FormField::addPanel('Contact information')
->setIcon('phone')->addCssClass('optional')
->setHelp('Phone number is preferred'),
TextField::new('phone'),
TextField::new('email')->hideOnIndex(),
// panels can be collapsible too (useful if your forms are long)
// this makes the panel collapsible but renders it expanded by default
FormField::addPanel('Contact information')->collapsible(),
// this makes the panel collapsible and renders it collapsed by default
FormField::addPanel('Contact information')->renderCollapsed(),
];
}
表单标签页
在显示大量字段的页面中,你可以使用用特殊 FormField
对象创建的“标签页”将它们分组
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
use EasyCorp\Bundle\EasyAdminBundle\Field\FormField;
public function configureFields(string $pageName): iterable
{
return [
IdField::new('id')->hideOnForm(),
// Add a tab
FormField::addTab('First Tab'),
// You can use a Form Panel inside a Form Tab
FormField::addPanel('User Details'),
// Your fields
TextField::new('firstName'),
TextField::new('lastName'),
// Add a second Form Tab
// Tabs can also define their icon, CSS class and help message
FormField::addTab('Contact information Tab')
->setIcon('phone')->addCssClass('optional')
->setHelp('Phone number is preferred'),
TextField::new('phone'),
];
}
字段类型
这些是 EasyAdmin 提供的所有内置字段
字段配置
本节介绍所有字段类型可用的配置选项。此外,某些字段还定义了其他配置选项,如字段参考中所示。
标签选项
字段构造函数的第二个可选参数是标签
1 2 3 4 5 6 7 8 9 10
// not defining the label explicitly or setting it to NULL means
// that the label is autogenerated (e.g. 'firstName' -> 'First Name')
TextField::new('firstName'),
TextField::new('firstName', null),
// set the label explicitly to display exactly that label
TextField::new('firstName', 'Name'),
// set the label to FALSE to not display any label for this field
TextField::new('firstName', false),
设计选项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
TextField::new('firstName', 'Name')
// 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')
格式化选项
formatValue()
方法允许在 index
和 detail
页面中渲染值之前应用 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
TextField::new('firstName', 'Name')
// if TRUE, listing can be sorted by this field (default: TRUE)
// unmapped fields and Doctrine associations cannot be sorted
->setSortable(false)
// help message displayed for this field in the 'detail', 'edit' and 'new' pages
->setHelp('...')
// 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'])
字段参考
注意
本节尚未准备就绪。我们正在努力。同时,你可以依靠你的 IDE 自动完成功能来发现每个字段的所有配置选项。
创建自定义字段
字段是实现 EasyCorp
的类。尽管该接口仅要求实现一些方法,但你可能想要添加内置字段中提供的所有方法来配置所有常见的字段选项。你可以为此使用 EasyCorp
。
假设你想创建一个自定义 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 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')
;
}
}
接下来,创建用于在 index
和 detail
CRUD 页面中渲染字段的模板。该模板可以使用任何 Twig 模板功能和以下变量
ea
,一个EasyCorp
实例,它存储了管理上下文,并且在所有后端模板中都可用;\Bundle \EasyAdminBundle \Context \AdminContext field
,一个EasyCorp
实例,它存储了正在渲染的字段的配置和值;\Bundle \EasyAdminBundle \Dto \FieldDto entity
,一个EasyCorp
实例,它存储了字段所属的实体的实例以及关于该 Doctrine 实体的其他有用数据。\Bundle \EasyAdminBundle \Dto \EntityDto
注意
此模板不用于 edit
和 new
CRUD 页面,它们使用 Symfony Form 主题来定义如何显示每个表单字段。
就这样。你现在可以在你的任何 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
的类。
一旦实现,为你的配置器定义一个 Symfony 服务,并使用 ea.field_configurator
标签标记它。你可以选择定义标签的 priority
属性,以便在内置配置器之前或之后运行你的配置器。