给定两个学说实体(
Person
和Company
),一对多关联,以及一个看起来像这样的存储库
namespace TestBundle\Entity\Repository;
use Doctrine\ORM\EntityRepository;
class PersonRepository extends EntityRepository {
public function findAllByAge($age) {
$qb = $this->createQueryBuilder('p')
->select('p','c')
->leftjoin('p.company', 'c')
->where("p.age = :age")
->setParameter('age', $age);
// ...
}
}
如何检索公司的实体(对象或名称),最好是从 $qb 对象(或从别名、DQL、AST、解析器等)?
理想情况下,我希望有一个数组,其中包含 Querybuilder 实例使用的所有别名,或者至少包含在 select 方法中定义的别名及其实体,格式为:
[
'p' => 'TestBundle\Entity\Person',
'c' => 'TestBundle\Entity\Company',
// etc
]
在
$qb->getDQLPart('join')
甚至像 $qb->getQuery()->getAST()->fromClause->identificationVariableDeclarations
这样的较低级别中,存在有关别名的连接信息,但它仅包含根实体及其别名(p = TestBundle\Entity\Person)。
getRootEntity
、getRootAliases
、getAllAliases
没有帮助,因为我获得了根实体和/或所有别名,但无法将它们链接在一起。
$em->getClassMetadata($rootentity)->associationMappings
为我提供了根实体的关联,它包含用于连接的目标实体,但没有别名。当然,我可以将字段名称映射到 $qb->getDQLPart('join')
中的信息,但这会让我进入一个必须从每个实体对象中递归地 抓取信息的区域。这似乎可能会导致严重错误。
查询生成器如何将关联转换为正确的实体?或者它根本不这样做,只是解析到较低级别的东西而不知道它正在使用什么实体?
我需要这些信息,以便确保某些实体字段具有特定的二级索引。这些二级索引可以使用注释来设置,并按学说存储在实体中(
$em->getClassMetadata($entity)->table['indexes']
)。
我需要(以编程方式)知道哪些字段在构建查询时具有二级索引,并且希望尽可能保持在抽象树的较高位置。
您应该执行此操作的方法非常简单:
namespace TestBundle\Entity\Repository;
use Doctrine\ORM\EntityRepository;
class PersonRepository extends EntityRepository {
public function findAllByAge($age) {
$qb = $this->createQueryBuilder('p')
->where("p.age = :age")
->setParameter('age', $age);
return $qb->getQuery()->getResult();
}
}
...然后,走路时的反应:
$people = $personRepository->findAllByAge($age);
$companies = array_map(function ($person) {
return $person->getCompany();
}, $people);
我知道您的想法:这不会产生不必要的请求吗?难道不能通过一次 SQL 调用获得所有这些信息吗?嗯,这确实是可能的,但从长远来看,它并不像这那么简单。
我不会推荐它,除非对该请求的性能有巨大的需求(例如大量导入/导出)。但由于 Doctrine 已经添加了一个抽象层,我认为性能在这里不是问题。
编辑:
那么在这种情况下,您可以做的最好的事情就是使用带有自定义结果集映射器的本机查询。由于存储库的工作方式,在其中声明的常规 DQL 查询将始终期望返回存储库的实体(在您的情况下,它将尝试吐出一个
Person
实例),所以恐怕没有真正的方法围绕它。
这实际上是为了更好,因为 DQL 添加了一个抽象层,这在性能密集型查询的情况下是不受欢迎的。
对于长查询,我也强烈建议使用
->iterate()
,它将把请求分成更小的块。最后你的方法应该是这样的:
namespace TestBundle\Entity\Repository;
use Doctrine\ORM\EntityRepository;
class PersonRepository extends EntityRepository
{
public function getAllByAgeIterator($age)
{
$rsm = new ResultSetMappingBuilder($this->_em);
// RSM configuration here
$qb = $this->_em->createNativeQuery("
// Your SQL query here
", $rsm)->setParameter('age', $age);
return $qb->getQuery()->iterate();
}
}
我没有详细说明
ResultSetMappingBuilder
配置,但我想你会发现就好了。
如果我理解正确,您可以直接从查询返回
Company
对象的集合,甚至可以从 PersonRepository
:
namespace TestBundle\Entity\Repository;
use Doctrine\ORM\EntityRepository;
class PersonRepository extends EntityRepository {
public function findAllByAge($age) {
$qb = $this->getEntityManager()
->createQueryBuilder()
->from('YourBundle:Person', 'p')
->select('c')
->distinct()
->leftjoin('p.company', 'c')
->where("p.age = :age")
->setParameter('age', $age);
// ...
}
}
我不确定从
Company
存储库返回 Person
实例是否好,但这主要是约定问题。当然,您始终可以创建 CompanyRepository::findAllCompaniesWithEployeesAtAge($age)
和反向连接,例如:
namespace TestBundle\Entity\Repository;
use Doctrine\ORM\EntityRepository;
class CompanyRepository extends EntityRepository {
public function findAllCompaniesWithEployeesAtAge($age) {
$qb = $this->createQueryBuilder('c')
->select('c')
->distinct()
->leftjoin('c.person', 'p') // or whatever inverse side is called
->where("p.age = :age")
->setParameter('age', $age);
// ...
}
}