如何为关联/外键设置 ResultSetMapping [是为什么本机查询返回的关联始终为空]

Isa*_*way 4 php doctrine symfony

有关 ResultSetMapping 的更新问题,请参阅最后的编辑

\n\n

我定义了两个实体(Item 和 ItemType),其中之一与另一个实体具有 ManyToOne 关联。由于生成查找正确项目的一些复杂性,我有相当多的本机查询。这些查询始终返回第一个实体的所有列(SELECT items.* ...)。

\n\n

我发现我的关联在第一项上始终为空,并且我不确定我做错了什么。任何帮助,将不胜感激。

\n\n

实体:

\n\n

\n\n

namespace AppBundle\\Entity;\n\nuse Psr\\Log\\LoggerInterface;\nuse Doctrine\\ORM\\Mapping as ORM;\n\n/**\n * @ORM\\Table(name="items")\n * @ORM\\Entity(repositoryClass="AppBundle\\Entity\\ItemRepository")\n */\nclass Item {\n\n    /**\n     * @ORM\\Column(type="integer")\n     * @ORM\\Id\n     * @ORM\\GeneratedValue(strategy="AUTO")\n     */\n    private $id;\n\n    /**\n     * @ORM\\Column(name="account_id", type="integer")\n     */\n    private $accountId;\n\n    /**\n     * @ORM\\ManyToOne(targetEntity="ItemType")\n     * @ORM\\JoinColumn(name="item_type_id", referencedColumnName="id")\n     */\n    private $itemType;\n\n    // ..snip.. //\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

物品种类

\n\n

\n\n

namespace AppBundle\\Entity;\n\nuse Doctrine\\ORM\\Mapping as ORM;\n\n/**\n * @ORM\\Table(name="item_types")\n * @ORM\\Entity(repositoryClass="AppBundle\\Entity\\ItemTypeRepository")\n */\nclass ItemType {\n\n    /**\n     * @ORM\\Column(type="integer")\n     * @ORM\\Id\n     * @ORM\\GeneratedValue(strategy="AUTO")\n     */\n    private $id;\n\n    /**\n     * @ORM\\Column(name="account_id", type="integer")\n     */\n    private $accountId;\n\n    /**\n     * @ORM\\Column(type="string", length=128)\n     */\n    private $name;\n\n    // ..snip.. //\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

该查询是由 ItemRepository 类的 getItem 方法生成的。这有点长,但可以归结为SELECT items.* FROM items ...通过以下方式运行的查询getEntityManager()->createNativeQuery($sql, $rsm);

\n\n

\n\n

namespace AppBundle\\Entity;\n\nuse Psr\\Log\\LoggerInterface;\nuse Doctrine\\ORM\\Query\\ResultSetMapping;\n\n/**\n * ItemRepository\n *\n * This class was generated by the Doctrine ORM. Add your own custom\n * repository methods below.\n */\nclass ItemRepository extends \\Doctrine\\ORM\\EntityRepository\n{\n    /**\n     * @var \\Psr\\Log\\LoggerInterface $logger\n     */\n    protected $logger;\n\n    /**\n     * @var ItemTypeRepository\n     */\n    protected $itemTypes;\n\n    /**\n     * @var ItemValueRepository\n     */\n    protected $itemValues;\n\n    /**\n     * @var FieldRepository\n     */\n    protected $fields;\n\n    /**\n     * Called by service bindings in services.yml instead of __construct, which is needed by\n     * Doctrine.\n     */\n    public function initService(LoggerInterface $logger,\n                                ItemTypeRepository $itemTypes,\n                                ItemValueRepository $itemValues,\n                                FieldRepository $fields)\n    {\n        $this->logger = $logger;\n        $this->itemTypes = $itemTypes;\n        $this->itemValues = $itemValues;\n        $this->fields = $fields;\n    }\n\n    /**\n     * Get items for an account via itemId\n     *\n     * @param integer $accountId a user\'s account id\n     * @param $itemId unique ID for an Item\n     * @return Item_model\n     */\n    public function getItem($accountId, $itemId, $restrictedUserOwnerItemType, $restrictedUserOwnerItemId)\n    {\n        $this->logger->debug(__METHOD__.\'::params::\'.json_encode([\'accountId\' => $accountId, \'itemId\' => $itemId,\n                             \'restrictedUserOwnerItemType\' => $restrictedUserOwnerItemType, \'restrictedUserOwnerItemId\' => $restrictedUserOwnerItemId]));\n        if(!$accountId || !$itemId || !is_numeric($restrictedUserOwnerItemType) || !is_numeric($restrictedUserOwnerItemId)) \n            throw new \\InvalidArgumentException(\'getItem requires accountId, itemId, restrictedUserOwnerItemType and restrictedUserOwnerItemId\');\n\n        /*\n        $query = $this->itemsModel->builder();\n        $result = $query->where(\'account_id\', \'=\', $accountId)\n                        ->where(\'id\', \'=\', $itemId)\n                        ->first();\n        */\n\n        $sql = "SELECT items.*, ".\n        "item_types.id AS item_type_id, ".\n        "item_types.account_id AS item_type_account_id, ".\n        "item_types.name AS item_type_name, ".\n        "item_types.plural_name AS item_type_name, ".\n        "item_types.label AS item_type_label, ".\n        "item_types.plural_label AS item_type_plural_label, ".\n        "item_types.are_users AS item_type_are_users, ".\n        "item_types.own_users AS item_type_own_users ".\n        "FROM items ".\n        "JOIN item_types ON item_types.id = items.item_type_id ";\n\n        $isRestrictedUser = $restrictedUserOwnerItemType != 0 || $restrictedUserOwnerItemId != 0;\n        if($isRestrictedUser)\n        {\n            // Limit to items that are visible to restricted users\n            $sql .= <<<SQL\n\n\n      WHERE item_types.visible_to_restricted_users = 1 \n\nSQL;\n\n            // Limit to items that contain a relationship field pointed at the same owner item type,\n            // with the same item ID. For instance, limit items to those that have a Clients relationship\n            // field with "Acme Co." client selected as the client.\n            $sql .= <<<SQL\n\nAND items.id IN ( /* Where Item Belongs to Same Owner */\n      SELECT item_id \n      FROM item_values \n      JOIN fields ON fields.id = item_values.field_id\n      JOIN items ON items.id = item_values.item_id AND item_values.ver = items.ver\n      JOIN item_types ON item_types.id = items.item_type_id\n      WHERE item_values.value = ?\n        AND fields.field_type = "Relationship"\n        AND fields.field_item_type_id = ?)\n\nSQL;\n            $params[] = $restrictedUserOwnerItemId;     // Example: 3  -- CLIENT ID\n            $params[] = $restrictedUserOwnerItemType;   // Example: 10 -- CLIENTS\n\n            $sql .= "AND ";\n        } else {\n            $sql .= "WHERE ";\n        }\n\n        $sql .= "items.account_id = ? AND items.id = ? ";\n        $params[] = $accountId;\n        $params[] = $itemId;\n\n        // Get raw records\n        $rsm = $this->standardResultSetMapping();\n        // $this->logger->debug($sql);\n        // $this->logger->debug(print_r($params, true));\n        echo $sql;\n        $query = $this->getEntityManager()->createNativeQuery($sql, $rsm);\n        $query->setParameters($params);\n\n        // Wake up the entities\n        $result = array();\n        foreach($query->getResult() as $row) {\n            $row->initServiceEntity($this->logger, $this, $this->itemValues, $this->fields);\n            $result[] = $row;\n        }\n\n        if(!$result || count($result) == 0)\n            throw new \\InvalidArgumentException("Item could not be located for Item #".$itemId.". You may not have permission to view this item or it may not exist.");\n        else\n        {\n            return $result[0];\n        }\n    }\n\n    private function standardResultSetMapping()\n    {\n        $rsm = new ResultSetMapping();\n        //                    Class,                    Table\n        $rsm->addEntityResult(\'\\AppBundle\\Entity\\Item\', \'items\');\n        $rsm->addEntityResult(\'\\AppBundle\\Entity\\ItemType\', \'item_types\');\n        //                   Table,   Column,           Property\n        $rsm->addFieldResult(\'items\', \'id\',             \'id\');\n        $rsm->addFieldResult(\'items\', \'account_id\',     \'accountId\');\n        //$rsm->addFieldResult(\'items\', \'item_type_id\',   \'itemTypeId\');\n        $rsm->addFieldResult(\'items\', \'field_count\',    \'fieldCount\');\n        $rsm->addFieldResult(\'items\', \'ver\',            \'ver\');\n        $rsm->addFieldResult(\'items\', \'title\',          \'title\');\n        $rsm->addMetaResult(\'items\', \'item_type_id\',    \'item_type_id\', true);\n\n        $rsm->addFieldResult(\'item_types\', \'item_type_id\',          \'id\');\n        $rsm->addFieldResult(\'item_types\', \'item_type_name\',        \'name\');\n        $rsm->addFieldResult(\'item_types\', \'item_type_plural_name\', \'pluralName\');\n        $rsm->addFieldResult(\'item_types\', \'item_type_label\',       \'label\');\n        $rsm->addFieldResult(\'item_types\', \'item_type_plural_label\',\'pluralLabel\');\n        $rsm->addFieldResult(\'item_types\', \'item_type_are_users\',   \'areUsers\');\n        $rsm->addFieldResult(\'item_types\', \'item_type_own_users\',   \'ownUsers\');\n\n        return $rsm;\n    }\n\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

返回 Item 实体,但 itemType 始终为空:

\n\n
Item {#548 \xe2\x96\xbc\n  -id: 23\n  -accountId: 1\n  -itemType: null\n  -fieldCount: 4\n  -ver: 1451940837\n  -title: "New Item"\n  #fields: []\n  #itemValues: []\n  #cacheValues: []\n  #logger: Logger {#268 \xe2\x96\xb6}\n  #itemsRepository: ItemRepository {#349 \xe2\x96\xb6}\n  #itemValuesRepository: ItemValueRepository {#416 \xe2\x96\xb6}\n  #fieldsRepository: FieldRepository {#338 \xe2\x96\xb6}\n  #loaded: true\n  #changeCount: 0\n}\n
Run Code Online (Sandbox Code Playgroud)\n\n

item_types 数据

\n\n
id  account_id  name  plural_name label plural_label  are_users own_users\n31  1           task  tasks       Task  Tasks         1         0\n
Run Code Online (Sandbox Code Playgroud)\n\n

项目数据

\n\n
id  account_id  item_type_id  field_count ver         title\n23  1           31            4           1451940837  New Item\n
Run Code Online (Sandbox Code Playgroud)\n\n

编辑我认为,我已将其范围缩小到 ResultSetMapping 配置。更新了上面的代码。结果现在返回两个不同的对象,但没有将它们连接起来(Item\'s itemType 仍然为 null):

\n\n
object(AppBundle\\Entity\\Item)[560]\n  private \'id\' => int 23\n  private \'accountId\' => int 1\n  private \'itemType\' => null\n  private \'fieldCount\' => int 4\n  private \'ver\' => int 1451940837\n  private \'title\' => string \'New Item\' (length=8)\n  protected \'fields\' => \n    array (size=0)\n      empty\n  protected \'itemValues\' => \n    array (size=0)\n      empty\n  protected \'cacheValues\' => \n    array (size=0)\n      empty\n  protected \'logger\' => null\n  protected \'itemsRepository\' => null\n  protected \'itemValuesRepository\' => null\n  protected \'fieldsRepository\' => null\n  protected \'loaded\' => boolean false\n  protected \'changeCount\' => int 0\nobject(AppBundle\\Entity\\ItemType)[507]\n  private \'id\' => int 31\n  private \'accountId\' => int 1\n  private \'name\' => string \'task\' (length=4)\n  private \'pluralName\' => string \'tasks\' (length=5)\n  private \'label\' => string \'Task\' (length=4)\n  private \'pluralLabel\' => string \'Tasks\' (length=5)\n  private \'areUsers\' => boolean true\n  private \'ownUsers\' => boolean false\n
Run Code Online (Sandbox Code Playgroud)\n\n

所以现在的问题基本上是:

\n\n

如何设置 ResultSetMapping 以便它返回所有连接关联完好无损的实体?

\n

Jas*_*man 5

Doctrine关于 Native SQL 的文档有一些很好的见解,并且它非常清楚地表明您的错误是什么。对您现有帖子的简短回答是,您应该使用addJoinedEntityResult()而不是addEntityResult()用于您的ItemType实体。

实体结果的文档指出:

实体结果描述在转换结果中显示为根元素的实体类型。

这意味着,如果您在同一映射中添加两个实体结果,您将获得当前看到的结果 - ItemItemType作为两个不同的对象返回。但是,您知道两者是相关的,因此连接实体结果更有意义:

连接实体结果描述了一种实体类型,该实体类型在转换结果中显示为连接关系元素,附加到(根)实体结果。

要直接按原样修复代码,您需要更改

 $rsm->addEntityResult('\AppBundle\Entity\ItemType', 'item_types');
Run Code Online (Sandbox Code Playgroud)

对此:

$rsm->addJoinedEntityResult(
    '\AppBundle\Entity\ItemType',
    'item_types',
    'items',
    'itemType'
);
Run Code Online (Sandbox Code Playgroud)

格式为addJoinedEntityResult($class, $alias, $parentAlias, $relation),因此您可以看到添加的第三个和第四个参数指向父别名,以及Item类中指向ItemType 的字段。

话虽这么说,我认为你让这变得太复杂了,并且可以通过使用 ResultSetMappingBuilder 大大简化你的代码。这可以自动将字段映射到其等效的 SQL 列,然后,如果您更改了数据库中的字段名称或列名称,则无需挖掘所有代码来更新映射。

因此,standardResultSetMapping()您可以简单地执行以下操作,而不是调用复杂的函数:

$rsm = new ResultSetMappingBuilder($this->_em);
$rsm->addRootEntityFromClassMetadata('AppBundle\Entity\Item', 'items');
$rsm->addJoinedEntityFromClassMetadata('AppBundle\Entity\ItemType', 'item_types', 'items', 'itemType',
    ['id' => 'item_type_id', 
     'account_id' => 'item_type_id', 
     'name' => 'item_type_name',
     'plural_name' => 'item_type_plural_name',
     'label' => 'item_type_label',
     'plural_label' => 'item_type_plural_label',
     'are_users' => 'item_type_are_users',
     'own_users' => 'item_type_own_users']
);
Run Code Online (Sandbox Code Playgroud)

这样,您就可以消除冗余代码,使其不易出错,更易于测试,并自动处理实体和数据库的更新。第二次调用表明您仍然可以传递重命名列的数组。