Как загрузить связанные сущности в подкласс с отображением наследования Doctrine? - PullRequest
0 голосов
/ 30 апреля 2018

В 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. Отображение наследования в вашей модели сущности:

В этом примере я покажу вам концепцию, которую я реализовал в своем проекте.

App \ Entity \ 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 ... */
}

App \ Entity \ RegularProduct.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;
    }
}

App \ Entity \ AliasedProduct.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. Гидратирование связанных сущностей на унаследованных классах

Когда я хочу достичь этого в классе с отображением наследования, мне кажется, что мне также нужно присоединиться к Sub-сущности 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 без добавления строки $qb->addSelect("rp") в моем коде?

...