Symfony2 表格:坚持相关 objects 的自动化

Symfony2 Form: AUTOMATION on persisting with related objects

我有一个用于插入实体 类别 的表单。该实体有两个与之相关的其他实体。 一个相关实体是另一个单独的实体 Group。另一个实体本身是 self-referenced Category,它是一个表示先决条件的数组 collection。到目前为止一切顺利,我可以使用正确的 ORM 注释来保存主要实体和关系。

类别

的粗略方案

我创建了一个类型 class 用于创建文档中描述的 best-practice 表单。

$form = $this->createForm(new CategoryType($em));

情况

在持久化实体之前,我必须对其进行初始化并将发布的数据设置给它。已发布的相关 objects 不能简单地设置为持久实体,因为它们的数据类型错误。 (例如 self-referencing collection 仅作为带有 id 的数组发布,而不是所选项目的数组 collection。) 所以我捕获了这个原始数据并从实体管理器中单独获取相关实体。

目标

插入实体应自动填充相关实体,无需通过实体管理器单独获取这些实体

问题

这就是相关objects的表单组件没有完全发布和提供的意思吗?或者我在实施中缺少什么? 有没有办法更自动化地做到这一点?

在表格 class 的“先决条件”属性 上,我必须做 mapped => false 否则我会收到错误类型传递的异常。但最后我希望表单通过映射自动匹配,无需跳过映射,无需从实体管理器中单独获取相关实体。

class CategoryType extends AbstractType
{

    public function __construct($em)
    {
        $this->em = $em;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $qb = $this->em->createQueryBuilder();

        $categories = $qb->select('e.id, e.title')
            ->from('MyvendorCoreBundle:Category', 'e')
            ->indexBy('e', 'e.id')
            ->orderBy('e.title')
            ->getQuery()
            ->getResult();

        $categories_choice = array_map(function ($value) {
            return $value['title'];
        }, $categories);

        $builder->add('title')
            ->add('group_Id', new GroupType($this->em))
            ->add('preconditions', 'choice', array(
            'choices' => $categories_choice,
            'multiple' => true,
            'mapped' => false
        ))
            ->add('save', 'submit');
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Myvendor\CoreBundle\Entity\Category'
        ));
    }

    public function getName()
    {
        return 'category';
    }
}

控制器方法

public function newAction(Request $request)
    {
        $em = $this->getDoctrine()->getManager();
        $form = $this->createForm(new CategoryType($em));

        // Repopulating the form after submission
        $form->handleRequest($request);

        // Prepare a new empty Category
        $category = new Category();

        if ($form->isValid()) {
            /* Catch some raw datas posted from the form */
            // Posted precondition category ids to get its entities more later
            $precondition_category_ids = $form->get('preconditions')->getData();
            // Posted group entity that have only filled the group id in the object          
            $group_raw = $form->get('group_Id')->getData();

            // Get the explicit filled group entity throuth the posted id.
            $group = $em->find('MyvendorCoreBundle:Group', $group_raw->getGroupid());

            // Fill the prepaired group with the posted datas
            $category->setTitle($form->get('title')->getData());
            $category->setGroupId($group);

            // Adding preconditions
            try {
                for ($i = 0; count($precondition_category_ids) > $i; $i ++) {
                    $precondition_category_id = $precondition_category_ids[$i];
                    if (0 >= $precondition_category_id) { // Retrieving id must be greater than 0
                        throw new \Exception('Error retrieving precondition id');
                    }
                    $precondition_category = $em->find('MyvendorCoreBundle:Category', $precondition_category_id);

                    if ($precondition_category instanceof Category) {
                        $category->addPrecondition($precondition_category);
                    } else {
                        throw new \Exception('Error retrieving precondition as Myvendor\CoreBundle\Entity\Category');
                    }
                }
                $em->persist($category); // Insert the group item with its relations
                $em->flush();
                echo '<h1 style="color:green">persisted</h1>';
            } catch (\Exception $e) {
                echo '<h1 style="color:red">' . $e->getMessage() . '</h1>';
            }
        }

        return $this->render('MyvendorCoreBundle:fbm:new.html.twig', array(
            'form' => $form->createView()
        ));
    }

群组类型

