Symfony形式不保存具有ManyToMany关系的实体

时间:2015-05-26 16:07:37

标签: php symfony formbuilder

我在使用ManyToMany关系保存实体槽形式时遇到问题。

我无法保存关系“mappedBy”一侧的字段。

以下代码不会将任何内容保存到数据库中,也不会丢失任何错误:

transitionContext

它正在反过来。因此,如果我从CustomerType中保存一些东西,那么一切正常。

编辑:

下面的解决方案对我有用,但几天后我发现该解决方案存在问题。如果将使用已保存在数据库中的值提交表单,则Symfony将抛出错误。为了防止我必须检查给定的顾客是否已经分配给宠物。

检查当前分配的客户必须在功能开始时而不是在表单提交之后完成,因为在提交之后由于某种原因,Pet()对象包含提交的值,而不仅仅是存在于db中的值。

所以一开始我把所有已经分配的客户都放到了数组

containerView

表单提交后,我已检查提交的ID是否在数组中

// Entity/Pet
/**
 * @var \Doctrine\Common\Collections\Collection
 *
 * @ORM\ManyToMany(targetEntity="AppBundle\Entity\Customer", mappedBy="pet", cascade={"persist"})
 */
private $customer;

/**
 * Set customer
 *
 * @param \AppBundle\Entity\Customer $customer
 * @return Pet
 */
public function setCustomer($customer)
{
    $this->customer = $customer;

    return $this;
}

// Entity/Customer
/**
 * @var Pet
 *
 * @ORM\ManyToMany(targetEntity="AppBundle\Entity\Pet", inversedBy="customer", cascade={"persist"})
 * @ORM\JoinTable(name="customer_pet",
 *   joinColumns={
 *     @ORM\JoinColumn(name="customer_id", referencedColumnName="id")
 *   },
 *   inverseJoinColumns={
 *     @ORM\JoinColumn(name="pet_id", referencedColumnName="id")
 *   }
 * )
 */
private $pet;

// PetType.php
$builder->add('customer', 'entity', 
          array(
            'class' => 'AppBundle:Customer',
            'property' => 'firstname',
            'empty_value' => 'Choose owner',
            'multiple' => true
          ));

3 个答案:

答案 0 :(得分:1)

在Symfony2中,具有带有inversedBy教义注释的属性的实体是应该编辑由MANYTOMANY RELATION创建的EXTRA TABLE的实体。这就是为什么当您创建客户时,它会在该额外表中插入相应的行,从而保存相应的宠物。

如果您希望相反的行为发生,我建议:

//PetController.php
public function createAction(Request $request) {
    $entity = new Pet();
    $form = $this->createCreateForm($entity);
    $form->submit($request);



    if ($form->isValid()) {
        $em = $this->getDoctrine()->getManager();
        foreach ($form['customer']->getData()->getValues() as $v) {
            $customer = $em->getRepository('AppBundle:Customer')->find($v->getId());
            if ($customer) {
                $customer->addPet($entity);
            }
        }
        $em->persist($entity);
        $em->flush();

        return $this->redirect($this->generateUrl('pet_show', array('id' => $entity->getId())));
    }

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

private function createCreateForm(Pet $entity) {
        $form = $this->createForm(new PetType(), $entity, array(
            'action' => $this->generateUrl('pet_create'),
            'method' => 'POST',
        ));

        return $form;
    }

这两个是控制器中与Pet实体对应的标准Symfony2 CRUD生成的操作。

唯一的调整是在第一个操作中插入的 foreach 结构,这样您就可以强制将相同的宠物添加到您在表单中选择的每个客户,从而获得所需的行为。

看,很有可能这不是正确的方式,也不是正确的方式,但它是一种方式而且有效。希望它有所帮助。

答案 1 :(得分:0)

在我的服务<->项目场景中,服务具有“ inversedBy”,项目具有“ mappedBy”,我必须在项目控制器的编辑操作中执行此操作,以便在编辑项目时检查的服务将是坚持。

public function editAction(Request $request, Project $project = null)
{
    // Check entity exists blurb, and get it from the repository, if you're inputting an entity ID instead of object ...

    // << Many-to-many mappedBy hack
    $servicesOriginal = new ArrayCollection();
    foreach ($project->getServices() as $service) {
        $servicesOriginal->add($service);
    }
    // >> Many-to-many mappedBy hack

    $form = $this->createForm(ProjectType::class, $project);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $em = $this->getDoctrine()->getManager();

        // << Many-to-many mappedBy hack
        foreach ($servicesOriginal as $service) {
            if (!$project->getServices()->contains($service)) {
                $service->removeProject($project);
                $em->persist($service);
            }
        }

        foreach ($project->getServices() as $service) {
            $service->addProject($project);
            $em->persist($service);
        }
        // >> Many-to-many mappedBy hack

        $em->persist($project);
        $em->flush();

        return; // I have a custom `redirectWithMessage()` here, use what you like ...
    }

    return $this->render("Your-template", [
        $form       => $form->createView(),
        $project    => $project,
    ]);
}

