首页 > 解决方案 > 如何使用 Doctrine 继承映射在子类上加载相关实体?

问题描述

Doctrine 有几个很好的选择来为你的应用程序提供抽象和性能特性。

  1. 预取相关对象(通过加入它们)
  2. 实体模型中的继承映射

将两者结合起来似乎有点问题。我什至不确定 Doctrine 是否能够处理这种规模的复杂性。这将是我的问题真正所在的第三个讨论领域。

1、预取相关对象:

如果我只是想获取与 RegularProduct 相关的所有供应商实体,我会在我的存储库中执行类似的操作

App\Repository\RegularProductRepository.php:

namespace App\Repository;

use Doctrine\ORM\EntityRepository;

class RegularProductRepository extends EntityRepository
{
    /**
     * @return array The result
     */
    public function findWithSupplier() : array
    {
        $alias = "rp";

        $qb = $this->createQueryBuilder($alias);

        $qb->addSelect("s"); // hydrates Supplier entity
        $qb->leftJoin($alias . ".supplier", "s");

        return $qb->getQuery()->getResult();
    }
}

这会导致对数据库的单个查询。每当我在模板中读取供应商属性时,它都不需要再次查询数据库,因为供应商实体已经存储在 ReguralProduct 实体中。

2.实体模型中的继承映射:

在这个例子中,我向您展示了我在项目中实现的概念。

应用\实体\AbstractProductBase.php:

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

/**
 * @ORM\Entity(repositoryClass = "App\Repository\ProductBaseRepository")
 * @ORM\Table(name = "s4_product")
 * @ORM\MappedSuperclass
 * @ORM\InheritanceType("SINGLE_TABLE")
 * @ORM\DiscriminatorColumn(name = "product_type", type = "string")
 * @ORM\DiscriminatorMap({"RegularProduct" = "RegularProduct", "AliasedProduct" = "AliasedProduct"})
 */
abstract class AbstractProductBase
{
    /**
     * ID
     *
     * @var integer
     *
     * @ORM\Id
     * @ORM\Column(name = "product_id", type = "integer")
     * @ORM\GeneratedValue(strategy = "AUTO")
     */
    protected $id;

    /**
     * Name
     *
     * @var string
     *
     * @ORM\Column(name = "product_name", type = "string", length = 128)
     */
    protected $name;

    /**
     * Created At
     *
     * @var \DateTime
     *
     * @ORM\Column(name = "created_at_date", type = "datetime")
     */
    protected $createdAt;

    /**
     * Constructor
     */
    public function __construct()
    {
        $this->createdAt = new \DateTime();
    }

    /* ... getters & setters ... */
}

应用\实体\常规产品.php:

namespace App\Entity;

use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\ORM\Mapping as ORM;

use App\Entity\AliasedProduct;
use App\Entity\ProductDevice;
use App\Entity\Supplier;

/**
 * @ORM\Entity(repositoryClass = "App\Repository\RegularProductRepository")
 */
class RegularProduct extends AbstractProductBase
{
    /**
     * Constructor
     */
    public function __construct()
    {
        parent::__construct();

        $this->aliasedProducts = new ArrayCollection();
        $this->devices         = new ArrayCollection();
    }

    /**
     * Supplier
     *
     * Many Products have one Supplier
     *
     * @var Supplier
     *
     * @ORM\ManyToOne(targetEntity = "Supplier", inversedBy = "products")
     * @ORM\JoinColumn(name = "supplier_id", referencedColumnName = "supplier_id")
     */
    protected $supplier;

    /**
     * Aliased Products
     *
     * One RegularProduct has Many AliasedProducts
     *
     * @var ArrayCollection
     *
     * @ORM\OneToMany(targetEntity = "AliasedProduct", mappedBy = "regularProduct")
     */
    protected $aliasedProducts;

    /**
     * Devices
     * 
     * Many Products have many Devices (with association class ProductDevice)
     * 
     * @var ArrayCollection
     *
     * @ORM\OneToMany(targetEntity = "ProductDevice", mappedBy = "product", cascade = { "persist", "remove" })
     */
    protected $devices;

    /**
     * Set supplier
     *
     * @param \App\Entity\Supplier $supplier
     * @return RegularProduct
     */
    public function setSupplier(Supplier $supplier = null)
    {
        $this->supplier = $supplier;

        return $this;
    }

