Symfony:为什么坚持尝试插入对象管理器检索的对象?

Symfony: Why is persist trying to insert an object retrieved by object manager?

如果之前有人问过这个问题,我们深表歉意。我仍在尝试掌握 Symfony 术语,但我的搜索还不是很好!

我有(为了这个问题的目的)两个实体:文章和作者。

我将作者表单嵌入到添加文章表单中。我想检查电子邮件是否已存在,如果存在,则只需更新该记录(相应的名称)而不是添加重复项。我正在使用变压器来执行此操作。

我可以在我的转换器中找到一个现有的作者。但是,当我在控制器中保留文章时,出现错误:

"An exception occurred while executing 'INSERT INTO author... Integrity constraint violation: 1062 Duplicate entry" ... etc...

我真的很困惑,因为我的理解是 persist 应该更新我刚刚从数据库中检索到的记录!

文章负责人:

    public function newAction(Request $request){
    $article = new Article();
    $form = $this->createForm('AppBundle\Form\ArticleType', $article);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {

        //Set the timestamps on article/author.
        $date = new \DateTime("now");
        $article->setCreatedDate($date);
        $article->getAuthor()->setCreatedDate($date);

        //Persist to database.
        $em = $this->getDoctrine()->getManager();
        $em->persist($article);
        $em->flush($article);

        return $this->redirectToRoute('article_show', array('id' => $article->getId()));
    }

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

文章类型:

namespace AppBundle\Form;

use Symfony\Component\Form\AbstractType;
use Symfony\Component\Form\FormBuilderInterface;
use Symfony\Component\OptionsResolver\OptionsResolver;
use Symfony\Component\Form\Extension\Core\Type\CollectionType;

class ArticleType extends AbstractType
{
    /**
     * {@inheritdoc}
     */
    public function buildForm(FormBuilderInterface $builder, array $options)
    {
        $builder->add('name')->add('description')->add('thumbnail');

        //We'll handle dates. Don't want users to access that.
        //$builder->add('createdDate');

        $builder->add('author', AuthorType::class, array("label" => FALSE));
    }

    /**
     * {@inheritdoc}
     */
    public function configureOptions(OptionsResolver $resolver)
    {
        $resolver->setDefaults(array(
            'data_class' => 'AppBundle\Entity\Article'
        ));
    }

    /**
     * {@inheritdoc}
     */
    public function getBlockPrefix()
    {
        return 'appbundle_article';
    }


}

文章实体:

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Component\Validator\Constraints as Assert;
/**
 * Article
 *
 * @ORM\Table(name="article")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\ArticleRepository")
 */
class Article
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="name", type="string", length=255, unique=true)
     */
    private $name;

    /**
     * @var string
     *
     * @ORM\Column(name="description", type="text", nullable=true)
     */
    private $description;

    /**
     * @var string
     *
     * @ORM\Column(name="thumbnail", type="string", length=255, nullable=true)
     */
    private $thumbnail;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="created_date", type="datetime")
     */
    private $createdDate;

    /**
     * @ORM\ManyToOne(targetEntity="Author", inversedBy="articles", cascade={"persist"})
     * @ORM\JoinColumn(name="author_id", referencedColumnName="id")
     * @Assert\Valid()
     */
    private $author;


    /**
     * @ORM\OneToMany(targetEntity="Review", mappedBy="article")
     */
    private $reviews;

    public function __construct()
    {
        $this->reviews = new ArrayCollection();
    }

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

    /**
     * Set name
     *
     * @param string $name
     *
     * @return Article
     */
    public function setName($name)
    {
        $this->name = $name;

        return $this;
    }

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

    /**
     * Set description
     *
     * @param string $description
     *
     * @return Article
     */
    public function setDescription($description)
    {
        $this->description = $description;

        return $this;
    }

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

    /**
     * Set thumbnail
     *
     * @param string $thumbnail
     *
     * @return Article
     */
    public function setThumbnail($thumbnail)
    {
        $this->thumbnail = $thumbnail;

        return $this;
    }

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

    /**
     * Set createdDate
     *
     * @param \DateTime $createdDate
     *
     * @return Article
     */
    public function setCreatedDate($createdDate)
    {
        $this->createdDate = $createdDate;

        return $this;
    }

    /**
     * Get createdDate
     *
     * @return \DateTime
     */
    public function getCreatedDate()
    {
        return $this->createdDate;
    }

    /**
     * Set authorId
     *
     * @param integer $authorId
     *
     * @return Article
     */
    public function setAuthorId($authorId)
    {
        $this->authorId = $authorId;

        return $this;
    }

    /**
     * Get authorId
     *
     * @return int
     */
    public function getAuthorId()
    {
        return $this->authorId;
    }

    /**
     * Set author
     *
     * @param \AppBundle\Entity\Author $author
     *
     * @return Article
     */
    public function setAuthor(\AppBundle\Entity\Author $author = null)
    {
        $this->author = $author;

        return $this;
    }

    /**
     * Get author
     *
     * @return \AppBundle\Entity\Author
     */
    public function getAuthor()
    {
        return $this->author;
    }

    /**
     * Add review
     *
     * @param \AppBundle\Entity\Review $review
     *
     * @return Article
     */
    public function addReview(\AppBundle\Entity\Review $review)
    {
        $this->reviews[] = $review;

        return $this;
    }

    /**
     * Remove review
     *
     * @param \AppBundle\Entity\Review $review
     */
    public function removeReview(\AppBundle\Entity\Review $review)
    {
        $this->reviews->removeElement($review);
    }

    /**
     * Get reviews
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getReviews()
    {
        return $this->reviews;
    }

    public function __toString() {
        return $this->name;
    }

}

作者实体:

<?php

namespace AppBundle\Entity;

use Doctrine\ORM\Mapping as ORM;
use Doctrine\Common\Collections\ArrayCollection;
use Symfony\Bridge\Doctrine\Validator\Constraints\UniqueEntity;

/**
 * Author
 *
 * @ORM\Table(name="author")
 * @ORM\Entity(repositoryClass="AppBundle\Repository\AuthorRepository")
 * @UniqueEntity(fields={"email"}, message="Note: author already existed. Using that record")
 */
