在 Doctrine2 中为元表结构创建映射以在 FormBuilder 中使用
Posted
技术标签:
【中文标题】在 Doctrine2 中为元表结构创建映射以在 FormBuilder 中使用【英文标题】:Creating mappings in Doctrine2 for meta table structure for use in FormBuilder 【发布时间】:2013-02-23 10:56:09 【问题描述】:我有两张桌子:
分支机构:
+-------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+-------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| name | varchar(255) | NO | | NULL | |
+-------------+--------------+------+-----+---------+----------------+
分支元:
+------------+--------------+------+-----+---------+----------------+
| Field | Type | Null | Key | Default | Extra |
+------------+--------------+------+-----+---------+----------------+
| id | int(11) | NO | PRI | NULL | auto_increment |
| branch_id | int(11) | YES | MUL | NULL | |
| metaname | varchar(255) | NO | | NULL | |
| metavalue | varchar(255) | NO | | NULL | |
+------------+--------------+------+-----+---------+----------------+
我希望有不同的元字段,例如“电话”、“电子邮件”等,也可以是多个。
目前我有这段代码(省略了 setter/getter):
//实体/Branch.php
<?php
namespace Acme\Bundle\ConsysBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Branch
*
* @ORM\Entity(repositoryClass="Acme\Bundle\ConsysBundle\Entity\BranchRepository")
* @ORM\Table(name="branch")
*/
class Branch
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @var string
*
* @ORM\Column(name="name", type="string", length=255)
*/
private $name;
/**
*
* @ORM\OneToMany(targetEntity="BranchMeta", mappedBy="branch")
*/
private $metadata;
public function __construct()
$this->metadata = new ArrayCollection();
?>
//实体/BranchMeta.php
<?php
namespace Acme\Bundle\ConsysBundle\Entity;
use Doctrine\ORM\Mapping as ORM;
/**
* BranchMeta
*
* @ORM\Entity(repositoryClass="Acme\Bundle\ConsysBundle\Entity\BranchMetaRepository")
* @ORM\Table(name="branchmeta")
*/
class BranchMeta
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* @ORM\ManyToOne(targetEntity="Branch", inversedBy="metadata")
* @ORM\JoinColumn(name="branch_id", referencedColumnName="id")
*/
private $branch;
/**
* @var string
*
* @ORM\Column(name="metaname", type="string", length=255)
*/
private $name;
/**
* @var string
*
* @ORM\Column(name="metavalue", type="string", length=255)
*/
private $value;
?>
然后我需要构建一个表单来添加这样的分支:
Branch Name: [_________]
Branch Phone: [_________] [-]
Branch Phone: [_________] [+]
Branch Email: [_________] [-]
Branch Email: [_________] [+]
[Submit]
其中“分支电话”字段指向带有元名称“电话”和对应值的分支元表,而“分支电子邮件”字段指向带有元名称“电子邮件”的分支元表。两者都可以动态添加/删除。
如何使用 Symfony2 的 FormBuilder 构建它?我这样创建了 BranchType:
class BranchType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options)
$builder->add('name');
public function getName()
return 'branch';
还有BranchMetaType:
class BranchMetaType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options)
$builder->add('name');
$builder->add('value');
public function setDefaultOptions(OptionsResolverInterface $resolver)
$resolver->setDefaults(array(
'data_class' => 'Acme\Bundle\ConsysBundle\Entity\BranchMeta',
));
public function getName()
return 'branch_meta';
但后来我被卡住了......如何正确使用 BranchMetaType 来构建所需的表单?也许我在数据映射中遗漏了一些东西?
【问题讨论】:
【参考方案1】:其实还有一个更好的解决方案:
/**
* Meta
*
* @ORM\Entity
* @ORM\InheritanceType("SINGLE_TABLE")
* @ORM\DiscriminatorColumn(name="metaname", type="string")
* @ORM\DiscriminatorMap("phone" = "MetaPhone", "email" = "MetaEmail", "dummy" = "MetaDummy")
* @ORM\MappedSuperclass
*/
class Meta
// ...
/**
* @ORM\Entity
*/
class MetaPhone
/**
* @ORM\Entity
*/
class MetaEmail
/**
* @ORM\Entity
*/
class MetaDummy
这样做,我们将拥有相同的表、相同的实体(+ 一个超类实体),并且我们将像处理普通实体一样使用它们。没有hacky方式了。我希望我能在 7 个月前使用此功能...
【讨论】:
【参考方案2】:不知何故,这些信息散布在食谱http://symfony.com/doc/current/cookbook/form/form_collections.html 的附加注释部分学说:级联关系和保存“逆”方面。
这个问题给我留下了深刻的印象,因此我对其进行了一些实际的编码/研究。 :) 这是我发现的。
在Entity\Branch
中,告诉教义级联其对元数据属性的持久化操作,并修改setMetadata()
为每个要持久化的元数据设置当前分支。
/**
*
* @ORM\OneToMany(targetEntity="BranchMeta", mappedBy="branch", cascade="persist")
*/
private $metadata;
public function setMetadata(ArrayCollection $metadata)
foreach ($metadata as $m)
$m->setBranch($this);
$this->metadata = $metadata;
在Form\BranchType
中,使用适当的选项添加元数据字段,当然也可以将默认的data_class
设置为Entity\Branch
(你上面的BranchType
没有这个)。在此表单类型中,'by_reference' => false
选项对于确保在持久化期间调用 setMetadata()
函数很重要 (http://symfony.com/doc/current/reference/forms/types/collection.html#by-reference)。
public function buildForm(FormBuilderInterface $builder, array $options)
$builder->add('name');
$builder->add('metadata', 'collection',
array(
'type' => new BranchMetaType(),
'allow_add' => true,
'by_reference' => false,
));
public function setDefaultOptions(OptionsResolverInterface $resolver)
$resolver->setDefaults(
array(
'data_class' => 'Acme\Bundle\ConsysBundle\Entity\Branch'
));
最后尝试在控制器中添加一些虚拟分支元
public function newAction()
$entity = new Branch();
$branchmeta1 = new BranchMeta();
$branchmeta1->setMetaname('dummy meta name #1');
$branchmeta1->setMetavalue('dummy meta value #1');
$entity->getMetadata()->add($branchmeta1);
$branchmeta2 = new BranchMeta();
$branchmeta2->setMetaname('dummy meta name #2');
$branchmeta2->setMetavalue('dummy meta value #2');
$entity->getMetadata()->add($branchmeta2);
$form = $this->createForm(new BranchType(), $entity);
return array(
'entity' => $entity,
'form' => $form->createView(),
);
【讨论】:
感谢您的回复,但很遗憾这不是我想要的。这将在表单构建器(“元数据”)中添加一个字段集合,但我想要多个字段集合(“电话”、“电子邮件”、“传真”等),它们都物理存储在元数据表中,但由元数据名称字段标记。假设: id:1 branch_id:1 name:phone value:(123)1231231 id:2 branch_id:1 name:phone value:(312)1231231 id:3 branch_id:1 name:email value:vasya@example.com id :4 branch_id:1 name:fax value:(123)2525122 以此类推... 您无法通过标准的方式创建收藏集?这很奇怪。我看到这些“电话”、“电子邮件”、“传真”等可以使用 BranchMetaType 中的选择字段类型 (symfony.com/doc/current/reference/forms/types/choice.html) 轻松编码。 也许有一种标准且简单的方法来获得我需要的行为。问题是您的答案并不能解决这个问题。请检查我在您之后发布的我自己的解决方案。 我明白了。但是,通过查看屏幕截图,您仍然可以通过标准方式实现这一点,而不是通过将电话、电子邮件、传真等硬编码到分支实体中。顺便说一句,感谢您分享您的解决方案。【参考方案3】:最后我找到了一个不能称之为优雅但至少可行的解决方案。
分支.php:
namespace Acme\Bundle\ConsysBundle\Entity;
//use Gedmo\Mapping\Annotation as Gedmo;
use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
/**
* Branch
*
* @ORM\Entity(repositoryClass="Acme\Bundle\ConsysBundle\Entity\BranchRepository")
* @ORM\HasLifecycleCallbacks
* @ORM\Table(name="branch")
*/
class Branch
/**
* @var integer
*
* @ORM\Column(name="id", type="integer")
* @ORM\Id
* @ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
*
* @ORM\OneToMany(targetEntity="BranchMeta", mappedBy="branch", cascade="persist", "remove")
*/
private $metadata;
/**
* Virtual OneToMany field
*/
private $telephones;
/**
* Virtual OneToMany field
*/
private $faxes;
/**
* Virtual OneToMany field
*/
private $emails;
public function __construct()
$this->metadata = new ArrayCollection();
$this->telephones = new ArrayCollection();
$this->faxes = new ArrayCollection();
$this->emails = new ArrayCollection();
/* ...setters and getters are skipped... */
/**
* @ORM\PostLoad
*/
public function onPostLoad()
foreach (array('telephone' => 'Telephones', 'fax' => 'Faxes', 'email' => 'Emails') as $metakey => $metaname)
$metadata = $this->getMetadata()->filter(
function($entry) use ($metakey)
return $entry->getName() == $metakey;
);
$meta = call_user_func(array($this, 'set'.$metaname), $metadata);
控制器代码:
private function checkBranchAdded($branch)
foreach (array('telephone' => 'Telephones', 'fax' => 'Faxes', 'email' => 'Emails') as $metakey => $metaname)
$metadata = call_user_func(array($branch, 'get'.$metaname));
foreach ($metadata as $item)
if (!$item->getBranch())
$item->setBranch($branch);
$branch->getMetadata()->add($item);
private function checkBranchDeleted($branch)
$newMeta = array();
foreach (array('telephone' => 'Telephones', 'fax' => 'Faxes', 'email' => 'Emails') as $metakey => $metaname)
$metadata = call_user_func(array($branch, 'get'.$metaname));
$newMeta = array_merge($newMeta, array_keys($metadata->toArray()));
$em = $this->getDoctrine()->getEntityManager();
foreach($branch->getMetadata() as $key => $item)
if (!in_array($key, $newMeta))
$em->remove($item);
$branch->getMetadata()->remove($key);
/**
*
* @Template()
*/
public function editAction($activeMenu, $activeSubmenu, $activeSubsubmenu, $id, Request $request)
/* ... some code ... */
$repository = $this->getDoctrine()->getRepository('AcmeConsysBundle:Branch');
$branch = $repository->find($id);
if (!$branch)
throw $this->createNotFoundException('No branch found for id '.$id);
$form = $this->createForm(new BranchType(), $branch);
if ($request->isMethod('POST'))
$form->bind($request);
$this->checkBranchDeleted($branch);
$this->checkBranchAdded($branch);
/* ... some code ... */
/* ... some code ... */
表单生成器代码。 BranchType.php:
class BranchType extends AbstractType
public function buildForm(FormBuilderInterface $builder, array $options)
$builder->add('telephones', 'collection', array(
'type' => new BranchMetaType('telephone'),
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'options' => array(
),
));
$builder->add('faxes', 'collection', array(
'type' => new BranchMetaType('fax'),
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'options' => array(
),
));
$builder->add('emails', 'collection', array(
'type' => new BranchMetaType('email'),
'allow_add' => true,
'allow_delete' => true,
'prototype' => true,
'options' => array(
),
));
/* ... other fields ... */
/* ... other methods ... */
BranchMetaType.php:
class BranchMetaType extends AbstractType
protected $name;
public function __construct($name)
$this->name = $name;
public function buildForm(FormBuilderInterface $builder, array $options)
$builder->add('name', 'hidden', array(
'data' => $this->name,
));
$builder->add('value', 'text', array(
));
/* ... other methods ... */
【讨论】:
以上是关于在 Doctrine2 中为元表结构创建映射以在 FormBuilder 中使用的主要内容,如果未能解决你的问题,请参考以下文章