这既适用于从“ mappedBy”侧添加或删除多对多实体,因此EntityType输入应按预期工作。

这是怎么回事,我们首先要构建一个“原始”集合,其中包含已经为此项目链接的所有服务实体。然后在保存表单时,我们确保:

  • 首先,所有未检查的服务(原始集合中的那些项目,而不是项目对象)都将项目从其内部集合中删除,然后保留下来。
  • 第二,任何新检查的服务都将项目添加到其内部集合中,然后保留下来。

重要:这分别取决于您实体的addService()addProject()方法,以检查彼此的集合是否不包含重复项。如果不这样做,最终将导致有关重复记录插入的SQL级别错误。

在服务实体中,我有:

/**
 * Add project
 *
 * @param Project $project
 *
 * @return Service
 */
public function addProject(Project $project)
{
    if (!$this->projects->contains($project)) {
        $this->projects->add($project);
    }

    if (!$project->getServices()->contains($this)) {
        $project->getServices()->add($this);
    }

    return $this;
}

在项目实体中,我有:

/**
 * Add service
 *
 * @param Service $service
 *
 * @return Project
 */
public function addService(Service $service)
{
    if (!$this->services->contains($service)) {
        $this->services->add($service);
    }

    if (!$service->getProjects()->contains($this)) {
        $service->getProjects()->add($this);
    }

    return $this;
}

您也可以在控制器中进行检查,但是如果模型在可能的情况下对其进行了验证,这是有意义的,因为如果有任何来源的重复,该模型还是会破坏。

最后,在控制器的创建操作中,您可能甚至在$em->persist($project)之前也需要此位。 (您将不需要使用“原始”集合,因为还不存在。)

// << Many-to-many mappedBy hack
foreach ($project->getServices() as $service) {
    $service->addProject($project);
    $em->persist($service);
}
// >> Many-to-many mappedBy hack

答案 2 :(得分:0)

我只是遇到了同样的问题,但我以不同的方式解决了。

更改控制器中的代码并不是更好的方法。 就我而言,我有一个 GenericController 来处理我所有的 CRUD,因此我无法将特定代码放入其中。

最好的方法是在你的 PetType 中添加一个这样的监听器:

    // PetType.php
    $builder->add('customer', 'entity', 
          array(
            'class' => 'AppBundle:Customer',
            'property' => 'firstname',
            'empty_value' => 'Choose owner',
            'multiple' => true
          ))
            ->addEventListener( FormEvents::SUBMIT, function( FormEvent $event ) {
                /** @var Pet $pet */
                $pet = $event->getData();
                foreach ( $pet->getCustomers() as $customer ) {
                    $customer->addPet( $pet );
                }
            } );

这样您就可以将映射逻辑保持在同一位置。