仪表盘
仪表盘是后端的入口点,它们链接到一个或多个 资源。仪表盘还显示一个主菜单,用于导航资源和已登录用户的信息。
假设你有一个简单的应用程序,包含三个 Doctrine 实体:用户、博客文章和分类。你的员工可以创建和编辑所有这些实体,但外部合作者只能创建博客文章。
你可以在 EasyAdmin 中按如下方式实现这一点
- 创建三个 CRUD 控制器 (例如,
UserCrudController
、BlogPostCrudController
和CategoryCrudController
); - 为你的员工创建一个仪表盘 (例如,
DashboardController
) 并链接到这三个资源; - 为你的外部合作者创建一个仪表盘 (例如,
ExternalDashboardController
) 并且只链接到BlogPostCrudController
资源。
从技术上讲,仪表盘是常规的 Symfony 控制器,所以你可以像在控制器中通常做的那样做任何事情,例如注入服务和使用快捷方式,如 $this->render()
或 $this->isGranted()
。
仪表盘控制器类必须实现 EasyCorp
接口,这确保了某些方法在仪表盘中被定义。除了实现接口,你也可以继承 AbstractDashboardController
类。运行以下命令可以快速生成一个仪表盘控制器
1
$ php bin/console make:admin:dashboard
仪表盘路由
每个仪表盘都使用单个 Symfony 路由来服务于其所有 URL。所需的信息通过查询字符串参数传递。如果你使用 make:admin:dashboard
命令生成了仪表盘,则该路由使用 Symfony 路由注解 或 PHP 属性(如果项目需要 PHP 8 或更高版本)来定义
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// src/Controller/Admin/DashboardController.php
namespace App\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DashboardController extends AbstractDashboardController
{
/**
* @Route("/admin")
*/
public function index(): Response
{
return parent::index();
}
// ...
}
/admin
URL 只是一个默认值,你可以更改它。如果你这样做,别忘了在你的 Symfony 安全配置中也更新这个值,以便 限制对整个后端的访问。
没有必要为此路由定义显式名称。Symfony 会自动生成路由名称,EasyAdmin 在运行时获取该值以生成所有 URL。但是,如果你在应用程序的其他部分生成指向仪表盘的 URL,你可以定义一个显式路由名称来简化你的代码
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
// src/Controller/Admin/DashboardController.php
namespace App\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DashboardController extends AbstractDashboardController
{
/**
* @Route("/admin", name="some_route_name")
*/
public function index(): Response
{
return parent::index();
}
// ...
}
如果你不使用注解,你必须在一个单独的文件中使用 YAML、XML 或 PHP 配置来配置仪表盘路由
1 2 3 4 5 6
# config/routes.yaml
dashboard:
path: /admin
controller: App\Controller\Admin\DashboardController::index
# ...
在实践中,你无需在你的应用程序中处理此路由或查询字符串参数,因为 EasyAdmin 提供了一个服务来 生成管理 URL。
注意
使用单个路由来处理所有后端 URL 意味着生成的 URL 有点长且不够美观。这是一个合理的权衡,因为它使许多其他功能(例如生成管理 URL)变得更加简单。
仪表盘配置
仪表盘配置在 configureDashboard()
方法中定义(主菜单和用户菜单在其各自的方法中配置,稍后会解释)
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 43 44 45 46 47
namespace App\Controller\Admin;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
class DashboardController extends AbstractDashboardController
{
// ...
public function configureDashboard(): Dashboard
{
return Dashboard::new()
// the name visible to end users
->setTitle('ACME Corp.')
// you can include HTML contents too (e.g. to link to an image)
->setTitle('<img src="..."> ACME <span class="text-small">Corp.</span>')
// the path defined in this method is passed to the Twig asset() function
->setFaviconPath('favicon.svg')
// the domain used by default is 'messages'
->setTranslationDomain('my-custom-domain')
// there's no need to define the "text direction" explicitly because
// its default value is inferred dynamically from the user locale
->setTextDirection('ltr')
// 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()
// by default, all backend URLs include a signature hash. If a user changes any
// query parameter (to "hack" the backend) the signature won't match and EasyAdmin
// triggers an error. If this causes any issue in your backend, call this method
// to disable this feature and remove all URL signature checks
->disableUrlSignatures()
// by default, all backend URLs are generated as absolute URLs. If you
// need to generate relative URLs instead, call this method
->generateRelativeUrls()
;
}
}
主菜单
主菜单从仪表盘链接到不同的 CRUD 控制器。这是关联仪表盘和资源的唯一方法。出于安全原因,后端只能通过主菜单访问与仪表盘关联的资源。
主菜单是实现了 EasyCorp
的对象的集合,这些对象配置每个菜单项的外观和行为
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
use App\Entity\BlogPost;
use App\Entity\Category;
use App\Entity\Comment;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
class DashboardController extends AbstractDashboardController
{
// ...
public function configureMenuItems(): iterable
{
return [
MenuItem::linkToDashboard('Dashboard', 'fa fa-home'),
MenuItem::section('Blog'),
MenuItem::linkToCrud('Categories', 'fa fa-tags', Category::class),
MenuItem::linkToCrud('Blog Posts', 'fa fa-file-text', BlogPost::class),
MenuItem::section('Users'),
MenuItem::linkToCrud('Comments', 'fa fa-comment', Comment::class),
MenuItem::linkToCrud('Users', 'fa fa-user', User::class),
];
}
}
MenuItem::new()
的第一个参数是项目显示的标签,第二个参数是要显示的 FontAwesome 图标的完整 CSS 类。
菜单项配置选项
所有菜单项都定义了以下方法来配置一些选项
setCssClass(string $cssClass)
,设置应用于菜单项的<li>
父元素的 CSS 类或类;setLinkRel(string $rel)
,设置菜单项链接的rel
HTML 属性(查看 “rel”属性的允许值);setLinkTarget(string $target)
,设置菜单项链接的target
HTML 属性(默认为_self
);setPermission(string $permission)
,设置用户必须拥有的 Symfony 安全权限才能看到此菜单项。阅读 菜单安全参考 了解更多详情。setBadge($content, string $style='secondary')
,将给定的内容呈现为菜单项的徽章。它通常用于显示通知计数。第一个参数可以是任何可以在 Twig 模板中转换为字符串的值(数字、字符串、*stringable* 对象等)。第二个参数是预定义的 Bootstrap 样式之一(primary
、secondary
、success
、danger
、warning
、info
、light
、dark
)或任意字符串内容,该内容作为与徽章关联的 HTML 元素的style
属性的值传递。
其余选项取决于每个菜单项类型,如下节所述。
菜单项类型
CRUD 菜单项
这是最常见的菜单项类型,它链接到某个 CRUD 控制器 的某个操作。你必须传递与 CRUD 控制器关联的 Doctrine 实体的 FQCN,而不是传递 CRUD 控制器的 FQCN *(完全限定类名)*
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
use App\Entity\Category;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
public function configureMenuItems(): iterable
{
return [
// ...
// links to the 'index' action of the Category CRUD controller
MenuItem::linkToCrud('Categories', 'fa fa-tags', Category::class),
// links to a different CRUD action
MenuItem::linkToCrud('Add Category', 'fa fa-tags', Category::class)
->setAction('new'),
MenuItem::linkToCrud('Show Main Category', 'fa fa-tags', Category::class)
->setAction('detail')
->setEntityId(1),
// if the same Doctrine entity is associated to more than one CRUD controller,
// use the 'setController()' method to specify which controller to use
MenuItem::linkToCrud('Categories', 'fa fa-tags', Category::class)
->setController(LegacyCategoryCrudController::class),
// uses custom sorting options for the listing
MenuItem::linkToCrud('Categories', 'fa fa-tags', Category::class)
->setDefaultSort(['createdAt' => 'DESC']),
];
}
仪表盘菜单项
它链接到当前仪表盘的主页。你可以使用“路由菜单项”(如下所述)实现相同的效果,但这个更简单,因为你无需指定路由名称(它会自动找到)
1 2 3 4 5 6 7 8 9
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
public function configureMenuItems(): iterable
{
return [
MenuItem::linkToDashboard('Home', 'fa fa-home'),
// ...
];
}
路由菜单项
它链接到你的 Symfony 应用程序定义的任何路由
1 2 3 4 5 6 7 8 9 10
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
public function configureMenuItems(): iterable
{
return [
MenuItem::linkToRoute('The Label', 'fa ...', 'route_name'),
MenuItem::linkToRoute('The Label', 'fa ...', 'route_name', ['routeParamName' => 'routeParamValue']),
// ...
];
}
注意
阅读关于 在 EasyAdmin 中集成 Symfony 控制器/操作 的章节,以充分理解 linkToRoute()
生成的 URL。
URL 菜单项
它链接到相对或绝对 URL
1 2 3 4 5 6 7 8 9 10
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
public function configureMenuItems(): iterable
{
return [
MenuItem::linkToUrl('Visit public website', null, '/'),
MenuItem::linkToUrl('Search in Google', 'fab fa-google', 'https://google.com'),
// ...
];
}
为避免将内部后端信息泄露到外部网站,EasyAdmin 会将 rel="noopener"
属性添加到所有 URL 菜单项,除非菜单项定义了自己的 rel
选项。
分区菜单项
它在菜单项之间创建视觉分隔,并且可以选择性地显示一个标签,该标签充当下方菜单项的标题
1 2 3 4 5 6 7 8 9 10 11 12 13 14
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
public function configureMenuItems(): iterable
{
return [
// ...
MenuItem::section(),
// ...
MenuItem::section('Blog'),
// ...
];
}
注销菜单项
它链接到用户必须访问才能从应用程序注销的 URL。如果你知道注销路由名称,你可以使用“路由菜单项”实现相同的效果,但这个更方便,因为它会自动为当前安全防火墙找到注销 URL
1 2 3 4 5 6 7 8 9
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
public function configureMenuItems(): iterable
{
return [
// ...
MenuItem::linkToLogout('Logout', 'fa fa-exit'),
];
}
注意
注销菜单项在某些身份验证方案(如 HTTP Basic)下不起作用,因为由于这些身份验证方案的工作方式,它们没有配置默认的注销路径。
如果你遇到类似 *“无法找到当前防火墙 LogoutListener,请手动提供提供程序密钥。”* 的错误,你需要删除注销菜单项或向你的身份验证方案添加注销提供程序。
退出模拟菜单项
它链接到用户必须访问才能停止模拟其他用户的 URL
1 2 3 4 5 6 7 8 9
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
public function configureMenuItems(): iterable
{
return [
// ...
MenuItem::linkToExitImpersonation('Stop impersonation', 'fa fa-exit'),
];
}
子菜单
主菜单最多可以显示两级嵌套菜单。子菜单使用 subMenu()
项目类型定义
1 2 3 4 5 6 7 8 9 10 11 12 13
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
public function configureMenuItems(): iterable
{
return [
MenuItem::subMenu('Blog', 'fa fa-article')->setSubItems([
MenuItem::linkToCrud('Categories', 'fa fa-tags', Category::class),
MenuItem::linkToCrud('Posts', 'fa fa-file-text', BlogPost::class),
MenuItem::linkToCrud('Comments', 'fa fa-comment', Comment::class),
]),
// ...
];
}
注意
在子菜单中,父菜单项不能链接到任何资源、路由或 URL;它只能展开/折叠子菜单项。
复杂的主菜单
configureMenuItems()
的返回类型是 iterable
,因此你不必总是返回数组。例如,如果你的主菜单需要复杂的逻辑来决定为每个用户显示哪些项目,则使用生成器返回菜单项会更方便
1 2 3 4 5 6 7 8 9 10 11 12
public function configureMenuItems(): iterable
{
yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
if ('... some complex expression ...') {
yield MenuItem::section('Blog');
yield MenuItem::linkToCrud('Categories', 'fa fa-tags', Category::class);
yield MenuItem::linkToCrud('Blog Posts', 'fa fa-file-text', BlogPost::class);
}
// ...
}
用户菜单
当访问受保护的后端时,EasyAdmin 会显示已登录应用程序的用户的详细信息以及包含一些选项(如“注销”)的菜单(如果启用了 Symfony 的 注销功能)。
用户名是对当前用户对象调用 __toString()
方法的结果。用户头像是一个通用的头像图标。使用 configureUserMenu()
方法配置此菜单的功能和项目
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
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Config\UserMenu;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\Security\Core\User\UserInterface;
class DashboardController extends AbstractDashboardController
{
// ...
public function configureUserMenu(UserInterface $user): UserMenu
{
// Usually it's better to call the parent method because that gives you a
// user menu with some menu items already created ("sign out", "exit impersonation", etc.)
// if you prefer to create the user menu from scratch, use: return UserMenu::new()->...
return parent::configureUserMenu($user)
// use the given $user object to get the user name
->setName($user->getFullName())
// use this method if you don't want to display the name of the user
->displayUserName(false)
// you can return an URL with the avatar image
->setAvatarUrl('https://...')
->setAvatarUrl($user->getProfileImageUrl())
// use this method if you don't want to display the user image
->displayUserAvatar(false)
// you can also pass an email address to use gravatar's service
->setGravatarEmail($user->getMainEmailAddress())
// you can use any type of menu item, except submenus
->addMenuItems([
MenuItem::linkToRoute('My Profile', 'fa fa-id-card', '...', ['...' => '...']),
MenuItem::linkToRoute('Settings', 'fa fa-user-cog', '...', ['...' => '...']),
MenuItem::section(),
MenuItem::linkToLogout('Logout', 'fa fa-sign-out'),
]);
}
}
管理上下文
EasyAdmin 在每个后端请求上自动初始化一个类型为 EasyCorp
的变量。此对象实现了 上下文对象 设计模式,并存储了后端不同部分常用的所有信息。
此上下文对象会自动注入到每个模板中,作为一个名为 ea
的变量(“EasyAdmin”的首字母缩写)
1 2 3 4 5
<h1>{{ ea.dashboardTitle }}</h1>
{% for menuItem in ea.mainMenu.items %}
{# ... #}
{% endfor %}
AdminContext
变量在每个请求上动态创建,因此你不能直接将其注入到你的服务中。相反,使用 AdminContextProvider
服务来获取上下文变量
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18
use EasyCorp\Bundle\EasyAdminBundle\Provider\AdminContextProvider;
final class SomeService
{
private $adminContextProvider;
public function __construct(AdminContextProvider $adminContextProvider)
{
$this->adminContextProvider = $adminContextProvider;
}
public function someMethod()
{
$context = $this->adminContextProvider->getContext();
}
// ...
}
在 EasyAdmin 的 CRUD 控制器 和 集成到 EasyAdmin 中的 Symfony 控制器 中,在你想要注入上下文对象的任何参数中使用 AdminContext
类型提示
1 2 3 4 5 6 7 8 9 10
use EasyCorp\Bundle\EasyAdminBundle\Context\AdminContext;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
class SomeController extends AbstractController
{
public function someMethod(AdminContext $context)
{
// ...
}
}
翻译
后端界面使用 Symfony 翻译 功能完全翻译。EasyAdmin 自己的消息和内容使用 EasyAdminBundle
翻译域(感谢我们的社区为数十种语言慷慨提供翻译)。
其余内容(例如,菜单项的标签、实体和字段名称等)默认使用 messages
翻译域。你可以使用 translationDomain()
方法更改此值
1 2 3 4 5 6 7 8 9 10 11 12 13
class DashboardController extends AbstractDashboardController
{
// ...
public function configureDashboard(): Dashboard
{
return Dashboard::new()
// ...
// the argument is the name of any valid Symfony translation domain
->setTranslationDomain('admin');
}
}
后端使用在 Symfony 应用程序中配置的相同语言。当语言环境为阿拉伯语 (ar
)、波斯语 (fa
) 或希伯来语 (he
) 时,HTML 文本方向会自动设置为 rtl
(从右到左)。否则,文本将显示为 ltr
(从左到右),但你可以显式配置此值
1 2 3 4 5 6 7 8 9 10 11 12 13 14
class DashboardController extends AbstractDashboardController
{
// ...
public function configureDashboard(): Dashboard
{
return Dashboard::new()
// ...
// most of the times there's no need to configure this explicitly
// (default: 'rtl' or 'ltr' depending on the language)
->setTextDirection('rtl');
}
}
提示
如果你想让后端使用与公共网站不同的语言,你需要 使用用户语言环境 在翻译服务检索请求语言环境之前设置请求语言环境。
注意
存储在数据库中的内容(例如,博客文章的内容或产品的名称)不会被翻译。EasyAdmin 不支持将实体属性内容翻译成不同的语言。
页面模板
EasyAdmin 提供了几个页面模板,这些模板在你的仪表盘中添加自定义逻辑时非常有用。
登录表单模板
Twig 模板路径:@EasyAdmin/page/login.html.twig
它显示一个简单的用户名 + 密码登录表单,其样式与后端的其余部分相匹配。该模板定义了许多配置选项,但大多数应用程序可以依赖其默认值
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 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80
namespace App\Controller;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
use Symfony\Component\Security\Http\Authentication\AuthenticationUtils;
class SecurityController extends AbstractController
{
/**
* @Route("/login", name="login")
*/
public function login(AuthenticationUtils $authenticationUtils): Response
{
$error = $authenticationUtils->getLastAuthenticationError();
$lastUsername = $authenticationUtils->getLastUsername();
return $this->render('@EasyAdmin/page/login.html.twig', [
// parameters usually defined in Symfony login forms
'error' => $error,
'last_username' => $lastUsername,
// OPTIONAL parameters to customize the login form:
// the translation_domain to use (define this option only if you are
// rendering the login template in a regular Symfony controller; when
// rendering it from an EasyAdmin Dashboard this is automatically set to
// the same domain as the rest of the Dashboard)
'translation_domain' => 'admin',
// the title visible above the login form (define this option only if you are
// rendering the login template in a regular Symfony controller; when rendering
// it from an EasyAdmin Dashboard this is automatically set as the Dashboard title)
'page_title' => 'ACME login',
// the string used to generate the CSRF token. If you don't define
// this parameter, the login form won't include a CSRF token
'csrf_token_intention' => 'authenticate',
// the URL users are redirected to after the login (default: '/admin')
'target_path' => $this->generateUrl('admin_dashboard'),
// the label displayed for the username form field (the |trans filter is applied to it)
'username_label' => 'Your username',
// the label displayed for the password form field (the |trans filter is applied to it)
'password_label' => 'Your password',
// the label displayed for the Sign In form button (the |trans filter is applied to it)
'sign_in_label' => 'Log in',
// the 'name' HTML attribute of the <input> used for the username field (default: '_username')
'username_parameter' => 'my_custom_username_field',
// the 'name' HTML attribute of the <input> used for the password field (default: '_password')
'password_parameter' => 'my_custom_password_field',
// whether to enable or not the "forgot password?" link (default: false)
'forgot_password_enabled' => true,
// the path (i.e. a relative or absolute URL) to visit when clicking the "forgot password?" link (default: '#')
'forgot_password_path' => $this->generateUrl('...', ['...' => '...']),
// the label displayed for the "forgot password?" link (the |trans filter is applied to it)
'forgot_password_label' => 'Forgot your password?',
// whether to enable or not the "remember me" checkbox (default: false)
'remember_me_enabled' => true,
// remember me name form field (default: '_remember_me')
'remember_me_parameter' => 'custom_remember_me_param',
// whether to check by default the "remember me" checkbox (default: false)
'remember_me_checked' => true,
// the label displayed for the remember me checkbox (the |trans filter is applied to it)
'remember_me_label' => 'Remember me',
]);
}
}
内容页面模板
Twig 模板路径:@EasyAdmin/page/content.html.twig
它显示一个类似于索引/详情/表单页面的简单页面,带有主标题、侧边栏菜单和中央内容部分。唯一的区别是内容部分完全为空,因此它对于显示你自己的内容和自定义表单、在 EasyAdmin 中集成 Symfony 操作 等非常有用。示例
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
{# templates/admin/my-custom-page.html.twig #}
{% extends '@EasyAdmin/page/content.html.twig' %}
{% block content_title %}The Title of the Page{% endblock %}
{% block page_actions %}
<a class="btn btn-primary" href="...">Some Action</a>
{% endblock %}
{% block main %}
<table class="datagrid">
<thead>
<tr>
<td>Some Column</td>
<td>Another Column</td>
</tr>
</thead>
<tbody>
{% for data in my_own_data %}
<tr>
<td>{{ data.someColumn }}</td>
<td>{{ data.anotherColumn }}</td>
</tr>
{% endfor %}
</tbody>
</table>
{% endblock %}