class Author
{
    /**
     * @var int
     *
     * @ORM\Column(name="id", type="integer")
     * @ORM\Id
     * @ORM\GeneratedValue(strategy="AUTO")
     */
    private $id;

    /**
     * @var string
     *
     * @ORM\Column(name="email", type="string", length=255, unique=true)
     */
    private $email;

    /**
     * @var string
     *
     * @ORM\Column(name="first_name", type="string", length=255)
     */
    private $firstName;

    /**
     * @var string
     *
     * @ORM\Column(name="last_name", type="string", length=255)
     */
    private $lastName;

    /**
     * @var \DateTime
     *
     * @ORM\Column(name="created_date", type="datetime")
     */
    private $createdDate;

    /**
     * @ORM\OneToMany(targetEntity="Review", mappedBy="author")
     */
    private $reviews;

    /**
     * @ORM\OneToMany(targetEntity="article", mappedBy="author")
     */
    private $articles;

    public function __construct()
    {
        $this->reviews = new ArrayCollection();
        $this->articles = new ArrayCollection();
    }


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

    /**
     * Set email
     *
     * @param string $email
     *
     * @return Author
     */
    public function setEmail($email)
    {
        $this->email = $email;

        return $this;
    }

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

    /**
     * Set firstName
     *
     * @param string $firstName
     *
     * @return Author
     */
    public function setFirstName($firstName)
    {
        $this->firstName = $firstName;

        return $this;
    }

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

    /**
     * Set lastName
     *
     * @param string $lastName
     *
     * @return Author
     */
    public function setLastName($lastName)
    {
        $this->lastName = $lastName;

        return $this;
    }

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

    /**
     * Set createdDate
     *
     * @param \DateTime $createdDate
     *
     * @return Author
     */
    public function setCreatedDate($createdDate)
    {
        $this->createdDate = $createdDate;

        return $this;
    }

    /**
     * Get createdDate
     *
     * @return \DateTime
     */
    public function getCreatedDate()
    {
        return $this->createdDate;
    }

    /**
     * Add review
     *
     * @param \AppBundle\Entity\Review $review
     *
     * @return Author
     */
    public function addReview(\AppBundle\Entity\Review $review)
    {
        $this->reviews[] = $review;

        return $this;
    }

    /**
     * Remove review
     *
     * @param \AppBundle\Entity\Review $review
     */
    public function removeReview(\AppBundle\Entity\Review $review)
    {
        $this->reviews->removeElement($review);
    }

    /**
     * Get reviews
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getReviews()
    {
        return $this->reviews;
    }

    /**
     * Add article
     *
     * @param \AppBundle\Entity\article $article
     *
     * @return Author
     */
    public function addarticle(\AppBundle\Entity\article $article)
    {
        $this->articles[] = $article;

        return $this;
    }

