easy-admin-bundle
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseEasyAdmin Bundle Skill
EasyAdmin Bundle 技能文档
Build powerful Symfony admin panels quickly with EasyAdminBundle. This skill provides complete examples and patterns for creating production-ready admin interfaces.
Version: This skill covers EasyAdminBundle 4.x (current stable) Requirements: PHP 8.1+, Symfony 5.4/6.x/7.x/8.x, Doctrine ORM Official Docs: https://github.com/EasyCorp/EasyAdminBundle/tree/4.x/doc Repository: https://github.com/EasyCorp/EasyAdminBundle📚 Quick Reference: See API Reference section for complete field types, actions, and CRUD configuration methods
借助EasyAdminBundle快速构建功能强大的Symfony管理面板。本技能文档提供了创建可用于生产环境的管理界面的完整示例和实现模式。
版本说明: 本技能文档基于EasyAdminBundle 4.x版本(当前稳定版) 系统要求: PHP 8.1+、Symfony 5.4/6.x/7.x/8.x、Doctrine ORM 官方文档: https://github.com/EasyCorp/EasyAdminBundle/tree/4.x/doc 代码仓库: https://github.com/EasyCorp/EasyAdminBundle📚 快速参考: 查看API参考章节获取完整的字段类型、操作及CRUD配置方法
Quick Start
快速开始
bash
undefinedbash
undefinedInstall EasyAdmin bundle
Install EasyAdmin bundle
composer require easycorp/easyadmin-bundle
composer require easycorp/easyadmin-bundle
Create a dashboard controller
Create a dashboard controller
php bin/console make:admin:dashboard
php bin/console make:admin:dashboard
Create CRUD controllers for your entities
Create CRUD controllers for your entities
php bin/console make:admin:crud
undefinedphp bin/console make:admin:crud
undefinedComplete Dashboard Setup
完整仪表盘设置
Basic Dashboard Controller
基础仪表盘控制器
php
// src/Controller/Admin/DashboardController.php
namespace App\Controller\Admin;
use App\Entity\User;
use App\Entity\Post;
use App\Entity\Category;
use App\Entity\Comment;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DashboardController extends AbstractDashboardController
{
#[Route('/admin', name: 'admin')]
public function index(): Response
{
return $this->render('admin/dashboard.html.twig');
}
public function configureDashboard(): Dashboard
{
return Dashboard::new()
->setTitle('My Admin Panel')
->setFaviconPath('favicon.ico')
->setTranslationDomain('admin')
->setTextDirection('ltr')
->renderContentMaximized()
->renderSidebarMinimized()
->disableDarkMode();
}
public function configureMenuItems(): iterable
{
yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
yield MenuItem::section('Content');
yield MenuItem::linkToCrud('Posts', 'fa fa-newspaper', Post::class);
yield MenuItem::linkToCrud('Categories', 'fa fa-tags', Category::class);
yield MenuItem::linkToCrud('Comments', 'fa fa-comments', Comment::class);
yield MenuItem::section('Users');
yield MenuItem::linkToCrud('Users', 'fa fa-user', User::class);
yield MenuItem::section('External');
yield MenuItem::linkToUrl('Homepage', 'fa fa-globe', '/');
yield MenuItem::linkToLogout('Logout', 'fa fa-sign-out');
}
}php
// src/Controller/Admin/DashboardController.php
namespace App\Controller\Admin;
use App\Entity\User;
use App\Entity\Post;
use App\Entity\Category;
use App\Entity\Comment;
use EasyCorp\Bundle\EasyAdminBundle\Config\Dashboard;
use EasyCorp\Bundle\EasyAdminBundle\Config\MenuItem;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractDashboardController;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\Routing\Annotation\Route;
class DashboardController extends AbstractDashboardController
{
#[Route('/admin', name: 'admin')]
public function index(): Response
{
return $this->render('admin/dashboard.html.twig');
}
public function configureDashboard(): Dashboard
{
return Dashboard::new()
->setTitle('My Admin Panel')
->setFaviconPath('favicon.ico')
->setTranslationDomain('admin')
->setTextDirection('ltr')
->renderContentMaximized()
->renderSidebarMinimized()
->disableDarkMode();
}
public function configureMenuItems(): iterable
{
yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
yield MenuItem::section('Content');
yield MenuItem::linkToCrud('Posts', 'fa fa-newspaper', Post::class);
yield MenuItem::linkToCrud('Categories', 'fa fa-tags', Category::class);
yield MenuItem::linkToCrud('Comments', 'fa fa-comments', Comment::class);
yield MenuItem::section('Users');
yield MenuItem::linkToCrud('Users', 'fa fa-user', User::class);
yield MenuItem::section('External');
yield MenuItem::linkToUrl('Homepage', 'fa fa-globe', '/');
yield MenuItem::linkToLogout('Logout', 'fa fa-sign-out');
}
}Dashboard with Submenus
带子菜单的仪表盘
php
public function configureMenuItems(): iterable
{
yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
// Submenu example
yield MenuItem::subMenu('Content', 'fa fa-file')->setSubItems([
MenuItem::linkToCrud('Posts', 'fa fa-newspaper', Post::class),
MenuItem::linkToCrud('Pages', 'fa fa-file-text', Page::class),
MenuItem::linkToCrud('Media', 'fa fa-images', Media::class),
]);
yield MenuItem::subMenu('E-commerce', 'fa fa-shopping-cart')->setSubItems([
MenuItem::linkToCrud('Products', 'fa fa-box', Product::class),
MenuItem::linkToCrud('Orders', 'fa fa-shopping-bag', Order::class),
MenuItem::linkToCrud('Customers', 'fa fa-users', Customer::class),
]);
}php
public function configureMenuItems(): iterable
{
yield MenuItem::linkToDashboard('Dashboard', 'fa fa-home');
// Submenu example
yield MenuItem::subMenu('Content', 'fa fa-file')->setSubItems([
MenuItem::linkToCrud('Posts', 'fa fa-newspaper', Post::class),
MenuItem::linkToCrud('Pages', 'fa fa-file-text', Page::class),
MenuItem::linkToCrud('Media', 'fa fa-images', Media::class),
]);
yield MenuItem::subMenu('E-commerce', 'fa fa-shopping-cart')->setSubItems([
MenuItem::linkToCrud('Products', 'fa fa-box', Product::class),
MenuItem::linkToCrud('Orders', 'fa fa-shopping-bag', Order::class),
MenuItem::linkToCrud('Customers', 'fa fa-users', Customer::class),
]);
}Field Types Reference
字段类型参考
Basic Fields
基础字段
php
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField;
use EasyCorp\Bundle\EasyAdminBundle\Field\NumberField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
use EasyCorp\Bundle\EasyAdminBundle\Field\EmailField;
use EasyCorp\Bundle\EasyAdminBundle\Field\UrlField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TelephoneField;
public function configureFields(string $pageName): iterable
{
yield TextField::new('title')->setMaxLength(255);
yield TextEditorField::new('content'); // Rich text editor
yield TextareaField::new('description')->setMaxLength(500);
yield NumberField::new('price')->setNumDecimals(2);
yield IntegerField::new('quantity');
yield EmailField::new('email');
yield UrlField::new('website');
yield TelephoneField::new('phone');
}php
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextareaField;
use EasyCorp\Bundle\EasyAdminBundle\Field\NumberField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
use EasyCorp\Bundle\EasyAdminBundle\Field\EmailField;
use EasyCorp\Bundle\EasyAdminBundle\Field\UrlField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TelephoneField;
public function configureFields(string $pageName): iterable
{
yield TextField::new('title')->setMaxLength(255);
yield TextEditorField::new('content'); // Rich text editor
yield TextareaField::new('description')->setMaxLength(500);
yield NumberField::new('price')->setNumDecimals(2);
yield IntegerField::new('quantity');
yield EmailField::new('email');
yield UrlField::new('website');
yield TelephoneField::new('phone');
}Date and Time Fields
日期与时间字段
php
use EasyCorp\Bundle\EasyAdminBundle\Field\DateField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TimeField;
public function configureFields(string $pageName): iterable
{
yield DateField::new('publishedAt')
->setFormat('dd/MM/yyyy');
yield DateTimeField::new('createdAt')
->setFormat('dd/MM/yyyy HH:mm')
->hideOnForm();
yield TimeField::new('openingTime')
->setFormat('HH:mm');
}php
use EasyCorp\Bundle\EasyAdminBundle\Field\DateField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TimeField;
public function configureFields(string $pageName): iterable
{
yield DateField::new('publishedAt')
->setFormat('dd/MM/yyyy');
yield DateTimeField::new('createdAt')
->setFormat('dd/MM/yyyy HH:mm')
->hideOnForm();
yield TimeField::new('openingTime')
->setFormat('HH:mm');
}Boolean and Choice Fields
布尔与选择字段
php
use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField;
public function configureFields(string $pageName): iterable
{
yield BooleanField::new('isPublished')
->renderAsSwitch();
yield ChoiceField::new('status')
->setChoices([
'Draft' => 'draft',
'Published' => 'published',
'Archived' => 'archived',
])
->renderAsBadges([
'draft' => 'warning',
'published' => 'success',
'archived' => 'secondary',
]);
yield ChoiceField::new('roles')
->setChoices([
'User' => 'ROLE_USER',
'Editor' => 'ROLE_EDITOR',
'Admin' => 'ROLE_ADMIN',
])
->allowMultipleChoices()
->renderExpanded(); // Show as checkboxes
}php
use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField;
public function configureFields(string $pageName): iterable
{
yield BooleanField::new('isPublished')
->renderAsSwitch();
yield ChoiceField::new('status')
->setChoices([
'Draft' => 'draft',
'Published' => 'published',
'Archived' => 'archived',
])
->renderAsBadges([
'draft' => 'warning',
'published' => 'success',
'archived' => 'secondary',
]);
yield ChoiceField::new('roles')
->setChoices([
'User' => 'ROLE_USER',
'Editor' => 'ROLE_EDITOR',
'Admin' => 'ROLE_ADMIN',
])
->allowMultipleChoices()
->renderExpanded(); // Show as checkboxes
}Association Fields
关联字段
php
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
public function configureFields(string $pageName): iterable
{
// Many-to-One
yield AssociationField::new('category')
->setRequired(true);
// One-to-Many / Many-to-Many
yield AssociationField::new('tags')
->setFormTypeOptions([
'by_reference' => false,
])
->autocomplete(); // Ajax autocomplete for large datasets
// Custom query for association
yield AssociationField::new('author')
->setQueryBuilder(
fn (QueryBuilder $qb) => $qb
->andWhere('entity.isActive = :active')
->setParameter('active', true)
);
}php
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
public function configureFields(string $pageName): iterable
{
// Many-to-One
yield AssociationField::new('category')
->setRequired(true);
// One-to-Many / Many-to-Many
yield AssociationField::new('tags')
->setFormTypeOptions([
'by_reference' => false,
])
->autocomplete(); // Ajax autocomplete for large datasets
// Custom query for association
yield AssociationField::new('author')
->setQueryBuilder(
fn (QueryBuilder $qb) => $qb
->andWhere('entity.isActive = :active')
->setParameter('active', true)
);
}File and Image Fields
文件与图片字段
php
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\FileField;
public function configureFields(string $pageName): iterable
{
yield ImageField::new('thumbnail')
->setBasePath('uploads/images')
->setUploadDir('public/uploads/images')
->setUploadedFileNamePattern('[randomhash].[extension]')
->setRequired(false);
yield ImageField::new('gallery')
->setBasePath('uploads/gallery')
->setUploadDir('public/uploads/gallery')
->setUploadedFileNamePattern('[slug]-[timestamp].[extension]')
->setFormTypeOptions(['multiple' => true]);
yield FileField::new('attachment')
->setBasePath('uploads/files')
->setUploadDir('public/uploads/files');
}php
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\FileField;
public function configureFields(string $pageName): iterable
{
yield ImageField::new('thumbnail')
->setBasePath('uploads/images')
->setUploadDir('public/uploads/images')
->setUploadedFileNamePattern('[randomhash].[extension]')
->setRequired(false);
yield ImageField::new('gallery')
->setBasePath('uploads/gallery')
->setUploadDir('public/uploads/gallery')
->setUploadedFileNamePattern('[slug]-[timestamp].[extension]')
->setFormTypeOptions(['multiple' => true]);
yield FileField::new('attachment')
->setBasePath('uploads/files')
->setUploadDir('public/uploads/files');
}Money and Percent Fields
货币与百分比字段
php
use EasyCorp\Bundle\EasyAdminBundle\Field\MoneyField;
use EasyCorp\Bundle\EasyAdminBundle\Field\PercentField;
public function configureFields(string $pageName): iterable
{
yield MoneyField::new('price')
->setCurrency('USD')
->setStoredAsCents(false);
yield MoneyField::new('priceInCents')
->setCurrency('EUR')
->setStoredAsCents(true);
yield PercentField::new('discount')
->setNumDecimals(2);
}php
use EasyCorp\Bundle\EasyAdminBundle\Field\MoneyField;
use EasyCorp\Bundle\EasyAdminBundle\Field\PercentField;
public function configureFields(string $pageName): iterable
{
yield MoneyField::new('price')
->setCurrency('USD')
->setStoredAsCents(false);
yield MoneyField::new('priceInCents')
->setCurrency('EUR')
->setStoredAsCents(true);
yield PercentField::new('discount')
->setNumDecimals(2);
}Other Useful Fields
其他实用字段
php
use EasyCorp\Bundle\EasyAdminBundle\Field\SlugField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ColorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\CountryField;
use EasyCorp\Bundle\EasyAdminBundle\Field\LanguageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\LocaleField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TimezoneField;
use EasyCorp\Bundle\EasyAdminBundle\Field\CodeEditorField;
public function configureFields(string $pageName): iterable
{
yield SlugField::new('slug')
->setTargetFieldName('title');
yield ColorField::new('brandColor');
yield CountryField::new('country');
yield LanguageField::new('language');
yield LocaleField::new('locale');
yield TimezoneField::new('timezone');
yield CodeEditorField::new('customCss')
->setLanguage('css')
->setNumOfRows(10);
}php
use EasyCorp\Bundle\EasyAdminBundle\Field\SlugField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ColorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\CountryField;
use EasyCorp\Bundle\EasyAdminBundle\Field\LanguageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\LocaleField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TimezoneField;
use EasyCorp\Bundle\EasyAdminBundle\Field\CodeEditorField;
public function configureFields(string $pageName): iterable
{
yield SlugField::new('slug')
->setTargetFieldName('title');
yield ColorField::new('brandColor');
yield CountryField::new('country');
yield LanguageField::new('language');
yield LocaleField::new('locale');
yield TimezoneField::new('timezone');
yield CodeEditorField::new('customCss')
->setLanguage('css')
->setNumOfRows(10);
}Complete CRUD Controller Examples
完整CRUD控制器示例
User CRUD with All Features
包含全部功能的用户CRUD
php
namespace App\Controller\Admin;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Config\Filters;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\ArrayField;
use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\EmailField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Filter\BooleanFilter;
use EasyCorp\Bundle\EasyAdminBundle\Filter\DateTimeFilter;
class UserCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return User::class;
}
public function configureCrud(Crud $crud): Crud
{
return $crud
->setEntityLabelInSingular('User')
->setEntityLabelInPlural('Users')
->setPageTitle('index', 'User Management')
->setPageTitle('new', 'Create User')
->setPageTitle('edit', 'Edit %entity_label_singular%')
->setSearchFields(['username', 'email', 'firstName', 'lastName'])
->setDefaultSort(['createdAt' => 'DESC'])
->setPaginatorPageSize(30)
->setEntityPermission('ROLE_ADMIN');
}
public function configureFields(string $pageName): iterable
{
yield IdField::new('id')->hideOnForm();
yield TextField::new('username')
->setColumns(6);
yield EmailField::new('email')
->setColumns(6);
yield TextField::new('firstName')
->setColumns(6)
->hideOnIndex();
yield TextField::new('lastName')
->setColumns(6)
->hideOnIndex();
yield ImageField::new('avatar')
->setBasePath('uploads/avatars')
->setUploadDir('public/uploads/avatars')
->setUploadedFileNamePattern('[slug]-[timestamp].[extension]')
->hideOnIndex();
yield ArrayField::new('roles')
->hideOnIndex();
yield BooleanField::new('isVerified')
->renderAsSwitch(false);
yield BooleanField::new('isActive')
->renderAsSwitch(false);
yield DateTimeField::new('createdAt')
->hideOnForm();
yield DateTimeField::new('lastLoginAt')
->hideOnForm()
->hideOnIndex();
}
public function configureFilters(Filters $filters): Filters
{
return $filters
->add('username')
->add('email')
->add(BooleanFilter::new('isActive'))
->add(BooleanFilter::new('isVerified'))
->add(DateTimeFilter::new('createdAt'))
->add(DateTimeFilter::new('lastLoginAt'));
}
public function configureActions(Actions $actions): Actions
{
$impersonate = Action::new('impersonate', 'Impersonate')
->linkToCrudAction('impersonate')
->setCssClass('btn btn-warning')
->displayIf(fn (User $user) => $this->isGranted('ROLE_SUPER_ADMIN'));
return $actions
->add(Crud::PAGE_INDEX, Action::DETAIL)
->add(Crud::PAGE_INDEX, $impersonate)
->update(Crud::PAGE_INDEX, Action::DELETE, function (Action $action) {
return $action->displayIf(fn (User $user) => $user->getId() !== $this->getUser()->getId());
});
}
}php
namespace App\Controller\Admin;
use App\Entity\User;
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Config\Filters;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\ArrayField;
use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\EmailField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
use EasyCorp\Bundle\EasyAdminBundle\Filter\BooleanFilter;
use EasyCorp\Bundle\EasyAdminBundle\Filter\DateTimeFilter;
class UserCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return User::class;
}
public function configureCrud(Crud $crud): Crud
{
return $crud
->setEntityLabelInSingular('User')
->setEntityLabelInPlural('Users')
->setPageTitle('index', 'User Management')
->setPageTitle('new', 'Create User')
->setPageTitle('edit', 'Edit %entity_label_singular%')
->setSearchFields(['username', 'email', 'firstName', 'lastName'])
->setDefaultSort(['createdAt' => 'DESC'])
->setPaginatorPageSize(30)
->setEntityPermission('ROLE_ADMIN');
}
public function configureFields(string $pageName): iterable
{
yield IdField::new('id')->hideOnForm();
yield TextField::new('username')
->setColumns(6);
yield EmailField::new('email')
->setColumns(6);
yield TextField::new('firstName')
->setColumns(6)
->hideOnIndex();
yield TextField::new('lastName')
->setColumns(6)
->hideOnIndex();
yield ImageField::new('avatar')
->setBasePath('uploads/avatars')
->setUploadDir('public/uploads/avatars')
->setUploadedFileNamePattern('[slug]-[timestamp].[extension]')
->hideOnIndex();
yield ArrayField::new('roles')
->hideOnIndex();
yield BooleanField::new('isVerified')
->renderAsSwitch(false);
yield BooleanField::new('isActive')
->renderAsSwitch(false);
yield DateTimeField::new('createdAt')
->hideOnForm();
yield DateTimeField::new('lastLoginAt')
->hideOnForm()
->hideOnIndex();
}
public function configureFilters(Filters $filters): Filters
{
return $filters
->add('username')
->add('email')
->add(BooleanFilter::new('isActive'))
->add(BooleanFilter::new('isVerified'))
->add(DateTimeFilter::new('createdAt'))
->add(DateTimeFilter::new('lastLoginAt'));
}
public function configureActions(Actions $actions): Actions
{
$impersonate = Action::new('impersonate', 'Impersonate')
->linkToCrudAction('impersonate')
->setCssClass('btn btn-warning')
->displayIf(fn (User $user) => $this->isGranted('ROLE_SUPER_ADMIN'));
return $actions
->add(Crud::PAGE_INDEX, Action::DETAIL)
->add(Crud::PAGE_INDEX, $impersonate)
->update(Crud::PAGE_INDEX, Action::DELETE, function (Action $action) {
return $action->displayIf(fn (User $user) => $user->getId() !== $this->getUser()->getId());
});
}
}Product CRUD with Categories and Images
包含分类与图片的产品CRUD
php
namespace App\Controller\Admin;
use App\Entity\Product;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
use EasyCorp\Bundle\EasyAdminBundle\Field\MoneyField;
use EasyCorp\Bundle\EasyAdminBundle\Field\SlugField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
class ProductCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Product::class;
}
public function configureCrud(Crud $crud): Crud
{
return $crud
->setEntityLabelInSingular('Product')
->setEntityLabelInPlural('Products')
->setSearchFields(['name', 'sku', 'description'])
->setDefaultSort(['createdAt' => 'DESC']);
}
public function configureFields(string $pageName): iterable
{
yield IdField::new('id')->onlyOnIndex();
yield TextField::new('name')
->setColumns(8);
yield SlugField::new('slug')
->setTargetFieldName('name')
->setColumns(4)
->hideOnIndex();
yield TextField::new('sku')
->setColumns(6);
yield MoneyField::new('price')
->setCurrency('USD')
->setColumns(6);
yield IntegerField::new('stock')
->setColumns(6);
yield AssociationField::new('category')
->setColumns(6)
->autocomplete();
yield AssociationField::new('tags')
->hideOnIndex()
->autocomplete();
yield TextEditorField::new('description')
->hideOnIndex();
yield ImageField::new('image')
->setBasePath('uploads/products')
->setUploadDir('public/uploads/products')
->setUploadedFileNamePattern('[slug]-[timestamp].[extension]');
yield BooleanField::new('isFeatured')
->renderAsSwitch(false);
yield BooleanField::new('isActive')
->renderAsSwitch(false);
yield DateTimeField::new('createdAt')
->hideOnForm();
}
}php
namespace App\Controller\Admin;
use App\Entity\Product;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Field\BooleanField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IntegerField;
use EasyCorp\Bundle\EasyAdminBundle\Field\MoneyField;
use EasyCorp\Bundle\EasyAdminBundle\Field\SlugField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
class ProductCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Product::class;
}
public function configureCrud(Crud $crud): Crud
{
return $crud
->setEntityLabelInSingular('Product')
->setEntityLabelInPlural('Products')
->setSearchFields(['name', 'sku', 'description'])
->setDefaultSort(['createdAt' => 'DESC']);
}
public function configureFields(string $pageName): iterable
{
yield IdField::new('id')->onlyOnIndex();
yield TextField::new('name')
->setColumns(8);
yield SlugField::new('slug')
->setTargetFieldName('name')
->setColumns(4)
->hideOnIndex();
yield TextField::new('sku')
->setColumns(6);
yield MoneyField::new('price')
->setCurrency('USD')
->setColumns(6);
yield IntegerField::new('stock')
->setColumns(6);
yield AssociationField::new('category')
->setColumns(6)
->autocomplete();
yield AssociationField::new('tags')
->hideOnIndex()
->autocomplete();
yield TextEditorField::new('description')
->hideOnIndex();
yield ImageField::new('image')
->setBasePath('uploads/products')
->setUploadDir('public/uploads/products')
->setUploadedFileNamePattern('[slug]-[timestamp].[extension]');
yield BooleanField::new('isFeatured')
->renderAsSwitch(false);
yield BooleanField::new('isActive')
->renderAsSwitch(false);
yield DateTimeField::new('createdAt')
->hideOnForm();
}
}Blog Post CRUD with Rich Content
包含富文本内容的博客文章CRUD
php
namespace App\Controller\Admin;
use App\Entity\Post;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\SlugField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
class PostCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Post::class;
}
public function configureCrud(Crud $crud): Crud
{
return $crud
->setEntityLabelInSingular('Post')
->setEntityLabelInPlural('Blog Posts')
->setSearchFields(['title', 'content', 'excerpt'])
->setDefaultSort(['publishedAt' => 'DESC']);
}
public function configureFields(string $pageName): iterable
{
yield IdField::new('id')->onlyOnIndex();
yield TextField::new('title');
yield SlugField::new('slug')
->setTargetFieldName('title')
->hideOnIndex();
yield TextField::new('excerpt')
->setMaxLength(200)
->hideOnIndex();
yield TextEditorField::new('content')
->hideOnIndex();
yield ImageField::new('featuredImage')
->setBasePath('uploads/posts')
->setUploadDir('public/uploads/posts');
yield AssociationField::new('author')
->autocomplete();
yield AssociationField::new('category');
yield AssociationField::new('tags')
->hideOnIndex()
->autocomplete();
yield ChoiceField::new('status')
->setChoices([
'Draft' => 'draft',
'Published' => 'published',
'Archived' => 'archived',
])
->renderAsBadges([
'draft' => 'warning',
'published' => 'success',
'archived' => 'secondary',
]);
yield DateTimeField::new('publishedAt')
->hideOnIndex();
yield DateTimeField::new('createdAt')
->hideOnForm();
yield DateTimeField::new('updatedAt')
->hideOnForm()
->hideOnIndex();
}
}php
namespace App\Controller\Admin;
use App\Entity\Post;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use EasyCorp\Bundle\EasyAdminBundle\Controller\AbstractCrudController;
use EasyCorp\Bundle\EasyAdminBundle\Field\AssociationField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ChoiceField;
use EasyCorp\Bundle\EasyAdminBundle\Field\DateTimeField;
use EasyCorp\Bundle\EasyAdminBundle\Field\IdField;
use EasyCorp\Bundle\EasyAdminBundle\Field\ImageField;
use EasyCorp\Bundle\EasyAdminBundle\Field\SlugField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextEditorField;
use EasyCorp\Bundle\EasyAdminBundle\Field\TextField;
class PostCrudController extends AbstractCrudController
{
public static function getEntityFqcn(): string
{
return Post::class;
}
public function configureCrud(Crud $crud): Crud
{
return $crud
->setEntityLabelInSingular('Post')
->setEntityLabelInPlural('Blog Posts')
->setSearchFields(['title', 'content', 'excerpt'])
->setDefaultSort(['publishedAt' => 'DESC']);
}
public function configureFields(string $pageName): iterable
{
yield IdField::new('id')->onlyOnIndex();
yield TextField::new('title');
yield SlugField::new('slug')
->setTargetFieldName('title')
->hideOnIndex();
yield TextField::new('excerpt')
->setMaxLength(200)
->hideOnIndex();
yield TextEditorField::new('content')
->hideOnIndex();
yield ImageField::new('featuredImage')
->setBasePath('uploads/posts')
->setUploadDir('public/uploads/posts');
yield AssociationField::new('author')
->autocomplete();
yield AssociationField::new('category');
yield AssociationField::new('tags')
->hideOnIndex()
->autocomplete();
yield ChoiceField::new('status')
->setChoices([
'Draft' => 'draft',
'Published' => 'published',
'Archived' => 'archived',
])
->renderAsBadges([
'draft' => 'warning',
'published' => 'success',
'archived' => 'secondary',
]);
yield DateTimeField::new('publishedAt')
->hideOnIndex();
yield DateTimeField::new('createdAt')
->hideOnForm();
yield DateTimeField::new('updatedAt')
->hideOnForm()
->hideOnIndex();
}
}Custom Actions
自定义操作
Add Custom Action to CRUD
为CRUD添加自定义操作
php
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use Symfony\Component\HttpFoundation\Response;
class ProductCrudController extends AbstractCrudController
{
public function configureActions(Actions $actions): Actions
{
// Create custom action
$exportAction = Action::new('export', 'Export CSV')
->linkToCrudAction('exportCsv')
->setCssClass('btn btn-success')
->createAsGlobalAction(); // Shows in index page header
$duplicateAction = Action::new('duplicate', 'Duplicate')
->linkToCrudAction('duplicateProduct')
->setCssClass('btn btn-info');
return $actions
->add(Crud::PAGE_INDEX, $exportAction)
->add(Crud::PAGE_DETAIL, $duplicateAction)
->add(Crud::PAGE_INDEX, Action::DETAIL)
->reorder(Crud::PAGE_INDEX, [Action::DETAIL, Action::EDIT, 'duplicate', Action::DELETE]);
}
public function exportCsv(): Response
{
$products = $this->entityManager->getRepository(Product::class)->findAll();
// Generate CSV content
$csv = "ID,Name,SKU,Price\n";
foreach ($products as $product) {
$csv .= sprintf("%d,%s,%s,%.2f\n",
$product->getId(),
$product->getName(),
$product->getSku(),
$product->getPrice()
);
}
return new Response($csv, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="products.csv"',
]);
}
public function duplicateProduct(AdminContext $context): Response
{
$product = $context->getEntity()->getInstance();
$newProduct = clone $product;
$newProduct->setName($product->getName() . ' (Copy)');
$newProduct->setSku($product->getSku() . '-copy');
$this->entityManager->persist($newProduct);
$this->entityManager->flush();
$this->addFlash('success', 'Product duplicated successfully');
return $this->redirect($context->getReferrer());
}
}php
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
use Symfony\Component\HttpFoundation\Response;
class ProductCrudController extends AbstractCrudController
{
public function configureActions(Actions $actions): Actions
{
// Create custom action
$exportAction = Action::new('export', 'Export CSV')
->linkToCrudAction('exportCsv')
->setCssClass('btn btn-success')
->createAsGlobalAction(); // Shows in index page header
$duplicateAction = Action::new('duplicate', 'Duplicate')
->linkToCrudAction('duplicateProduct')
->setCssClass('btn btn-info');
return $actions
->add(Crud::PAGE_INDEX, $exportAction)
->add(Crud::PAGE_DETAIL, $duplicateAction)
->add(Crud::PAGE_INDEX, Action::DETAIL)
->reorder(Crud::PAGE_INDEX, [Action::DETAIL, Action::EDIT, 'duplicate', Action::DELETE]);
}
public function exportCsv(): Response
{
$products = $this->entityManager->getRepository(Product::class)->findAll();
// Generate CSV content
$csv = "ID,Name,SKU,Price\n";
foreach ($products as $product) {
$csv .= sprintf("%d,%s,%s,%.2f\n",
$product->getId(),
$product->getName(),
$product->getSku(),
$product->getPrice()
);
}
return new Response($csv, 200, [
'Content-Type' => 'text/csv',
'Content-Disposition' => 'attachment; filename="products.csv"',
]);
}
public function duplicateProduct(AdminContext $context): Response
{
$product = $context->getEntity()->getInstance();
$newProduct = clone $product;
$newProduct->setName($product->getName() . ' (Copy)');
$newProduct->setSku($product->getSku() . '-copy');
$this->entityManager->persist($newProduct);
$this->entityManager->flush();
$this->addFlash('success', 'Product duplicated successfully');
return $this->redirect($context->getReferrer());
}
}Batch Actions
批量操作
php
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
public function configureActions(Actions $actions): Actions
{
$batchPublish = Action::new('batchPublish', 'Publish selected')
->linkToCrudAction('batchPublish')
->addCssClass('btn btn-success')
->setIcon('fa fa-check');
return $actions
->addBatchAction($batchPublish);
}
public function batchPublish(BatchActionDto $batchActionDto): Response
{
$entityManager = $this->entityManager;
foreach ($batchActionDto->getEntityIds() as $id) {
$post = $entityManager->find(Post::class, $id);
if ($post) {
$post->setStatus('published');
$post->setPublishedAt(new \DateTime());
}
}
$entityManager->flush();
$this->addFlash('success', sprintf('Published %d posts', count($batchActionDto->getEntityIds())));
return $this->redirect($batchActionDto->getReferrerUrl());
}php
use EasyCorp\Bundle\EasyAdminBundle\Config\Action;
use EasyCorp\Bundle\EasyAdminBundle\Config\Actions;
public function configureActions(Actions $actions): Actions
{
$batchPublish = Action::new('batchPublish', 'Publish selected')
->linkToCrudAction('batchPublish')
->addCssClass('btn btn-success')
->setIcon('fa fa-check');
return $actions
->addBatchAction($batchPublish);
}
public function batchPublish(BatchActionDto $batchActionDto): Response
{
$entityManager = $this->entityManager;
foreach ($batchActionDto->getEntityIds() as $id) {
$post = $entityManager->find(Post::class, $id);
if ($post) {
$post->setStatus('published');
$post->setPublishedAt(new \DateTime());
}
}
$entityManager->flush();
$this->addFlash('success', sprintf('Published %d posts', count($batchActionDto->getEntityIds())));
return $this->redirect($batchActionDto->getReferrerUrl());
}Advanced Configurations
高级配置
Conditional Field Display
条件字段显示
php
public function configureFields(string $pageName): iterable
{
yield TextField::new('title');
// Show only on index page
yield TextField::new('summary')->onlyOnIndex();
// Show only on forms (new/edit)
yield TextEditorField::new('content')->onlyOnForms();
// Show only on detail page
yield TextField::new('fullDetails')->onlyOnDetail();
// Hide on specific pages
yield DateTimeField::new('createdAt')
->hideOnForm()
->hideOnIndex();
// Conditional display based on context
if (Crud::PAGE_EDIT === $pageName) {
yield DateTimeField::new('updatedAt')->setFormTypeOptions(['disabled' => true]);
}
}php
public function configureFields(string $pageName): iterable
{
yield TextField::new('title');
// Show only on index page
yield TextField::new('summary')->onlyOnIndex();
// Show only on forms (new/edit)
yield TextEditorField::new('content')->onlyOnForms();
// Show only on detail page
yield TextField::new('fullDetails')->onlyOnDetail();
// Hide on specific pages
yield DateTimeField::new('createdAt')
->hideOnForm()
->hideOnIndex();
// Conditional display based on context
if (Crud::PAGE_EDIT === $pageName) {
yield DateTimeField::new('updatedAt')->setFormTypeOptions(['disabled' => true]);
}
}Custom Form Layouts
自定义表单布局
php
use EasyCorp\Bundle\EasyAdminBundle\Field\FormField;
public function configureFields(string $pageName): iterable
{
yield FormField::addPanel('Basic Information');
yield TextField::new('name');
yield TextField::new('sku');
yield FormField::addPanel('Pricing')->setIcon('fa fa-dollar-sign');
yield MoneyField::new('price')->setCurrency('USD');
yield MoneyField::new('costPrice')->setCurrency('USD');
yield PercentField::new('taxRate');
yield FormField::addPanel('Inventory');
yield IntegerField::new('stock');
yield IntegerField::new('minStock');
yield BooleanField::new('trackInventory');
yield FormField::addPanel('SEO')->setHelp('Search engine optimization settings');
yield TextField::new('metaTitle');
yield TextareaField::new('metaDescription');
}php
use EasyCorp\Bundle\EasyAdminBundle\Field\FormField;
public function configureFields(string $pageName): iterable
{
yield FormField::addPanel('Basic Information');
yield TextField::new('name');
yield TextField::new('sku');
yield FormField::addPanel('Pricing')->setIcon('fa fa-dollar-sign');
yield MoneyField::new('price')->setCurrency('USD');
yield MoneyField::new('costPrice')->setCurrency('USD');
yield PercentField::new('taxRate');
yield FormField::addPanel('Inventory');
yield IntegerField::new('stock');
yield IntegerField::new('minStock');
yield BooleanField::new('trackInventory');
yield FormField::addPanel('SEO')->setHelp('Search engine optimization settings');
yield TextField::new('metaTitle');
yield TextareaField::new('metaDescription');
}Security and Permissions
安全与权限
php
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
public function configureCrud(Crud $crud): Crud
{
return $crud
->setEntityPermission('ROLE_ADMIN') // Required role for all operations
->setEntityLabelInSingular('Product')
->setEntityLabelInPlural('Products');
}
public function configureActions(Actions $actions): Actions
{
return $actions
// Only super admins can delete
->setPermission(Action::DELETE, 'ROLE_SUPER_ADMIN')
// Conditional permissions
->update(Crud::PAGE_INDEX, Action::EDIT, function (Action $action) {
return $action->displayIf(function (Product $product) {
return $this->isGranted('ROLE_EDITOR') || $product->getAuthor() === $this->getUser();
});
});
}php
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud;
public function configureCrud(Crud $crud): Crud
{
return $crud
->setEntityPermission('ROLE_ADMIN') // Required role for all operations
->setEntityLabelInSingular('Product')
->setEntityLabelInPlural('Products');
}
public function configureActions(Actions $actions): Actions
{
return $actions
// Only super admins can delete
->setPermission(Action::DELETE, 'ROLE_SUPER_ADMIN')
// Conditional permissions
->update(Crud::PAGE_INDEX, Action::EDIT, function (Action $action) {
return $action->displayIf(function (Product $product) {
return $this->isGranted('ROLE_EDITOR') || $product->getAuthor() === $this->getUser();
});
});
}Custom Queries and Filters
自定义查询与过滤器
php
use Doctrine\ORM\QueryBuilder;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FilterCollection;
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\SearchDto;
public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder
{
$qb = parent::createIndexQueryBuilder($searchDto, $entityDto, $fields, $filters);
// Only show active products
$qb->andWhere('entity.isActive = :active')
->setParameter('active', true);
// Only show products from current user's store
if (!$this->isGranted('ROLE_ADMIN')) {
$qb->andWhere('entity.store = :store')
->setParameter('store', $this->getUser()->getStore());
}
return $qb;
}php
use Doctrine\ORM\QueryBuilder;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FieldCollection;
use EasyCorp\Bundle\EasyAdminBundle\Collection\FilterCollection;
use EasyCorp\Bundle\EasyAdminBundle\Dto\EntityDto;
use EasyCorp\Bundle\EasyAdminBundle\Dto\SearchDto;
public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder
{
$qb = parent::createIndexQueryBuilder($searchDto, $entityDto, $fields, $filters);
// Only show active products
$qb->andWhere('entity.isActive = :active')
->setParameter('active', true);
// Only show products from current user's store
if (!$this->isGranted('ROLE_ADMIN')) {
$qb->andWhere('entity.store = :store')
->setParameter('store', $this->getUser()->getStore());
}
return $qb;
}Event Listeners and Hooks
事件监听器与钩子
Modify Entity Before Persist
持久化前修改实体
php
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityPersistedEvent;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityUpdatedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ProductSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
BeforeEntityPersistedEvent::class => ['setCreatedAt'],
BeforeEntityUpdatedEvent::class => ['setUpdatedAt'],
];
}
public function setCreatedAt(BeforeEntityPersistedEvent $event): void
{
$entity = $event->getEntityInstance();
if ($entity instanceof Product) {
$entity->setCreatedAt(new \DateTime());
}
}
public function setUpdatedAt(BeforeEntityUpdatedEvent $event): void
{
$entity = $event->getEntityInstance();
if ($entity instanceof Product) {
$entity->setUpdatedAt(new \DateTime());
}
}
}php
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityPersistedEvent;
use EasyCorp\Bundle\EasyAdminBundle\Event\BeforeEntityUpdatedEvent;
use Symfony\Component\EventDispatcher\EventSubscriberInterface;
class ProductSubscriber implements EventSubscriberInterface
{
public static function getSubscribedEvents(): array
{
return [
BeforeEntityPersistedEvent::class => ['setCreatedAt'],
BeforeEntityUpdatedEvent::class => ['setUpdatedAt'],
];
}
public function setCreatedAt(BeforeEntityPersistedEvent $event): void
{
$entity = $event->getEntityInstance();
if ($entity instanceof Product) {
$entity->setCreatedAt(new \DateTime());
}
}
public function setUpdatedAt(BeforeEntityUpdatedEvent $event): void
{
$entity = $event->getEntityInstance();
if ($entity instanceof Product) {
$entity->setUpdatedAt(new \DateTime());
}
}
}Configuration Files
配置文件
Route Configuration
路由配置
yaml
undefinedyaml
undefinedconfig/routes.yaml
config/routes.yaml
admin:
resource: App\Controller\Admin\DashboardController
type: easyadmin
prefix: /admin
undefinedadmin:
resource: App\Controller\Admin\DashboardController
type: easyadmin
prefix: /admin
undefinedSecurity Configuration
安全配置
yaml
undefinedyaml
undefinedconfig/packages/security.yaml
config/packages/security.yaml
security:
# ... other config
access_control:
- { path: ^/admin/login, roles: PUBLIC_ACCESS }
- { path: ^/admin, roles: ROLE_ADMIN }
role_hierarchy:
ROLE_EDITOR: ROLE_USER
ROLE_ADMIN: ROLE_EDITOR
ROLE_SUPER_ADMIN: ROLE_ADMINundefinedsecurity:
# ... other config
access_control:
- { path: ^/admin/login, roles: PUBLIC_ACCESS }
- { path: ^/admin, roles: ROLE_ADMIN }
role_hierarchy:
ROLE_EDITOR: ROLE_USER
ROLE_ADMIN: ROLE_EDITOR
ROLE_SUPER_ADMIN: ROLE_ADMINundefinedEasyAdmin Configuration
EasyAdmin配置
yaml
undefinedyaml
undefinedconfig/packages/easyadmin.yaml
config/packages/easyadmin.yaml
easy_admin:
site_name: 'My Admin Panel'
formats:
date: 'd/m/Y'
time: 'H:i'
datetime: 'd/m/Y H:i:s'
design:
brand_color: '#1976D2'
menu:
- { label: 'Dashboard', icon: 'fa fa-home', route: 'admin' }
# Custom assets
assets:
css:
- 'css/admin.css'
js:
- 'js/admin.js'undefinedeasy_admin:
site_name: 'My Admin Panel'
formats:
date: 'd/m/Y'
time: 'H:i'
datetime: 'd/m/Y H:i:s'
design:
brand_color: '#1976D2'
menu:
- { label: 'Dashboard', icon: 'fa fa-home', route: 'admin' }
# Custom assets
assets:
css:
- 'css/admin.css'
js:
- 'js/admin.js'undefinedTroubleshooting
故障排除
Routes Not Found
路由未找到
bash
undefinedbash
undefinedClear cache
Clear cache
php bin/console cache:clear
php bin/console cache:clear
Verify routes
Verify routes
php bin/console debug:router | grep admin
undefinedphp bin/console debug:router | grep admin
undefinedImages Not Displaying
图片无法显示
Ensure your public directory structure matches:
public/
uploads/
images/
products/
avatars/确保你的public目录结构如下:
public/
uploads/
images/
products/
avatars/Association Fields Empty
关联字段为空
Add for collections:
by_reference => falsephp
yield AssociationField::new('tags')
->setFormTypeOptions(['by_reference' => false]);为集合添加:
by_reference => falsephp
yield AssociationField::new('tags')
->setFormTypeOptions(['by_reference' => false]);Autocomplete Not Working
自动补全无法工作
Install and configure autocomplete:
bash
composer require symfony/ux-autocomplete
php bin/console importmap:install安装并配置自动补全组件:
bash
composer require symfony/ux-autocomplete
php bin/console importmap:installPerformance Tips
性能优化建议
- Use pagination - Keep default page size reasonable (20-50 items)
- Limit search fields - Only include fields that need to be searchable
- Use autocomplete for associations - Especially with large datasets
- Disable unnecessary features - Hide detail pages if not needed
- Cache queries - Use query result caching for complex filters
- 使用分页 - 保持合理的默认每页数据量(20-50条)
- 限制搜索字段 - 仅包含需要搜索的字段
- 为关联字段使用自动补全 - 尤其是在数据集较大时
- 禁用不必要的功能 - 如果不需要详情页则隐藏
- 缓存查询 - 为复杂过滤器使用查询结果缓存
Common Patterns
常见模式
Multi-Tenant CRUD
多租户CRUD
php
public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder
{
$qb = parent::createIndexQueryBuilder($searchDto, $entityDto, $fields, $filters);
$qb->andWhere('entity.tenant = :tenant')
->setParameter('tenant', $this->getUser()->getTenant());
return $qb;
}
public function persistEntity(EntityManagerInterface $em, $entityInstance): void
{
$entityInstance->setTenant($this->getUser()->getTenant());
parent::persistEntity($em, $entityInstance);
}php
public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder
{
$qb = parent::createIndexQueryBuilder($searchDto, $entityDto, $fields, $filters);
$qb->andWhere('entity.tenant = :tenant')
->setParameter('tenant', $this->getUser()->getTenant());
return $qb;
}
public function persistEntity(EntityManagerInterface $em, $entityInstance): void
{
$entityInstance->setTenant($this->getUser()->getTenant());
parent::persistEntity($em, $entityInstance);
}Soft Delete Integration
软删除集成
php
use Doctrine\ORM\QueryBuilder;
public function createIndexQueryBuilder(...): QueryBuilder
{
$qb = parent::createIndexQueryBuilder(...);
$qb->andWhere('entity.deletedAt IS NULL');
return $qb;
}php
use Doctrine\ORM\QueryBuilder;
public function createIndexQueryBuilder(...): QueryBuilder
{
$qb = parent::createIndexQueryBuilder(...);
$qb->andWhere('entity.deletedAt IS NULL');
return $qb;
}Audit Trail
审计追踪
php
public function updateEntity(EntityManagerInterface $em, $entityInstance): void
{
$entityInstance->setUpdatedBy($this->getUser());
$entityInstance->setUpdatedAt(new \DateTime());
parent::updateEntity($em, $entityInstance);
}php
public function updateEntity(EntityManagerInterface $em, $entityInstance): void
{
$entityInstance->setUpdatedBy($this->getUser());
$entityInstance->setUpdatedAt(new \DateTime());
parent::updateEntity($em, $entityInstance);
}Requirements
系统要求
- PHP: 8.1 or higher
- Symfony: 5.4, 6.x, 7.x, or 8.x
- Doctrine ORM: Required for entity management
- Twig: Template engine
- PHP: 8.1 或更高版本
- Symfony: 5.4、6.x、7.x 或 8.x
- Doctrine ORM: 实体管理必需
- Twig: 模板引擎
Installation
安装步骤
bash
undefinedbash
undefinedInstall bundle (version 4.x)
Install bundle (version 4.x)
composer require easycorp/easyadmin-bundle
composer require easycorp/easyadmin-bundle
Optional: Install UX components for enhanced features
Optional: Install UX components for enhanced features
composer require symfony/ux-autocomplete
composer require symfony/ux-autocomplete
Generate dashboard
Generate dashboard
php bin/console make:admin:dashboard
php bin/console make:admin:dashboard
Generate CRUD controllers
Generate CRUD controllers
php bin/console make:admin:crud
undefinedphp bin/console make:admin:crud
undefinedAPI Reference
API参考
Complete API reference for quick lookup. See files in directory:
references/供快速查阅的完整API参考。请查看目录下的文件:
references/📋 Fields
📋 字段
Complete list of all 31 field types with their configuration methods:
- Common Methods: hideOnIndex(), onlyOnForms(), setColumns(), setRequired(), etc.
- Field Types: TextField, AssociationField, ImageField, MoneyField, DateTimeField, BooleanField, ChoiceField, and 24 more
- Form Layout: FormField panels, tabs, columns, rows
- Doctrine Mappings: Type to field mappings
包含全部31种字段类型及其配置方法的完整列表:
- 通用方法: hideOnIndex(), onlyOnForms(), setColumns(), setRequired()等
- 字段类型: TextField、AssociationField、ImageField、MoneyField、DateTimeField、BooleanField、ChoiceField及其他24种类型
- 表单布局: FormField面板、标签页、列、行
- Doctrine映射: 类型到字段的映射关系
⚡ Actions
⚡ 操作
Actions API and configuration methods:
- Built-in Actions: INDEX, DETAIL, EDIT, NEW, DELETE, SAVE_AND_RETURN, etc.
- Configuration: add(), remove(), update(), disable(), setPermission(), reorder()
- Custom Actions: linkToCrudAction(), linkToRoute(), linkToUrl()
- Batch Actions: addBatchAction(), BatchActionDto usage
操作API及配置方法:
- 内置操作: INDEX、DETAIL、EDIT、NEW、DELETE、SAVE_AND_RETURN等
- 配置: add()、remove()、update()、disable()、setPermission()、reorder()
- 自定义操作: linkToCrudAction()、linkToRoute()、linkToUrl()
- 批量操作: addBatchAction()、BatchActionDto的使用
🔧 CRUD
🔧 CRUD
CRUD controller configuration and lifecycle methods:
- configureCrud(): setEntityLabelInSingular(), setSearchFields(), setDefaultSort(), setPaginatorPageSize(), etc.
- Query Builders: createIndexQueryBuilder(), createEditQueryBuilder(), createNewQueryBuilder()
- Lifecycle: createEntity(), persistEntity(), updateEntity(), deleteEntity()
- Routes & Context: AdminContext, generateUrl(), getReferrer()
CRUD控制器配置及生命周期方法:
- configureCrud(): setEntityLabelInSingular()、setSearchFields()、setDefaultSort()、setPaginatorPageSize()等
- 查询构建器: createIndexQueryBuilder()、createEditQueryBuilder()、createNewQueryBuilder()
- 生命周期: createEntity()、persistEntity()、updateEntity()、deleteEntity()
- 路由与上下文: AdminContext、generateUrl()、getReferrer()
Official Documentation
官方文档
Main Documentation
主文档
- Repository: https://github.com/EasyCorp/EasyAdminBundle
- Documentation (4.x): https://github.com/EasyCorp/EasyAdminBundle/tree/4.x/doc
- Symfony Docs: https://symfony.com/bundles/EasyAdminBundle/current/index.html
Key Documentation Files
关键文档文件
- CRUD Operations - Complete CRUD configuration
- Dashboard - Dashboard setup and configuration
- Fields Reference - All available field types
- Actions - Custom actions and batch operations
- Filters - Filtering configuration
- Security - Permission and security setup
- Events - Event listeners and hooks
- Design - Customizing the UI
- Upgrade Guide - Migration between versions
Getting Latest Information
获取最新信息
bash
undefinedbash
undefinedClone the repository to browse documentation locally
Clone the repository to browse documentation locally
git clone https://github.com/EasyCorp/EasyAdminBundle.git
cd EasyAdminBundle
git clone https://github.com/EasyCorp/EasyAdminBundle.git
cd EasyAdminBundle
Switch to 4.x branch
Switch to 4.x branch
git checkout 4.x
git checkout 4.x
View documentation
View documentation
cd doc
ls -la # List all documentation files
cd doc
ls -la # List all documentation files
Read specific documentation
Read specific documentation
cat fields.rst
cat crud.rst
cat dashboards.rst
undefinedcat fields.rst
cat crud.rst
cat dashboards.rst
undefinedChecking for Updates
检查更新
bash
undefinedbash
undefinedCheck installed version
Check installed version
composer show easycorp/easyadmin-bundle
composer show easycorp/easyadmin-bundle
Update to latest 4.x version
Update to latest 4.x version
composer update easycorp/easyadmin-bundle
composer update easycorp/easyadmin-bundle
Check for newer versions
Check for newer versions
composer outdated easycorp/easyadmin-bundle
undefinedcomposer outdated easycorp/easyadmin-bundle
undefinedVersion Notes
版本说明
- 4.x (Current Stable): Production ready, actively maintained
- 5.x (Beta): In development, use for testing only
- This skill is based on 4.x patterns and syntax
- 4.x(当前稳定版): 可用于生产环境,持续维护中
- 5.x(测试版): 开发中,仅用于测试
- 本技能文档基于4.x版本的模式与语法
Community Resources
社区资源
- GitHub Issues: https://github.com/EasyCorp/EasyAdminBundle/issues
- GitHub Discussions: https://github.com/EasyCorp/EasyAdminBundle/discussions
- Symfony Community: https://symfony.com/community
- GitHub Issues: https://github.com/EasyCorp/EasyAdminBundle/issues
- GitHub Discussions: https://github.com/EasyCorp/EasyAdminBundle/discussions
- Symfony社区: https://symfony.com/community