    /**
     * Get supplier
     *
     * @return \App\Entity\Supplier
     */
    public function getSupplier()
    {
        return $this->supplier;
    }

    /**
     * Add aliasedProduct
     *
     * @param \App\Entity\AliasedProduct $aliasedProduct
     * @return RegularProduct
     */
    public function addAliasedProduct(AliasedProduct $aliasedProduct)
    {
        $aliasedProduct->setRegularProduct($this);

        $this->aliasedProducts[] = $aliasedProduct;

        return $this;
    }

    /**
     * Remove aliasedProduct
     *
     * @param \App\Entity\AliasedProduct $aliasedProduct
     */
    public function removeCopy(AliasedProduct $aliasedProduct)
    {
        $this->aliasedProducts->removeElement($aliasedProduct);
    }

    /**
     * Get aliasedProducts
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getAliasedProducts()
    {
        return $this->aliasedProducts;
    }

    /**
     * Add device
     *
     * @param \App\Entity\ProductDevice $device
     *
     * @return Product
     */
    public function addDevice(ProductDevice $device)
    {
        $device->setProduct($this);

        $this->devices[] = $device;

        return $this;
    }

    /**
     * Remove device
     *
     * @param \App\Entity\ProductDevice $device
     */
    public function removeDevice(ProductDevice $device)
    {
        $this->devices->removeElement($device);
    }

    /**
     * Get devices
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getDevices()
    {
        return $this->devices;
    }
}

应用\实体\别名产品.php:

namespace App\Entity;

use Doctrine\ORM\Mapping as ORM;

use App\Entity\AbstractProductBase;
use App\Entity\RegularProduct;

/**
 * Aliased Product Entity
 *
 * @ORM\Entity()
 */
class AliasedProduct extends AbstractProductBase
{
    /**
     * Constructor
     */
    public function __construct()
    {
        parent::__construct();
    }

    /**
     * Regular Product
     *
     * Many AliasedProducts have one RegularProduct
     *
     * @var \App\Entity\RegularProduct
     *
     * @ORM\ManyToOne(targetEntity = "RegularProduct", inversedBy = "aliasedProducts", fetch = "EXTRA_LAZY")
     * @ORM\JoinColumn(name = "original_product_id", referencedColumnName = "product_id")
     */
    protected $regularProduct;

    /**
     * Set regularProduct
     *
     * @var \App\Entity\RegularProduct
     *
     * @return AliasedProduct
     */
    public function setRegularProduct(RegularProduct $regularProduct = null)
    {
        $this->regularProduct = $regularProduct;

        return $this;
    }

    /**
     *
     * @return \App\Entity\RegularProduct
     */
    public function getRegularProduct()
    {
        return $this->regularProduct;
    }

    /**
     * Get supplier
     *
     * @return Supplier
     */
    public function getSupplier()
    {
        return $this->regularProduct->getSupplier();
    }
}

3.在继承类上水合相关实体

当我想在具有继承映射的类上实现这一点时,似乎我还需要加入子实体 RegularProduct 在我的结果集中将是 RegularProduct 和 AliasedProduct 实体。

在下面的存储库中执行查询会导致 HydrationException 并显示以下消息:The parent object of entity result with alias 'd' was not found. The parent alias is 'rp'.

App\Repository\ProductBaseRepository.php:

namespace App\Repository;

use Doctrine\ORM\EntityRepository;
use App\Entity\RegularProduct;

abstract class ProductBaseRepository
{
    /**
     * @return array
     */
    public function getAllWithDevices() : array
    {
        $alias = "pb"; // AbstractProductBase

        $qb->leftJoin(
            RegularProduct::class,
            "rp",
            "WITH",
            $qb->expr()->eq($alias,"p")
        );

        $qb->addSelect("d"); // hydrates ProductDevice entity

        $qb->leftJoin(
            "rp.devices",
            "d",
            "WITH",
            $qb->expr()->eq("rp", "d.product")
        );

        return $qb->getQuery()->getResult();
    }
}

我试图通过添加来更改查询$qb->addSelect("rp")。这解决了获取异常但带来了一个新问题;结果不干净。此时,结果数组包含所有RegularProducts 两次,AliasedProducts 一次,并且对于每个AliasedProduct 都有一个空变量。

是否可以通过任何方式执行仅在RegularProducts 上补充Devices 而不$qb->addSelect("rp")在我的代码中添加该行的查询?

标签: symfonyinheritanceormdoctrine

解决方案


推荐阅读