步骤 9: 设置管理后台

把将要举办的会议录入到数据库是项目管理员的工作。所谓的 管理后台 是网站中一个受保护的区域,用来让 项目管理员 管理网站数据,处理提交的反馈和做其它一些事。

我们如何快速做一个后台呢?通过用一个 bundle,它可以根据项目的数据模型来生成后台!EasyAdmin 最适合不过了。

配置 EasyAdmin

首先,把 EasyAdmin 加进项目的依赖中。

  1. $ symfony composer req "admin:^3"

EasyAdmin 会基于一些特定的控制器,来为你的应用程序自动生成一个管理后台。新建 src/Controller/Admin/ 目录,我们会在此存放这些控制器。

  1. $ mkdir src/Controller/Admin/

作为使用 EasyAdmin 的第一步,我们来创建一个“网站管理仪表盘”,它将是管理网站数据的主入口。

  1. $ symfony console make:admin:dashboard

接受默认的答案,这会创建如下的控制器:

src/Controller/Admin/DashboardController.php

  1. namespace App\Controller\Admin;
  2. use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
  3. use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
  4. use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
  5. use Symfony\Component\HttpFoundation\Response;
  6. use Symfony\Component\Routing\Annotation\Route;
  7. class DashboardController extends AbstractDashboardController
  8. {
  9. /**
  10. * @Route("/admin", name="admin")
  11. */
  12. public function index(): Response
  13. {
  14. return parent::index();
  15. }
  16. public function configureDashboard(): Dashboard
  17. {
  18. return Dashboard::new()
  19. ->setTitle('Guestbook');
  20. }
  21. public function configureMenuItems(): iterable
  22. {
  23. yield MenuItem::linktoDashboard('Dashboard', 'fa fa-home');
  24. // yield MenuItem::linkToCrud('The Label', 'icon class', EntityClass::class);
  25. }
  26. }

按照约定,所有的管理控制器都放在它们自己的 App\Controller\Admin 命名空间下。

/admin 路径访问这个生成的后台,该路径是由 index() 方法来配置的;你可以把这个 URL 改成任何你想要的形式:

步骤 9: 设置管理后台 - 图1

哇!我们有了一个很好看的管理界面外壳,可以根据我们的需要对它进行定制。

接下去的步骤是创建控制器来管理会议和评论。

在仪表盘的控制器里,你可能已经注意到了 configureMenuItems() 方法,它带有一条有关增加链接到 “CRUD” 类的注释。CRUD 是英文中“增、查、改、删”的首字母缩略词,它们是你想要对任何实体做的 4 个基本操作。这也正是我们希望管理后台为我们去做的;EasyAdmin 甚至更进一步,为我们处理了搜索和过滤。

我们来为会议生成一个 CRUD 类:

  1. $ symfony console make:admin:crud

选择 1 来为会议创建一个管理界面,其余的问题都选择默认答案。这会生成如下文件:

src/Controller/Admin/ConferenceCrudController.php

  1. namespace App\Controller\Admin;
  2. use App\Entity\Conference;
  3. use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
  4. class ConferenceCrudController extends AbstractCrudController
  5. {
  6. public static function getEntityFqcn(): string
  7. {
  8. return Conference::class;
  9. }
  10. /*
  11. public function configureFields(string $pageName): iterable
  12. {
  13. return [
  14. IdField::new('id'),
  15. TextField::new('title'),
  16. TextEditorField::new('description'),
  17. ];
  18. }
  19. */
  20. }

对评论也一样处理:

  1. $ symfony console make:admin:crud

最后一步是把会议和评论的 CRUD 管理类链接到仪表盘:

patch_file

  1. --- a/src/Controller/Admin/DashboardController.php
  2. +++ b/src/Controller/Admin/DashboardController.php
  3. @@ -2,6 +2,8 @@
  4. namespace App\Controller\Admin;
  5. +use App\Entity\Comment;
  6. +use App\Entity\Conference;
  7. use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
  8. use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
  9. use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
  10. @@ -26,7 +28,8 @@ class DashboardController extends AbstractDashboardController
  11. public function configureMenuItems(): iterable
  12. {
  13. - yield MenuItem::linktoDashboard('Dashboard', 'fa fa-home');
  14. - // yield MenuItem::linkToCrud('The Label', 'fas fa-list', EntityClass::class);
  15. + yield MenuItem::linktoRoute('Back to the website', 'fas fa-home', 'homepage');
  16. + yield MenuItem::linkToCrud('Conferences', 'fas fa-map-marker-alt', Conference::class);
  17. + yield MenuItem::linkToCrud('Comments', 'fas fa-comments', Comment::class);
  18. }
  19. }

我们改写了 configureMenuItems() 方法,加了配有相关图标的会议和评论菜单项,也加了一个回到网站首页的链接。

EasyAdmin 通过 MenuItem::linkToRoute() 方法来暴露出一个 API 接口,便于链接到实体对应的 CRUD 类。

主仪表盘页面目前还是空的。该页面可以用来展示一些统计信息,或是任何相关的信息。由于我们没有什么重要的内容要展示,我们把它重定向到会议列表页:

patch_file

  1. --- a/src/Controller/Admin/DashboardController.php
  2. +++ b/src/Controller/Admin/DashboardController.php
  3. @@ -7,6 +7,7 @@ use App\Entity\Conference;
  4. use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
  5. use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
  6. use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
  7. +use EasyCorp\Bundle\EasyAdminBundle\Router\CrudUrlGenerator;
  8. use Symfony\Component\HttpFoundation\Response;
  9. use Symfony\Component\Routing\Annotation\Route;
  10. @@ -17,7 +18,10 @@ class DashboardController extends AbstractDashboardController
  11. */
  12. public function index(): Response
  13. {
  14. - return parent::index();
  15. + $routeBuilder = $this->get(CrudUrlGenerator::class)->build();
  16. + $url = $routeBuilder->setController(ConferenceCrudController::class)->generateUrl();
  17. +
  18. + return $this->redirect($url);
  19. }
  20. public function configureDashboard(): Dashboard

当展示实体间的关系时(和评论关联的一场会议),EasyAdmin 会尝试用会议实体的字符串表示。默认情况下,如果实体没有定义 __toString() 这个“魔术”方法,它的惯例就是使用实体名和主键(比如 Conference #1)。为了使展示的内容更具意义,我们来为 Conference 类定义这个方法:

patch_file

  1. --- a/src/Entity/Conference.php
  2. +++ b/src/Entity/Conference.php
  3. @@ -44,6 +44,11 @@ class Conference
  4. $this->comments = new ArrayCollection();
  5. }
  6. + public function __toString(): string
  7. + {
  8. + return $this->city.' '.$this->year;
  9. + }
  10. +
  11. public function getId(): ?int
  12. {
  13. return $this->id;

Comment 类也做同样的处理:

patch_file

  1. --- a/src/Entity/Comment.php
  2. +++ b/src/Entity/Comment.php
  3. @@ -48,6 +48,11 @@ class Comment
  4. */
  5. private $photoFilename;
  6. + public function __toString(): string
  7. + {
  8. + return (string) $this->getEmail();
  9. + }
  10. +
  11. public function getId(): ?int
  12. {
  13. return $this->id;

现在你可以在管理后台里直接添加/修改/删除会议。你可以玩玩看,并且添加至少一个会议。

步骤 9: 设置管理后台 - 图2

添加几个不带照片的评论。现在先手工设置下日期;在后面的步骤中,我们会让 createdAt 列自动填充。

步骤 9: 设置管理后台 - 图3

定制 EasyAdmin

默认的后台运行得很好,但是我们可以在许多方面对它进行定制,以提高使用体验。我们来对 Comment 实体类做一些简单的改动,以此演示一些可能的定制方式:

patch_file

  1. --- a/src/Controller/Admin/CommentCrudController.php
  2. +++ b/src/Controller/Admin/CommentCrudController.php
  3. @@ -3,7 +3,15 @@
  4. namespace App\Controller\Admin;
  5. use App\Entity\Comment;
  6. +use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
  7. +use EasyCorp\Bundle\EasyAdminBundle\Config\Filters;
  8. use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
  9. +use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
  10. +use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
  11. +use EasyCorp\Bundle\EasyAdminBundle\Field\EmailField;
  12. +use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField;
  13. +use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
  14. +use EasyCorp\Bundle\EasyAdminBundle\Filter\EntityFilter;
  15. class CommentCrudController extends AbstractCrudController
  16. {
  17. @@ -12,14 +20,44 @@ class CommentCrudController extends AbstractCrudController
  18. return Comment::class;
  19. }
  20. - /*
  21. + public function configureCrud(Crud $crud): Crud
  22. + {
  23. + return $crud
  24. + ->setEntityLabelInSingular('Conference Comment')
  25. + ->setEntityLabelInPlural('Conference Comments')
  26. + ->setSearchFields(['author', 'text', 'email'])
  27. + ->setDefaultSort(['createdAt' => 'DESC']);
  28. + ;
  29. + }
  30. +
  31. + public function configureFilters(Filters $filters): Filters
  32. + {
  33. + return $filters
  34. + ->add(EntityFilter::new('conference'))
  35. + ;
  36. + }
  37. +
  38. public function configureFields(string $pageName): iterable
  39. {
  40. - return [
  41. - IdField::new('id'),
  42. - TextField::new('title'),
  43. - TextEditorField::new('description'),
  44. - ];
  45. + yield AssociationField::new('conference');
  46. + yield TextField::new('author');
  47. + yield EmailField::new('email');
  48. + yield TextareaField::new('text')
  49. + ->hideOnIndex()
  50. + ;
  51. + yield TextField::new('photoFilename')
  52. + ->onlyOnIndex()
  53. + ;
  54. +
  55. + $createdAt = DateTimeField::new('createdAt')->setFormTypeOptions([
  56. + 'html5' => true,
  57. + 'years' => range(date('Y'), date('Y') + 5),
  58. + 'widget' => 'single_text',
  59. + ]);
  60. + if (Crud::PAGE_EDIT === $pageName) {
  61. + yield $createdAt->setFormTypeOption('disabled', true);
  62. + } else {
  63. + yield $createdAt;
  64. + }
  65. }
  66. - */
  67. }

为了定制 Comment 这部分,要在 configureFields() 方法中明确列出字段,这让我们能以想要的次序来对它们进行排列。有些字段要更多配置下,比如在列表页隐藏文本字段。

configureFilters() 方法定义了在搜索之外还要暴露哪些过滤器。

步骤 9: 设置管理后台 - 图4

这些改动只是对于 EasyAdmin 定制可能性的一个小小介绍。

在后台里玩玩,比如可以用会议来过滤评论,或者根据邮件来搜索评论。目前唯一的问题是任何人都可以进入后台。别担心,我们会在以后的步骤中将它保护起来。

  1. $ symfony run psql -c "TRUNCATE conference RESTART IDENTITY CASCADE"

深入学习


This work, including the code samples, is licensed under a Creative Commons BY-NC-SA 4.0 license.