基于AbstractCrudController,如何通过Doctrine ORM获取并展示MAX(updatedAt)值?
解决EasyAdmin中添加聚合函数后出现的"Cannot read property 'id' from an array"错误
错误原因
你通过addSelect添加聚合子查询后,Doctrine的查询结果会返回实体对象与标量值的混合数组(比如[$rolesEntity, $lastUpdatedAt]),而EasyAdmin的默认模板逻辑期望结果是纯实体对象,尝试直接读取id属性时,误将数组当成实体,导致报错。
解决方案
推荐两种优雅的处理方式:
方式一:使用Doctrine的HIDDEN关键字保留实体结构
HIDDEN关键字会让Doctrine仍然返回完整的实体对象,同时将聚合值作为隐藏字段附加到实体上,不会破坏EasyAdmin的默认 hydration 逻辑。
- 修改
createIndexQueryBuilder方法:
public function createIndexQueryBuilder(SearchDto $searchDto, EntityDto $entityDto, FieldCollection $fields, FilterCollection $filters): QueryBuilder { $builder = parent::createIndexQueryBuilder($searchDto, $entityDto, $fields, $filters); // 添加HIDDEN关键字,确保Doctrine返回实体对象而非混合数组 return $builder->addSelect('(SELECT MAX(roles.updatedAt) FROM App\Entity\Roles roles) AS HIDDEN lastUpdatedAt'); }
- 在
configureFields中配置展示该字段:
use EasyCorp\Bundle\EasyAdminBundle\Field\Field; public function configureFields(string $pageName): iterable { yield Field::new('id')->onlyOnIndex(); yield Field::new('name'); yield Field::new('updatedAt'); // 配置聚合字段,用Twig表达式获取隐藏值 yield Field::new('lastUpdatedAt') ->setLabel('全局最后更新时间') ->setTemplatePath('admin/fields/global_last_updated.html.twig'); }
- 创建对应的Twig模板(
templates/admin/fields/global_last_updated.html.twig):
{% set globalLastUpdated = entity.lastUpdatedAt %} {{ globalLastUpdated ? globalLastUpdated|date('Y-m-d H:i:s') : '无数据' }}
方式二:单独查询聚合值并传递到模板
如果不想修改主查询,可以在index方法中单独获取全局最大值,再传递到模板展示。
- 重写
index方法:
use EasyCorp\Bundle\EasyAdminBundle\Dto\CrudDto; use Symfony\Component\HttpFoundation\Response; use App\Entity\Roles; public function index(CrudDto $crudDto): Response { // 单独查询全局最大更新时间 $globalLastUpdatedAt = $this->getDoctrine() ->getRepository(Roles::class) ->createQueryBuilder('r') ->select('MAX(r.updatedAt)') ->getQuery() ->getSingleScalarResult(); // 将值存入上下文,供模板使用 $crudDto->getContext()->set('globalLastUpdatedAt', $globalLastUpdatedAt); return parent::index($crudDto); }
- 自定义索引页模板(
templates/admin/crud/roles_index.html.twig):
{% extends '@EasyAdmin/crud/index.html.twig' %} {% block content_header %} {{ parent() }} <div class="mt-2"> <span class="badge bg-secondary">全局最后更新时间:{{ globalLastUpdatedAt|date('Y-m-d H:i:s') }}</span> </div> {% endblock %}
- 在
configureCrud中指定自定义模板:
use EasyCorp\Bundle\EasyAdminBundle\Config\Crud; public function configureCrud(Crud $crud): Crud { return $crud ->setTemplate('index', 'admin/crud/roles_index.html.twig'); }
不推荐的方案
不要尝试修改EasyAdmin的结果处理逻辑来适配混合数组,这会破坏框架的默认行为,增加维护成本。
内容的提问来源于stack exchange,提问作者walruz
相关产品推荐
相关产品推荐

