如何查询多对一关系的反面?

How to query the inverse side of a ManyToOne relationship?

长话短说:每个 Organization 可以关联(可以拥有)许多 User 个对象,每个 User 关联(拥有)一个 Organization

每个用户可以属于一个组织,每个组织可以有多个用户。所以它是教科书 ManyToOne。

到目前为止一切顺利。我按如下方式设置 classes(我将省略不相关的部分):

用户class:

// User.php
/** @ORM\Entity */
class User implements UserInterface
{
    /**
     * @ORM\ManyToOne(targetEntity=Organization::class, inversedBy="user")
     * @ORM\JoinColumn(name="organization_id", referencedColumnName="id", nullable=false)
     */
    private $organization;


    public function getOrganization(): ?Organization
    {
        return $this->organization;
    }

    public function setOrganization(?Organization $organization): self
    {
        $this->organization = $organization;

        return $this;
    }
}

组织class:

/** @ORM\Entity */
class Organization
{
    /**
     * @ORM\OneToMany(targetEntity=User::class, mappedBy="organization")
     */
    private $user;

    public function __construct()
    {
        $this->user = new ArrayCollection();
    }

    /**
     * @return Collection|User[]
     */
    public function getUser(): Collection
    {
        return $this->user;
    }

}

在组织回购中,我使用原则设置了一个简单的查询来获取用户所属的所有组织(最多应为 1 个):

class OrganizationRepository extends ServiceEntityRepository
{
    private $security;

    public function __construct(ManagerRegistry $registry, Security $security)
    {
        $this->security = $security;
        parent::__construct($registry, Organization::class);
    }

    public function selectAllFromUser()
    {
        return $this->createQueryBuilder('o')
            ->andWhere('o.user = :user')
            // ->where('o.user = :user') tried with this too
            ->setParameter('user', $this->security->getUser())
            ->getQuery()
            ->getResult()
            ;
    }

这给了我以下错误:

[Semantical Error] line 0, col 48 near 'user = :user': Error: Invalid PathExpression. StateFieldPathExpression or SingleValuedAssociationField expected.

不可否认,组织上没有 FK,因此从 SQL 的角度来看,发出错误是正常的。但理论上这就是我想要的关系。

那么我做错了什么?

我也可以使用 One-To-Many, Unidirectional with Join Table,但此时使用 ManyToMany 并通过代码强制执行 ManyToOne 会更容易。

试试这个..

$this->createQueryBuilder()
    ->select('o, u')
    ->from(Organization::class)
    ->join(User::class, 'u')
    ->where('u = :user')
    ->setParameter('user', $this-security->getUser()->getId());// i'm not sure if ->getId() is necessary...

或者只是

$this->findBy(['user' => $this-security->getUser()]);

首先,你把事情复杂化了。

您根本不需要浏览存储库,也不需要创建查询构建器。

如果您想从用户那里获取组织,只需使用您的域并获取组织。

$organization = $user->getOrganization();

if (!$organization instanceof Organization) {
    // it appears this user was organizationless, which I would assume
    // is an error condition for your application, but since you typed
    // $organiza as nullable, it's possible and should be checked against 
}

好了,大功告成。无论您要在何处注入 OrganizationRepository,只需注入 Security 或您用来获取用户的任何 method/service,并直接获取其组织。


其次,你有一个命名问题。 属性user应该叫users。你可能认为这是一件小事,但它是一个集合,拥有一个暗示它拥有一个简单实体的变量名真的没有意义。

如果您真的想让查询正常工作(再次强调,不需要这样做,因为您可以简单地从用户那里获取组织),正确的命名会让您意识到您所做的比较并不很有意义。如果您将其命名为 Organization::users,那么您会很清楚自己所做的事情没有任何意义:

->andWhere('o.users = :user')

当看到这个时,您可能已经意识到您正在尝试将集合与单个实体进行比较。不合逻辑。

对于正确的查询,您需要进行不同的比较,并确保用户 属于组织的用户集合中。为此,您使用 MEMBER OF.

例如,像这样:

public function getOrganizationByUser(User $user): Organization
{
    $organization = $this->createQueryBuilder('o')
        ->andWhere(':user MEMBER OF o.users')
        ->setParameter('user', $user)
        ->setMaxResults(1)
        ->getQuery()
        ->getOneOrNullResult();

    if ($organization instanceof Organization) {
        return $organization;
    }

    throw new \InvalidArgumentException('User without Organization');
}