在 Doctrine2 中为元表结构创建映射以在 FormBuilder 中使用

问题描述 投票:0回答:3

我有两张桌子:

分行:

+-------------+--------------+------+-----+---------+----------------+
| 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]

其中“Branch Phone”字段指向元名称为“phone”和对应值的branchmeta 表,“Branch Email”字段指向元名称为“email”的branchmeta 表。两者都可以动态添加/删除。

如何使用 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 在我的情况下构建所需的表单?也许我在数据映射中遗漏了一些东西?

php symfony orm doctrine-orm formbuilder
3个回答
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
{
}

这样做,我们将拥有相同的表、相同的实体(+一个超类实体),并且我们将像使用普通实体一样使用它们。不再有什么黑客方法了。我希望我在 7 个月前就知道了这个功能...


0
投票

不知何故,信息分散在食谱中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-参考)。

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(),
    );
}

0
投票

最后我找到了一个解决方案,我不能称之为优雅,但至少它有效。

分支.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 ... */
}
© www.soinside.com 2019 - 2024. All rights reserved.