You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.09 09:02:10