easy-admin-bundle

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

EasyAdmin 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
undefined
bash
undefined

Install 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
undefined
php bin/console make:admin:crud
undefined

Complete 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
undefined
yaml
undefined

config/routes.yaml

config/routes.yaml

admin: resource: App\Controller\Admin\DashboardController type: easyadmin prefix: /admin
undefined
admin: resource: App\Controller\Admin\DashboardController type: easyadmin prefix: /admin
undefined

Security Configuration

安全配置

yaml
undefined
yaml
undefined

config/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_ADMIN
undefined
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_ADMIN
undefined

EasyAdmin Configuration

EasyAdmin配置

yaml
undefined
yaml
undefined

config/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'
undefined
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'
undefined

Troubleshooting

故障排除

Routes Not Found

路由未找到

bash
undefined
bash
undefined

Clear cache

Clear cache

php bin/console cache:clear
php bin/console cache:clear

Verify routes

Verify routes

php bin/console debug:router | grep admin
undefined
php bin/console debug:router | grep admin
undefined

Images Not Displaying

图片无法显示

Ensure your public directory structure matches:
public/
  uploads/
    images/
    products/
    avatars/
确保你的public目录结构如下:
public/
  uploads/
    images/
    products/
    avatars/

Association Fields Empty

关联字段为空

Add
by_reference => false
for collections:
php
yield AssociationField::new('tags')
    ->setFormTypeOptions(['by_reference' => false]);
为集合添加
by_reference => false
php
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:install

Performance Tips

性能优化建议

  1. Use pagination - Keep default page size reasonable (20-50 items)
  2. Limit search fields - Only include fields that need to be searchable
  3. Use autocomplete for associations - Especially with large datasets
  4. Disable unnecessary features - Hide detail pages if not needed
  5. Cache queries - Use query result caching for complex filters
  1. 使用分页 - 保持合理的默认每页数据量(20-50条)
  2. 限制搜索字段 - 仅包含需要搜索的字段
  3. 为关联字段使用自动补全 - 尤其是在数据集较大时
  4. 禁用不必要的功能 - 如果不需要详情页则隐藏
  5. 缓存查询 - 为复杂过滤器使用查询结果缓存

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
undefined
bash
undefined

Install 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
undefined
php bin/console make:admin:crud
undefined

API Reference

API参考

Complete API reference for quick lookup. See files in
references/
directory:
供快速查阅的完整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

主文档

Key Documentation Files

关键文档文件

Getting Latest Information

获取最新信息

bash
undefined
bash
undefined

Clone the repository to browse documentation locally

Clone the repository to browse documentation locally

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
undefined
cat fields.rst cat crud.rst cat dashboards.rst
undefined

Checking for Updates

检查更新

bash
undefined
bash
undefined

Check 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
undefined
composer outdated easycorp/easyadmin-bundle
undefined

Version 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

社区资源