CRUD 控制器
CRUD 控制器 为 Doctrine ORM 实体提供 CRUD 操作 (创建、显示、更新、删除)。每个 CRUD 控制器可以关联到一个或多个仪表盘。
从技术上讲,这些 CRUD 控制器是常规的 Symfony 控制器,因此您可以执行通常在控制器中执行的任何操作,例如注入服务和使用快捷方式,如 $this->render()
或 $this->isGranted()
。
CRUD 控制器必须实现 EasyCorp
,这确保在控制器中定义了某些方法。除了实现接口之外,您还可以从 AbstractCrudController
类扩展。运行以下命令以生成 CRUD 控制器的基本结构
1
$ php bin/console make:admin:crud
CRUD 控制器页面
CRUD 控制器的四个主要页面是
index
,显示实体列表,可以分页、按列排序,并使用搜索查询和过滤器进行优化;detail
,显示给定实体的内容;new
,允许创建新的实体实例;edit
,允许更新给定实体的任何属性。
这些页面由 AbstractCrudController
控制器中具有相同名称的四个操作生成。此控制器定义了其他辅助操作 (例如 delete
和 autocomplete
),这些操作与任何页面都不匹配。
AbstractCrudController
中这些操作的默认行为适用于大多数后端,但您可以通过多种方式自定义它:EasyAdmin 事件、自定义 EasyAdmin 模板 等。
页面名称和常量
某些方法需要一些 CRUD 页面的名称作为参数。您可以使用以下任何字符串:'index'
、'detail'
、'edit'
和 'new'
。如果您喜欢使用常量来表示这些值,请使用 Crud::PAGE_INDEX
、Crud::PAGE_DETAIL
、Crud::PAGE_EDIT
和 Crud::PAGE_NEW
(它们在 EasyCorp
类中定义)。
CRUD 控制器配置
CRUD 控制器的唯一强制配置选项是控制器管理的 Doctrine 实体的 FQCN。这被定义为一个公共静态方法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
namespace App\Controller\Admin;
use App\Entity\Product;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
class ProductCrudController extends AbstractCrudController
{
// it must return a FQCN (fully-qualified class name) of a Doctrine ORM entity
public static function getEntityFqcn(): string
{
return Product::class;
}
// ...
}
其余 CRUD 选项使用 configureCrud()
方法配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
namespace App\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
class ProductCrudController extends AbstractCrudController
{
// ...
public function configureCrud(Crud $crud): Crud
{
return $crud
->setEntityLabelInSingular('...')
->setDateFormat('...')
// ...
;
}
}
设计选项
1 2 3 4 5 6 7 8 9 10 11 12
public function configureCrud(Crud $crud): Crud
{
return $crud
// set this option if you prefer the page content to span the entire
// browser width, instead of the default design which sets a max width
->renderContentMaximized()
// set this option if you prefer the sidebar (which contains the main menu)
// to be displayed as a narrow column instead of the default expanded design
->renderSidebarMinimized()
;
}
实体选项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
public function configureCrud(Crud $crud): Crud
{
return $crud
// the labels used to refer to this entity in titles, buttons, etc.
->setEntityLabelInSingular('Product')
->setEntityLabelInPlural('Products')
// in addition to a string, the argument of the singular and plural label methods
// can be a closure that defines two nullable arguments: entityInstance (which will
// be null in 'index' and 'new' pages) and the current page name
->setEntityLabelInSingular(
fn (?Product $product, ?string $pageName) => $product ? $product->toString() : 'Product'
)
->setEntityLabelInPlural(function (?Category $category, ?string $pageName) {
return 'edit' === $pageName ? $category->getLabel() : 'Categories';
})
// the Symfony Security permission needed to manage the entity
// (none by default, so you can manage all instances of the entity)
->setEntityPermission('ROLE_EDITOR')
;
}
标题和帮助选项
默认情况下,index
和 new
页面的页面标题基于使用 setEntityLabelInSingular()
和 setEntityLabelInPlural()
方法定义的 实体选项 值。detail
和 edit
页面中,EasyAdmin 首先尝试将实体转换为字符串表示形式,否则回退到通用标题。
您可以使用以下方法覆盖默认页面标题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
public function configureCrud(Crud $crud): Crud
{
return $crud
// the visible title at the top of the page and the content of the <title> element
// it can include these placeholders:
// %entity_name%, %entity_as_string%,
// %entity_id%, %entity_short_id%
// %entity_label_singular%, %entity_label_plural%
->setPageTitle('index', '%entity_label_plural% listing')
// you can pass a PHP closure as the value of the title
->setPageTitle('new', fn () => new \DateTime('now') > new \DateTime('today 13:00') ? 'New dinner' : 'New lunch')
// in DETAIL and EDIT pages, the closure receives the current entity
// as the first argument
->setPageTitle('detail', fn (Product $product) => (string) $product)
->setPageTitle('edit', fn (Category $category) => sprintf('Editing <b>%s</b>', $category->getName()))
// the help message displayed to end users (it can contain HTML tags)
->setHelp('edit', '...')
;
}
日期、时间和数字格式化选项
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
public function configureCrud(Crud $crud): Crud
{
return $crud
// the argument must be either one of these strings: 'short', 'medium', 'long', 'full', 'none'
// (the strings are also available as \EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField::FORMAT_* constants)
// or a valid ICU Datetime Pattern (see https://unicode-org.github.io/icu/userguide/format_parse/datetime/)
->setDateFormat('...')
->setTimeFormat('...')
// first argument = datetime pattern or date format; second optional argument = time format
->setDateTimeFormat('...', '...')
->setDateIntervalFormat('%%y Year(s) %%m Month(s) %%d Day(s)')
->setTimezone('...')
// this option makes numeric values to be rendered with a sprintf()
// call using this value as the first argument.
// this option overrides any formatting option for all numeric values
// (e.g. setNumDecimals(), setRoundingMode(), etc. are ignored)
// NumberField and IntegerField can override this value with their
// own setNumberFormat() methods, which works in the same way
->setNumberFormat('%.2d');
;
}
搜索、排序和分页选项
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
public function configureCrud(Crud $crud): Crud
{
return $crud
// the names of the Doctrine entity properties where the search is made on
// (by default it looks for in all properties)
->setSearchFields(['name', 'description'])
// use dots (e.g. 'seller.email') to search in Doctrine associations
->setSearchFields(['name', 'description', 'seller.email', 'seller.address.zipCode'])
// set it to null to disable and hide the search box
->setSearchFields(null)
// defines the initial sorting applied to the list of entities
// (user can later change this sorting by clicking on the table columns)
->setDefaultSort(['id' => 'DESC'])
->setDefaultSort(['id' => 'DESC', 'title' => 'ASC', 'startsAt' => 'DESC'])
// you can sort by Doctrine associations up to two levels
->setDefaultSort(['seller.name' => 'ASC'])
// the max number of entities to display per page
->setPaginatorPageSize(30)
// the number of pages to display on each side of the current page
// e.g. if num pages = 35, current page = 7 and you set ->setPaginatorRangeSize(4)
// the paginator displays: [Previous] 1 ... 3 4 5 6 [7] 8 9 10 11 ... 35 [Next]
// set this number to 0 to display a simple "< Previous | Next >" pager
->setPaginatorRangeSize(4)
// these are advanced options related to Doctrine Pagination
// (see https://www.doctrine-project.org/projects/doctrine-orm/en/2.7/tutorials/pagination.html)
->setPaginatorUseOutputWalkers(true)
->setPaginatorFetchJoinCollection(true)
;
}
注意
当使用 Doctrine 过滤器 时,列表可能不包含某些项目,因为它们已被这些全局 Doctrine 过滤器删除。当请求 URL 属于仪表盘时,请使用仪表盘路由名称以不应用过滤器。您还可以通过 应用程序上下文变量 获取仪表盘路由名称。
默认 Doctrine 查询用于获取 index
页面中显示的实体列表,它考虑了排序配置、可选的搜索查询、可选的 过滤器 和分页。如果您需要完全自定义此查询,请在您的 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
public function configureCrud(Crud $crud): Crud
{
return $crud
// this method allows to use your own template to render a certain part
// of the backend instead of using EasyAdmin default template
// the first argument is the "template name", which is the same as the
// Twig path but without the `@EasyAdmin/` prefix and the `.html.twig` suffix
->overrideTemplate('crud/field/id', 'admin/fields/my_id.html.twig')
// the theme/themes to use when rendering the forms of this entity
// (in addition to EasyAdmin default theme)
->addFormTheme('foo.html.twig')
// this method overrides all existing form themes (including the
// default EasyAdmin form theme)
->setFormThemes(['my_theme.html.twig', 'admin.html.twig'])
// this sets the options of the entire form (later, you can set the options
// of each form type via the methods of their associated fields)
// pass a single array argument to apply the same options for the new and edit forms
->setFormOptions([
'validation_groups' => ['Default', 'my_validation_group']
]);
// pass two array arguments to apply different options for the new and edit forms
// (pass an empty array argument if you want to apply no options to some form)
->setFormOptions(
['validation_groups' => ['my_validation_group']],
['validation_groups' => ['Default'], '...' => '...'],
);
;
}
创建或编辑实体后自定义重定向
默认情况下,在创建或编辑实体时单击“保存”按钮后,您将被重定向到上一页。如果您想更改此行为,请覆盖 getRedirectResponseAfterSave()
方法。
例如,如果您添加了一个名为“保存并查看详情”的 自定义操作,您可能更喜欢在保存更改后重定向到详情页面
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
protected function getRedirectResponseAfterSave(AdminContext $context, string $action): RedirectResponse
{
$submitButtonName = $context->getRequest()->request->all()['ea']['newForm']['btn'];
if ('saveAndViewDetail' === $submitButtonName) {
$url = $this->get(AdminUrlGenerator::class)
->setAction(Action::DETAIL)
->setEntityId($context->getEntity()->getPrimaryKeyValue())
->generateUrl();
return $this->redirect($url);
}
return parent::getRedirectResponseAfterSave($context, $action);
}
不同 CRUD 控制器中的相同配置
如果您想在所有 CRUD 控制器中执行相同的配置,则无需在每个控制器中重复配置。相反,在您的仪表盘中添加 configureCrud()
方法,所有控制器都将继承该配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
class DashboardController extends AbstractDashboardController
{
// ...
public function configureCrud(): Crud
{
return Crud::new()
// this defines the pagination size for all CRUD controllers
// (each CRUD controller can override this value if needed)
->setPaginatorPageSize(30)
;
}
}
字段
字段允许在每个 CRUD 页面 上显示 Doctrine 实体的内容。EasyAdmin 提供了内置字段来显示所有常见数据类型,但您也可以 创建自己的字段。
如果您的 CRUD 控制器从 AbstractCrudController
扩展,则字段会自动配置。在 index
页面中,您将看到一些字段,而在其余页面中,您将看到尽可能多的字段,以显示 Doctrine 实体的所有属性。
阅读关于 字段的章节,了解如何配置在每个页面上显示哪些字段、如何配置每个字段的呈现方式等。
自定义 CRUD 操作
默认的 CRUD 操作 (index()
、detail()
、edit()
、new()
和 delete()
控制器中的方法) 实现了应用程序中最常用的行为。
自定义其行为的第一种方法是在您自己的控制器中覆盖这些方法。但是,原始操作非常通用,以至于它们包含相当多的代码,因此覆盖它们并不是那么方便。
相反,您可以覆盖实现 CRUD 操作所需的某些功能的其他较小方法。例如,index()
操作调用名为 createIndexQueryBuilder()
的方法来创建 Doctrine 查询构建器,用于获取索引列表中显示的结果。如果您想自定义该列表,最好覆盖 createIndexQueryBuilder()
方法,而不是整个 index()
方法。有很多这样的方法,因此您应该查看 EasyCorp
类。
自定义 CRUD 操作的另一种选择是使用 EasyAdmin 触发的事件,例如 BeforeCrudActionEvent
和 AfterCrudActionEvent
。
创建、持久化和删除实体
CRUD 控制器的大多数操作最终都会创建、持久化或删除实体。如果您的 CRUD 控制器从 AbstractCrudController
扩展,则这些方法已经实现,但您可以通过覆盖方法和监听事件来自定义它们。
首先,您可以覆盖 createEntity()
、updateEntity()
、persistEntity()
和 deleteEntity()
方法。createEntity()
方法例如仅执行 return new $entityFqcn()
,因此如果您的实体需要传递构造函数参数或设置其某些属性,则需要覆盖它
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
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 createEntity(string $entityFqcn)
{
$product = new Product();
$product->createdBy($this->getUser());
return $product;
}
// ...
}
覆盖此行为的另一种方法是监听 EasyAdmin 在创建、更新、持久化、删除等实体时触发的事件。
将其他变量传递给 CRUD 模板
AbstractCrudController
中实现的默认 CRUD 操作不会以通常的 $this->render('...')
指令结束,以呈现 Twig 模板并在 Symfony Response
对象中返回其内容。
相反,CRUD 操作返回一个 EasyCorp
对象,其中包含传递给模板的变量,该模板呈现 CRUD 操作内容。此 KeyValueStore
对象类似于 Symfony 的 ParameterBag
对象。它就像一个面向对象的数组,具有有用的方法,例如 get()
、set()
、has()
等。
在结束每个 CRUD 操作之前,它们的 KeyValueStore
对象会传递给一个名为 configureResponseParameters()
的方法,您可以在自己的控制器中覆盖该方法以添加/删除/更改这些模板变量
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
namespace App\Controller\Admin;
use App\Entity\Product;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Config\KeyValueStore;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
class ProductCrudController extends AbstractCrudController
{
// ...
public function configureResponseParameters(KeyValueStore $responseParameters): KeyValueStore
{
if (Crud::PAGE_DETAIL === $responseParameters->get('pageName')) {
$responseParameters->set('foo', '...');
// keys support the "dot notation", so you can get/set nested
// values separating their parts with a dot:
$responseParameters->setIfNotSet('bar.foo', '...');
// this is equivalent to: $parameters['bar']['foo'] = '...'
}
return $responseParameters;
}
}
您可以根据需要向此 KeyValueStore
对象添加任意数量的参数。唯一必需的参数是 templateName
或 templatePath
,分别用于设置要呈现为 CRUD 操作结果的模板的名称或路径。
模板名称和模板路径
EasyAdmin 用于呈现其内容的所有模板都是可配置的。这就是为什么 EasyAdmin 处理“模板名称”而不是普通的 Twig 模板路径。
模板名称与模板路径相同,但没有 @EasyAdmin
前缀和 .html.twig
后缀。例如,@EasyAdmin/layout.html.twig
指的是 EasyAdmin 提供的内置布局模板。但是,layout
指的是“配置为应用程序布局的任何模板”。
使用模板名称而不是路径使您可以完全灵活地自定义应用程序行为,同时保留所有自定义模板。在 Twig 模板中,使用 ea.templatePath()
函数获取与给定模板名称关联的 Twig 路径
1 2 3 4 5 6 7
<div id="flash-messages">
{{ include(ea.templatePath('flash_messages')) }}
</div>
{% if some_value is null %}
{{ include(ea.templatePath('label/null')) }}
{% endif %}
生成管理后台 URL
3.2
AdminUrlGenerator
类是在 EasyAdmin 3.2.0 中引入的。在早期版本中,您必须使用 CrudUrlGenerator
类并调用 build()
方法来开始构建 URL。
正如关于仪表盘的文章中 解释的,给定仪表盘的所有 URL 都使用相同的路由,并且它们仅在查询字符串参数中有所不同。您可以使用 AdminUrlGenerator
服务在 PHP 代码中生成 URL,而无需处理这些问题。
生成 URL 时,您不是从头开始。EasyAdmin 重用当前请求中存在的所有查询参数。这样做是有目的的,因为基于当前 URL 生成新 URL 是最常见的场景。使用 unsetAll()
方法删除所有现有查询参数
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\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
class SomeCrudController extends AbstractCrudController
{
private $adminUrlGenerator;
public function __construct(AdminUrlGenerator $adminUrlGenerator)
{
$this->adminUrlGenerator = $adminUrlGenerator;
}
// ...
public function someMethod()
{
// instead of injecting the AdminUrlGenerator service in the constructor,
// you can also get it from inside a controller action as follows:
// $adminUrlGenerator = $this->get(AdminUrlGenerator::class);
// the existing query parameters are maintained, so you only
// have to pass the values you want to change.
$url = $this->adminUrlGenerator->set('page', 2)->generateUrl();
// you can remove existing parameters
$url = $this->adminUrlGenerator->unset('menuIndex')->generateUrl();
$url = $this->adminUrlGenerator->unsetAll()->set('foo', 'someValue')->generateUrl();
// the URL builder provides shortcuts for the most common parameters
$url = $this->adminUrlGenerator
->setController(SomeCrudController::class)
->setAction('theActionName')
->generateUrl();
// ...
}
}
提示
如果您出于任何原因需要手动处理管理后台 URL,则查询字符串参数的名称在 EA 类中定义为常量。
由于 ea_url()
Twig 函数,模板中也提供了完全相同的功能。在模板中,您可以省略对 generateUrl()
方法的调用 (它将自动为您调用)
1 2 3 4 5 6 7 8 9
{# both are equivalent #}
{% set url = ea_url({ page: 2 }).generateUrl() %}
{% set url = ea_url({ page: 2 }) %}
{% set url = ea_url().set('page', 2) %}
{% set url = ea_url()
.setController('App\\Controller\\Admin\\SomeCrudController')
.setAction('theActionName') %}
从 EasyAdmin 外部生成 CRUD URL
从 EasyAdmin 外部 (例如,从常规 Symfony 控制器) 生成 EasyAdmin 页面的 URL 时,管理后台上下文变量 不可用。这就是为什么您必须始终设置与 URL 关联的 CRUD 控制器的原因。如果您有多个仪表盘,则还必须设置仪表盘
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 40 41 42
use App\Controller\Admin\DashboardController;
use App\Controller\Admin\ProductCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Router\AdminUrlGenerator;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class SomeSymfonyController extends AbstractController
{
private $adminUrlGenerator;
public function __construct(AdminUrlGenerator $adminUrlGenerator)
{
$this->adminUrlGenerator = $adminUrlGenerator;
}
public function someMethod()
{
// if your application only contains one Dashboard, it's enough
// to define the controller related to this URL
$url = $this->adminUrlGenerator
->setController(ProductCrudController::class)
->setAction(Action::INDEX)
->generateUrl();
// in applications containing more than one Dashboard, you must also
// define the Dashboard associated to the URL
$url = $this->adminUrlGenerator
->setDashboard(DashboardController::class)
->setController(ProductCrudController::class)
->setAction(Action::INDEX)
->generateUrl();
// some actions may require to pass additional parameters
$url = $this->adminUrlGenerator
->setController(ProductCrudController::class)
->setAction(Action::EDIT)
->setEntityId($product->getId())
->generateUrl();
// ...
}
}
这同样适用于在 Twig 模板中生成的 URL
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
{# if your application defines only one Dashboard #}
{% set url = ea_url()
.setController('App\\Controller\\Admin\\ProductCrudController')
.setAction('index') %}
{# if you prefer PHP constants, use this:
.setAction(constant('EasyCorp\\Bundle\\EasyAdminBundle\\Config\\Action::INDEX')) #}
{# if your application defines multiple Dashboards #}
{% set url = ea_url()
.setDashboard('App\\Controller\\Admin\\DashboardController')
.setController('App\\Controller\\Admin\\ProductCrudController')
.setAction('index') %}
{# some actions may require to pass additional parameters #}
{% set url = ea_url()
.setController('App\\Controller\\Admin\\ProductCrudController')
.setAction('edit')
.setEntityId(product.id) %}