API-Platform Doctrine扩展:多对多关联字段排序报错求助
多对多关联集合排序报错解决方法
报错信息
'emrHeritage ': Error: Invalid PathExpression. StateFieldPathExpression or SingleValuedAssociationField expected.
相关代码
1. Utilisateur实体的多对多关联
#[ ORM\JoinTable(name: 'attribut_heritage'), ORM\JoinColumn(name: 'UtilisateurId', referencedColumnName: 'IdUtilisateur', columnDefinition: "CHAR(14) NOT NULL"), ORM\InverseJoinColumn(name: 'AttributId', referencedColumnName: 'IdAttribut', columnDefinition: "CHAR(14) NOT NULL"), ORM\ManyToMany(targetEntity: Emr::class), Groups(['utilisateur:get']) ] private Collection $emrHeritage;
2. Emr实体的关联字段
#[ ORM\ManyToOne(inversedBy: 'emrs'), ORM\JoinColumn( name: 'AttributEntiteId', referencedColumnName: 'IdAttributEntite', nullable: false ), Groups(['emr:get', 'utilisateur:get']) ] private ?Entite $entite = null; #[ ORM\ManyToOne(inversedBy: 'emrs'), ORM\JoinColumn( name: 'AttributMetierId', referencedColumnName: 'IdAttributMetier', nullable: false ), Groups(['emr:get', 'utilisateur:get']) ] private ?Metier $metier = null; #[ ORM\ManyToOne(inversedBy: 'emrs'), ORM\JoinColumn( name: 'AttributRoleId', referencedColumnName: 'IdAttributRole', nullable: false ), Groups(['emr:get', 'utilisateur:get']) ] private ?Role $role = null;
3. 排序用的code字段(以Entite为例)
#[ ORM\Column(name: 'Code', length: 128), Groups(['entite:get', 'emr:get', 'utilisateur:get']), ApiProperty( identifier: true, openapiContext: [ 'description' => "Code de l'entité", 'type' => 'string', 'example' => 'EXAMPLE' ] ) ] private ?string $code = null;
4. 报错的排序逻辑
final class EmrHeritageOrderExtension implements QueryCollectionExtensionInterface, QueryItemExtensionInterface private function orderBy(QueryBuilder $queryBuilder, string $resourceClass): void { if (Utilisateur::class !== $resourceClass) return; $rootAlias = $queryBuilder->getRootAliases()[0]; $queryBuilder->orderBy(sprintf('%s.emrHeritage.entite.code', $rootAlias), "ASC"); dump($queryBuilder->getQuery()); }
问题原因
emrHeritage是多对多关联,属于多值关联(MultiValuedAssociation),Doctrine的QueryBuilder不允许直接通过多值关联的路径(如$rootAlias.emrHeritage.entite.code)进行排序,因为这样的路径无法对应到单一的字段或单值关联。
解决方案
根据需求分两种情况处理:
情况1:让每个Utilisateur的emrHeritage集合内部自动排序
直接在实体的多对多关联注解中添加@ORM\OrderBy,这样每次加载集合时都会自动按指定字段排序:
#[ ORM\JoinTable(name: 'attribut_heritage'), ORM\JoinColumn(name: 'UtilisateurId', referencedColumnName: 'IdUtilisateur', columnDefinition: "CHAR(14) NOT NULL"), ORM\InverseJoinColumn(name: 'AttributId', referencedColumnName: 'IdAttribut', columnDefinition: "CHAR(14) NOT NULL"), ORM\ManyToMany(targetEntity: Emr::class), ORM\OrderBy(["entite.code" => "ASC"]), // 添加这一行 Groups(['utilisateur:get']) ] private Collection $emrHeritage;
情况2:对Utilisateur列表按关联的emrHeritage字段排序
如果是要对查询返回的Utilisateur列表进行排序(比如按关联的Emr的entite.code排序),需要先join多对多关联,使用join后的别名进行排序,同时为避免笛卡尔积重复,需添加distinct或group by:
修改EmrHeritageOrderExtension中的orderBy方法:
private function orderBy(QueryBuilder $queryBuilder, string $resourceClass): void { if (Utilisateur::class !== $resourceClass) return; $rootAlias = $queryBuilder->getRootAliases()[0]; // Join多对多关联,指定别名 $queryBuilder->leftJoin(sprintf('%s.emrHeritage', $rootAlias), 'emr'); // 按关联实体的code排序 $queryBuilder->orderBy('emr.entite.code', 'ASC'); // 添加distinct避免重复的Utilisateur结果 $queryBuilder->distinct(); }
注意:如果需要按多个字段排序(如先entite.code,再metier.code),可以多次调用
addOrderBy:$queryBuilder->orderBy('emr.entite.code', 'ASC') ->addOrderBy('emr.metier.code', 'ASC') ->addOrderBy('emr.role.code', 'ASC');
内容的提问来源于stack exchange,提问作者BernardA
相关产品推荐
相关产品推荐

