如何查询多对一关系的反面?
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');
}
长话短说:每个 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');
}