class GroupType extends AbstractType
{
    public function __construct($em){
        $this->em = $em;
    }

    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $groups = $this->em->createQuery("
            SELECT o.groupid, o.descr
            FROM MyvendorCoreBundle:Group o
            INDEX BY o.groupid
            ORDER BY o.descr
            ")->getResult();
        $groups_dropdown = array();
        $groups_dropdown = array_map(function($value) { return $value['descr']; }, $groups);
        $builder->add('groupid', 'choice', array(
            'label' => false,
            'choices' => $groups_dropdown,
            'attr' => array('style' => 'width: 300px')
        ));
    }

    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'Myvendor\CoreBundle\Entity\Group',
        ));
    }

    public function getName()
    {
        return 'group';
    }
}
/**
 * @ORM\Entity
 * @ORM\Table(name="category")
 */
class Category
{
    public function __construct()
    {
        $this->preconditions = new ArrayCollection();
    }

    /**
    * @ORM\Column(type="integer")
    * @ORM\Id
    * @ORM\GeneratedValue(strategy="AUTO")
    */
    protected $id;

    /**
     * @var \Myvendor\CoreBundle\Entity\Group
     *
     * @Assert\Type(type="Myvendor\CoreBundle\Entity\Group")
     * @Assert\Valid()
     * @ORM\ManyToOne(targetEntity="Myvendor\CoreBundle\Entity\Group", inversedBy="Category")
     * @ORM\JoinColumn(name="group_id", nullable=false, referencedColumnName="groupid")
     */
    private $group_Id;

    /**
     * @var string
     * @Assert\NotBlank()
     * @ORM\Column(type="string", length=255, nullable=false)
     */
    private $title;

       /**
     * Preconditions are Categorys referencing to an Category.
     * For a single Category its empty (which have no subelements).
     * A join table holds the references of a main Category to its sub-Categorys (preconditions)
     *
     * @ORM\ManyToMany(targetEntity="Category")
     * @ORM\JoinTable(name="category_precondition",
     *     joinColumns={@JoinColumn(name="category_id", referencedColumnName="id")},
     *     inverseJoinColumns={@JoinColumn(name="category_precondition_id", referencedColumnName="id")}
     * )
     */
    private $preconditions;


    /**
     * Get id
     *
     * @return integer
     */
    public function getId()
    {
        return $this->id;
    }

    /**
     * Set title
     *
     * @param string $title
     *
     * @return Category
     */
    public function setTitle($title)
    {
        $this->title = $title;

        return $this;
    }

    /**
     * Get title
     *
     * @return string
     */
    public function getTitle()
    {
        return $this->title;
    }

    /**
     * Set groupId
     *
     * @param \Myvendor\CoreBundle\Entity\Group $groupId
     *
     * @return Category
     */
    public function setGroupId(\Myvendor\CoreBundle\Entity\Group $groupId)
    {
        $this->group_Id = $groupId;

        return $this;
    }

    /**
     * Get groupId
     *
     * @return \Myvendor\CoreBundle\Entity\Group
     */
    public function getGroupId()
    {
        return $this->group_Id;
    }

    /**
     * Add precondition
     *
     * @param \Myvendor\CoreBundle\Entity\Category $precondition
     *
     * @return $this
     */
    public function addPrecondition(\Myvendor\CoreBundle\Entity\Category $precondition)
    {
        $this->preconditions[] = $precondition;

        return $this;
    }

    /**
     * Get preconditions
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getPreconditions()
    {
        return $this->preconditions;
    }
/**
 * Group
 *
 * @ORM\Table(name="group", indexes={@ORM\Index(name="homepage", columns={"homepage"}), @ORM\Index(name="theme", columns={"theme"})})
 * @ORM\Entity
 */
class Group
{
    /**
     * @var string
     *
     * @ORM\Column(name="descr", type="string", length=60, nullable=true)
     */
    private $descr;

    /**
     * @var integer
     *
     * @Assert\NotBlank()
     * @ORM\Column(name="groupid", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="IDENTITY")
     */
    public $groupid;

    /**
     * Set descr
     *
     * @param string $descr
     * @return Group
     */
    public function setDescr($descr)
    {
        $this->descr = $descr;

        return $this;
    }

    /**
     * Get descr
     *
     * @return string 
     */
    public function getDescr()
    {
        return $this->descr;
    }

    /**
     * Get groupid
     *
     * @return integer 
     */
    public function getGroupid()
    {
        return $this->groupid;
    }
}

解决方案是,选择实体的类型必须不是选择列表,而是真正的集合类型。

所以使用这样的东西

->add('preconditions', 'collection', array(
        'entry_type' => 'entity',
        'entry_options' => array(
            'class' => 'MyVendorCoreBundle:EduStructItem',
            'choice_label' => 'title'
        ),
        'allow_add' => true,
        'allow_delete' => true
    ))

而不是

->add('preconditions', 'choice', array(
        'choices' => $categories_choice,
        'multiple' => true,
        'mapped' => false
    ))