在 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' =&gt; 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 中使用的主要内容,如果未能解决你的问题,请参考以下文章

在 Apache Pig 中为元组创建模式

lua——元表元方法继承

如何映射分类变量以在 R Plotly 中为 3D 散点图中的点轮廓着色?

lua元表(metatable)

Lua中的元表与元方法

Lua中的元表与元方法