I'm building a Symfony 2 / Doctrine 2 application on top of an existing MySQL database. Due to poor decisions in the past, i'm stuck with references that are concatenated in a table-column. Unfortunately remodeling the database is not an option.
E.g. entity "Product" referring to multiple "Categories":
| id | name | category_ids |
|----|-----------|--------------|
| 1 | product a | 1,2,5 |
| 2 | product b | 3,4,1 |
| 3 | product c | 2 |
I would like to have the method getCategories
available in my "Product" entity which would return a Collection of Category objects.
Is there any way to achieve this with Doctrine?
Maybe use custom code that is based on "FIND_IN_SET"?
SELECT c.*
FROM product p
LEFT OUTER JOIN category c ON FIND_IN_SET(c.id, p.category_ids)
WHERE p.id=:product_id;
Or maybe define the association with exploded values?
explode(',',$this->category_ids)
I try to avoid having to use the EntityManager each time i need to retrieve Categories from my Product entity. Because:
答案 0 :(得分:2)
您可以为getCategories
方法制定水合策略,并在水化器类中注册此策略(甚至可以是DoctrineObject
保湿剂)。类似的东西:
<强>策略强>
<?php
namespace My\Hydrator\Strategy;
use Doctrine\Common\Collections\ArrayCollection;
use My\Entity\Category;
use Doctrine\Common\Persistence\ObjectManager;
use DoctrineModule\Persistence\ObjectManagerAwareInterface;
use Zend\Stdlib\Hydrator\Strategy\StrategyInterface;
class CategoriesStrategy implements StrategyInterface, ObjectManagerAwareInterface
{
/**
* @var ObjectManager
*/
protected $objectManager;
/**
* @param ObjectManager $objectManager
* @param String $hostName
*/
public function __construct(ObjectManager $objectManager)
{
$this->objectManager = $objectManager;
}
/**
* @param array $value
* @return ArrayCollection
*/
public function extract($value)
{
$collection = new ArrayCollection();
if (is_array($value)) {
foreach ($value as $id) {
$category = $this->getObjectManager()->find(Category::class, $id);
$collection->add($category);
}
}
return $collection;
}
/**
* @param ArrayCollection $value
* @return array
*/
public function hydrate($value)
{
$array = array();
/** @var Category $category */
foreach ($value as $category) {
$array[] = $category->getId();
}
return $array;
}
/**
* @param ObjectManager $objectManager
* @return $this
*/
public function setObjectManager(ObjectManager $objectManager)
{
$this->objectManager = $objectManager;
return $this;
}
/**
* @return ObjectManager
*/
public function getObjectManager()
{
return $this->objectManager;
}
}
您可能需要一家工厂在您的保湿箱内注册您的CategoriesStrategy
:
保湿工厂
<?php
namespace My\Hydrator;
use Doctrine\Common\Persistence\ObjectManager;
use DoctrineModule\Stdlib\Hydrator\DoctrineObject;
use My\Hydrator\Strategy\CategoriesStrategy;
use Zend\ServiceManager\FactoryInterface;
use Zend\ServiceManager\ServiceLocatorInterface;
use Zend\ServiceManager\ServiceManager;
class MyHydratorFactory implements FactoryInterface
{
/**
* @param ServiceLocatorInterface $serviceLocator
* @return DoctrineObject
*/
public function createService(ServiceLocatorInterface $serviceLocator)
{
/** @var ServiceManager $serviceManager */
$serviceManager = $serviceLocator->getServiceLocator();
/** @var ObjectManager $objectManager */
$objectManager = $serviceManager->get('bc_object_manager');
/** @var DoctrineObject $hydrator */
$hydrator = new DoctrineObject($objectManager);
$hydrator->addStrategy('categories', new CategoriesStrategy($objectManager));
return $hydrator;
}
}
这未经过测试,但您明白了......
另一种解决方案是为您的类别注册DBAL类型。您可以在the Doctrine2 documentation chapter 8.4. Custom Mapping Types中查看如何执行此操作。
在实体列定义中,您指向类别类型:
/**
* @var string
* @ORM\Column(type="categories")
*/
protected $categories;
你在学说中注册的魔法是这样的:
'doctrine' => array(
'configuration' => array(
'orm_default' => array(
'types' => array(
'categories' => 'My\DBAL\Types\CategoriesCollection '
)
)
)
)
然后是班级本身:
<?php
namespace My\DBAL\Types;
use Doctrine\Common\Collections\ArrayCollection;
use Doctrine\DBAL\Platforms\AbstractPlatform;
use Doctrine\Common\Collections\Collection;
class CategoriesCollection extends \Doctrine\DBAL\Types\Type
{
const NAME = 'categories';
/**
* @return string
*/
public function getName()
{
return self::NAME;
}
/**
* {@inheritdoc}
*/
public function getSQLDeclaration(array $fieldDeclaration, AbstractPlatform $platform)
{
return $platform->getDoctrineTypeMapping('simple_array');
}
/**
* @param Collection $collection
* @param AbstractPlatform $platform
* @return array
*/
public function convertToDatabaseValue($value, AbstractPlatform $platform)
{
$array = [];
foreach($value as $category)
{
$category_id = $category->getId();
array_push($array, $category_id);
}
return $array;
}
/**
* {@inheritdoc}
*/
public function convertToPHPValue($value, AbstractPlatform $platform)
{
$collection = new ArrayCollection();
if ($value === null) {
return $collection;
}
foreach($value as $category_id){
$category = $this->em->getReference('Vendor\Bundle\Entity\Category', $category_id);
$collection->add($category);
}
return $collection;
}
/**
* @var EntityManager
*/
protected $em;
/**
* @param EntityManager $entityManager
*/
public function setEntityManager(EntityManager $entityManager)
{
$this->em = $entityManager;
}
}
此解决方案几乎与其他解决方案相同,只有您使用Doctrine2内部实现。您仍然需要在DBAL类型中注册EntityManager
并且不确定最简单的方法,以便我留给您。
在Symfony中,您可以在app / config / config.yml文件中注册自定义映射类型
doctrine:
dbal:
types:
category_ids: Vendor\Bundle\Type\CategoriesCollection
您可以在捆绑包的引导顺序中注入EntityManager依赖项:
<?php
namespace Vendor\Bundle\Bundle;
use Doctrine\DBAL\Types\Type;
use Symfony\Component\HttpKernel\Bundle\Bundle;
use Trilations\TApp\CoreBundle\Type\CategoryCollectionType;
class VendorBundleBundle extends Bundle
{
public function boot()
{
$em = $this->container->get('doctrine.orm.default_entity_manager');
$categoryCollectionType = Type::getType('category_ids');
$categoryCollectionType->setEntityManager($em);
}
}
并将字段映射到正确的自定义映射:
Vendor\Bundle\Enitity\Product
table: product
fields:
categories: category_ids