Symfony: почему при попытке сохранения объекта, извлеченного менеджером объектов, не удается продолжить?

Извиняюсь, если об этом уже спрашивали. Я все еще пытаюсь понять термины Symfony, но мои поиски не так уж хороши!

У меня есть (для целей этого вопроса) две сущности: статья и автор.

Я встраиваю форму автора в форму добавления статьи. Я хотел бы проверить, существует ли уже электронная почта, и, если это так, просто обновить эту запись (соответствующее имя) вместо добавления дубликата. Я использую трансформатор, чтобы сделать это.

Я могу найти существующего автора в моем преобразователе. Однако, когда я сохраняю статью в контроллере, я получаю сообщение об ошибке:

«Возникла исключительная ситуация при выполнении автора INSERT INTO …
Нарушение ограничения целостности: 1062 Повторяющаяся запись «… и т.д …

Я действительно запутался, потому что, насколько я понимаю, настойчивость должна обновлять запись, которую я только что извлек из базы данных!

Контроллер статьи:

    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';
}}

Статья Entity:

<?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;
}

}

Заранее большое спасибо за любую помощь, которую вы можете оказать. Кроме того, это лучший способ обрабатывать дубликаты электронных писем? Я также хотел бы обновить имя / фамилию, вместо того, чтобы просто оставить существующее имя в базе данных. Должен ли я обновить имя в контроллере перед сохранением или это как-то должно быть сделано в преобразователе?

Спасибо!

0

Решение

Doctrine пытается вставить новую строку в базу данных, потому что вы звоните persist($article), Если вы хотели обновить строку, просто позвоните flush():

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

LE

Для этого примера я использую два более легких объекта:

1) AppBundle/Entity/Article.php сущность содержит только id, name, а также author, с:

/**
* @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 сущность содержит только id, email, а также articles:

/**
* @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

Другие решения

  1. Добавить скрытый тип поля идентификатора в вашей форме
  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(),
));
}
0

По вопросам рекламы [email protected]