В Doctrine есть несколько отличных опций для обеспечения вашего приложения функциями абстракции и производительности.
- Предварительная выборка связанных объектов (путем их соединения)
- Отображение наследования в вашей модели сущности
Объединение этих двух проблем кажется немного проблематичным. Я даже не уверен, способен ли 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")
в моем коде?