如何在Doctrine 2中选择鉴别器列

9

当运行下面的DQL时,我需要帮助只选择Doctrine 2中的鉴别器列。

SELECT p.type FROM AppBundle\Entity\Product p

type是实体AppBundle\Entity\Product中的鉴别器列。

@ORM\DiscriminatorColumn(name="type", type="smallint")`

@ORM\DiscriminatorMap({
    "0" = "AppBundle\Entity\Product",
    "1" = "AppBundle\Entity\Product\SingleIssue",
    "2" = "AppBundle\Entity\Product\CountBasedIssue",
    "3" = "AppBundle\Entity\Product\TimeBasedIssue"
})

我知道实体中type不是真正的属性,但有没有办法让我这样做?

提前感谢!

更新

经过两天阅读Doctrine代码后,我决定通过以下片段覆盖SqlWalker并创建新的Hydrator。

覆盖SqlWalker

<?php

namespace ...;

use Doctrine\ORM\Query\SqlWalker;

class CustomSqlWalker extends SqlWalker
{
    const FORCE_GET_DISCRIMINATOR_COLUMN = 'forceGetDiscriminatorColumn';
    const DISCRIMINATOR_CLASS_MAP = 'discriminatorClassMap';

    /**
     * {@inheritdoc}
     */
    public function walkSelectClause($selectClause)
    {
        $sql = parent::walkSelectClause($selectClause);
        $forceGetDiscriminatorColumn = $this->getQuery()->getHint(self::FORCE_GET_DISCRIMINATOR_COLUMN);
        if (empty($forceGetDiscriminatorColumn)) {
            return $sql;
        }

        foreach ($this->getQueryComponents() as $key => $queryComponent) {
            if (!in_array($key, $forceGetDiscriminatorColumn)) {
                continue;
            }

            $metadata = $queryComponent['metadata'];
            $discriminatorColumn = $metadata->discriminatorColumn['name'];
            $tableName = $metadata->table['name'];
            $tableAlias = $this->getSQLTableAlias($tableName, $key);
            $discriminatorColumnAlias = $this->getSQLColumnAlias($discriminatorColumn);
            $sql .= ", $tableAlias.$discriminatorColumn AS $discriminatorColumnAlias";
        }

        return $sql;
    }
}

自定义水合器

<?php

namespace ...;

use Doctrine\ORM\Internal\Hydration\ArrayHydrator;
use PDO;

class CustomHydrator extends ArrayHydrator
{
    /**
     * {@inheritdoc}
     */
    protected function hydrateAllData()
    {
        $result = array();

        $rootClassName = null;
        if (isset($this->_hints['forceGetDiscriminatorColumn']) &&
            isset($this->_hints['discriminatorClassMap'])) {
            $rootClassName = $this->_hints['discriminatorClassMap'];
        }

        while ($data = $this->_stmt->fetch(PDO::FETCH_ASSOC)) {
            foreach ($data as $key => $value) {
                if ($this->hydrateColumnInfo($key) != null ||
                    empty($rootClassName)) {
                    continue;
                }

                $metadata = $this->getClassMetadata($rootClassName);
                $discriminatorColumn = $metadata->discriminatorColumn;
                $fieldName = $discriminatorColumn['fieldName'];
                $type = $discriminatorColumn['type'];
                $this->_rsm->addScalarResult(
                    $key, $fieldName, $type
                );
            }
            $this->hydrateRowData($data, $result);
        }

        return $result;
    }
}

Configure custom hydrator

orm:
    ...
    hydrators:
        CustomHydrator: YourNamespace\To\CustomHydrator

最后一步

$query = $queryBuilder->getQuery();
$query->setHint(\Doctrine\ORM\Query::HINT_CUSTOM_OUTPUT_WALKER, 'YourNamespace\To\CustomSqlWalker');
$query->setHint(\YourNamespace\To\CustomSqlWalker::FORCE_GET_DISCRIMINATOR_COLUMN, array($rootAlias)); // this alias will be used in CustomSqlWalker class
$query->setHint(\YourNamespace\To\CustomSqlWalker::DISCRIMINATOR_CLASS_MAP, $this->getClassName()); // this full-qualify class name will be used in CustomHydrator class

$products = $query->getResult('CustomHydrator');

简而言之

我知道这个解决方案可能很复杂(也许只适用于我的情况),所以我希望有人能给我另一种简单的方法来解决这个问题,非常感谢!


虽然Matteo使用INSTANCE OF似乎是最干净的解决方案,但我的第一个想法是将鉴别器列加倍,其中一个Doctrine不会掩盖。 好吧,那很丑陋,并且需要确保两个列不能脱节,但它具有让您根据需要处理数据的优点。 - Balmipour
3个回答

13

无法直接访问鉴别器列。

有时需要查询特定类型的实体。 因为无法直接访问鉴别器列, Doctrine 提供了 INSTANCE OF 构造。

您可以使用 INSTANCE OF DQL 如文档中所述 查询实体的类型。例如:

$query = $em->createQuery("SELECT product FROM AppBundle\Entity\AbstractProduct product WHERE product  INSTANCE OF AppBundle\Entity\Product");
$products = $query->getResult();

希望这有所帮助。

1
是的,这是dql直接检索的正确答案。我注意到即使在我的项目中,我也使用了这种解决方案 :) - DonCallisto

8
我使用这个小“技巧”
  1. 为实体定义一个通用接口(可选但建议)
  2. 在此接口中创建一个getType方法
  3. 创建Discriminator实体的常量
  4. 在每个区分实体内部返回适当的常量
这样,您就可以检索判别器“通用”实体(在您的情况下为Product),并在其上调用getType
当然,如果您对直接由SQL完成的结果过滤感兴趣,那么这根本不是一种解决方案,并且恐怕目前没有任何可用的解决方案。 如果您发现比这更好的解决方案,请与我们分享。

同时 abstract public function getType() 也可以正常工作(我的鉴别器实体也是抽象的) - Erce

2

您应该能够使用标量结果和 INSTANCE OF 以及 case, when, (else,) end 子句来完成此操作:

SELECT
  (case
     when p INSTANCE OF AppBundle\Entity\Product then \'0\'
     when p INSTANCE OF AppBundle\Entity\Product\SingleIssue then \'1\'
     when p INSTANCE OF AppBundle\Entity\Product\CountBasedIssue then \'2\'
     when p INSTANCE OF AppBundle\Entity\Product\TimeBasedIssue then \'3\'
     else \'foobar\'
   end) as type
FROM
  AppBundle\Entity\Product p

当然,缺点是每次添加 DiscriminatorMap 条目时都需要更新查询。

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接