    /**
     * Remove article
     *
     * @param \AppBundle\Entity\article $article
     */
    public function removearticle(\AppBundle\Entity\article $article)
    {
        $this->articles->removeElement($article);
    }

    /**
     * Get articles
     *
     * @return \Doctrine\Common\Collections\Collection
     */
    public function getarticles()
    {
        return $this->articles;
    }

    /**
     *Return boolean depending on if the author has already reviewed the article
     * @param \AppBundle\Entity\Author $author
     * @return bool
     */
    public function hasarticle(\AppBundle\Entity\article $article)
    {
        return $this->getarticles()->contains($article);
    }

    public function __toString() {
        return $this->email;
    }

}

非常感谢您提供的任何帮助。另外,这是处理重复电子邮件的最佳方式吗?我也想更新 firstname/lastname 而不是只让现有名称留在数据库中。我应该在坚持之前更新控制器中的名称还是应该以某种方式在转换器中完成?

谢谢!

Doctrine 尝试将新行插入数据库,因为您正在调用 persist($article)。如果您打算更新该行,则只需调用 flush():

$this->getDoctrine()->getManager()->flush();

对于这个例子,我使用了两个较轻的实体:

1) AppBundle/Entity/Article.php 实体仅包含 idnameauthor,其中:

/**
 * @ORM\ManyToOne(targetEntity="Author", inversedBy="articles")
 */
private $author;

public function setAuthor(\AppBundle\Entity\Author $author = null)
{
    $this->author = $author;
    return $this;
}

//getAuthor()...
//getters for id and name + setter for name

2) AppBundle/Entity/Author.php 实体仅包含 idemailarticles:

/**
 * @ORM\OneToMany(targetEntity="Article", mappedBy="author")
 */
private $articles;

//getters for id and email + setter for email

public function __construct()
{
    $this->articles = new \Doctrine\Common\Collections\ArrayCollection();
}

public function addArticle(\AppBundle\Entity\Article $article)
{
    $this->articles[] = $article;
    return $this;
}

public function removeArticle(\AppBundle\Entity\Article $article)
{
    $this->articles->removeElement($article);
}

public function getArticles()
{
    return $this->articles;
}

3) AppBundle/Form/ArticleType.php:

$builder
    ->add('name')
    ->add('author', EntityType::class, [
        'class' => 'AppBundle:Author',
        'placeholder' => ' ',
        'query_builder' => function(EntityRepository $er) {
            return $er->createQueryBuilder('a');
        },
        'choice_label' => function($author) {
            return $author->getEmail();
        },
        'multiple' => false,
        'expanded' => false
    ])
;

4) AppBundle/Controller/ArticleController.php

/**
 * @Route("/{id}/edit", name="article_edit")
 * @Method({"GET", "POST"})
 */
public function editAction(Request $request, Article $article)
{
    $form = $this->createForm('AppBundle\Form\ArticleType', $article);
    $form->handleRequest($request);

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

        return $this->redirectToRoute('article_edit', array('id' => $article->getId()));
    }

    return $this->render('article/edit.html.twig', array(
        'form' => $form->createView(),
    ));
}

5) app/Resources/views/article/edit.html.twig

{{ form(form, { attr:{ 'id':'form' }) }}
<button type="submit" form="form">Update</button>

基本上,这段代码是由 bin/console doctrine:generate:crud AppBundle:Article

  1. 在表单中添加隐藏类型的 ID 字段
  2. 在您的控制器操作中使用 merge,而不是 persist

表单类型:

use Symfony\Component\Form\Extension\Core\Type\HiddenType;

public function buildForm(FormBuilderInterface $builder, array $options)
{
    // Add hidden type of ID field
    $builder->add('id', HiddenType::class);

    $builder->add('name')->add('description')->add('thumbnail');

    //We'll handle dates. Don't want users to access that.
    //$builder->add('createdDate');

    $builder->add('author', AuthorType::class, array("label" => FALSE));
}

控制器操作:

public function newAction(Request $request)
{
    $article = new Article();
    $form = $this->createForm('AppBundle\Form\ArticleType', $article);
    $form->handleRequest($request);

    if ($form->isSubmitted() && $form->isValid()) {
        $date = new \DateTime("now");
        $article->setCreatedDate($date);
        $article->getAuthor()->setCreatedDate($date);

        $em = $this->getDoctrine()->getManager();

        // Use merge, not persist
        $em->merge($article);

        $em->flush($article);

        return $this->redirectToRoute('article_show', array('id' => $article->getId()));
    }

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