How to Persist CollectionType in Symfony? - symfony

I'm getting Following Error:
A new entity was found through the relationship 'App\Entity\Item#sizeQtyAttr' that was not configured to cascade persist operations for entity: App\Entity\SizeQtyAttributes#0000000078a80e200000000074b3b674. To solve this issue: Either explicitly call EntityManager#persist() on this unknown entity or configure cascade persist this association in the mapping for example #ManyToOne(..,cascade={"persist"}). If you cannot find out which entity causes the problem implement 'App\Entity\SizeQtyAttributes#__toString()' to get a clue.
Class Item
/**
* #ORM\ManyToOne(targetEntity=Category::class, inversedBy="items")
*/
private $category;
/**
* #ORM\OneToMany(targetEntity=SizeQtyAttributes::class, mappedBy="item")
*/
private $sizeQtyAttr;
CLass SizeQtyAttributes
/**
* #ORM\ManyToOne(targetEntity=Item::class, inversedBy="SizeQtyAttr")
*/
private $item;
Controller
public function postProduct(Request $request, SluggerInterface $slugger, CategoryTreeAdminOptionList $categories): Response
{
$item = new Item();
$form = $this->createForm(ItemType::class, $item);
$categories->getCategoryList($categories->buildTree());
$form->handleRequest($request);
if($form->isSubmitted() && $form->isValid()){
$item = $form->getData();
$item_category = $this->getDoctrine()->getRepository(Category::class)->find($request->request->get('item_category'));
//dd($item->getSizeQtyAttributes()->getAttrName());
$file_image = $form->get('image')->getData();
if($file_image){
//image stuff
}
$item->setCategory($item_category);
$em = $this->getDoctrine()->getManager();
$em->persist($item);
$em->flush();
return $this->redirectToRoute('post_product');
}
I tried with adding cascade={"persist"} but didn't work. Did I miss something? Please Help.

Related

Argument 1 passed to App\Entity\CatalogComment::setUserId() must be an instance of App\Entity\User or null, int given

I'm trying to save my ID in my relation ManyToOne, but an error returned:
This is how i'm trying to save data:
$user = $this->getUser()->getId();
$catalogcomment = new CatalogComment();
$form = $this->createForm(CatalogCommentType::class, $catalogcomment);
$form->handleRequest($request);
if ($form->isSubmitted() && $form->isValid()) {
$catalogcomment->setUserId($user);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($catalogcomment);
$entityManager->flush();
return $this->redirectToRoute('catalog_index');
}
And this is my Entity CatalogComment related with the relation user_id
public function getUserId(): ?User
{
return $this->user_id;
}
public function setUserId(?User $user_id): self
{
$this->user_id = $user_id;
return $this;
}
The error received is:
Argument 1 passed to App\Entity\CatalogComment::setUserId() must be an instance of App\Entity\User or null, int given
What i'm doing wrong?
Thanks for your time.
I think you have to adjust your mapped relationship in the Entity CatalogComment not to have a property $userId but instead a property $user which should be of type User
class CatalogComment
{
// ...
/**
* #ManyToOne(targetEntity="User")
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
}
You have to create getter and setter for $user too, and then you can set the user in an CatalogComment Object as follows
$user = $this->getUser();
$catalogComment = new CatalogComment();
$catalogComment->setUser($user);
$em = $this->getDoctrine()->getManager();
$em->persist($catalogComment);
$em->flush();
Hope it helps :)

Symfony doctrine ManyToMany unidirectional mapping

Well, back again, i'll try to simplify my question as much as i can.
First of all, i have 2 Entities
Post
PostRating
I've created unidirectional ManyToMany relation between them, because I only need ratings to be added to each Post, if I try to map Post to PostRating too, I get Circular Reference error.
Post Entity, it creates 3rd table post_has_rating, no mapping inside PostRating Entity, It workes like expected, rating collection is added to each post, but if i want to find one rating, and edit it if needed, then it comes to be bigger headache than expected.
/**
* Post have many PostRating
* #ORM\ManyToMany(targetEntity="PostRating")
* #ORM\JoinTable(name="post_has_rating",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="postrating_id", referencedColumnName="id", unique=true)}
* )
*/
protected $ratings;
PostController thumbAction, simple word "ratingAction"
/**
* Search related videos from youtube
* #Route("/post/thumb", name="post_thumb")
* #param Request $request
* #return string
*/
public function thumbAction (Request $request) {
$content = json_decode($request->getContent());
$serializer = $this->get('serializer');
$em = $this->getDoctrine()->getManager();
$postRatingRepo = $this->getDoctrine()->getRepository(PostRating::class);
$postRepo = $this->getDoctrine()->getRepository(Post::class);
$me = $this->getUser()->getId();
/** #var PostRating $rating */
$rating = $postRatingRepo->findOneBy(['userId' => $me]);
/** #var Post $post */
$post = $postRepo->find($content->id);
if ($post->getRatings()->contains($rating)) {
$post->removeRating($rating);
$em->remove($rating);
}
$rating = new PostRating();
$rating->setUserId($me);
switch ($content->action) {
//NVM about those hardcoded words, they are about to be removed
case 'up':
$rating->setRating(1);
break;
case 'down':
$rating->setRating(0);
break;
}
$post->addRating($rating);
$em->persist($rating);
$em->persist($post);
$em->flush();
return new JsonResponse( $serializer->normalize( ['success' => 'Post thumbs up created'] ) );
}
Problems: $rating = $postRatingRepo->findOneBy(['userId' => $me]); this row needs to have postId too for $post->getRatings()->contains($rating), right now im getting all the raitings, that I have ever created, but it Throws error if i add it, Unknown column
Should i create custom repository, so i can create something like "findRating" with DQL?
OR
Can i make Post and PostRating Entities mapped to each other more simple way, i don't really want many-to-many relation, because I don't see point of using it
Considering you want to keep OneToMany unidirectional here is my suggestion
create a custom repository for your Post Entity
namespace AppBundle\Repository;
use Doctrine\ORM\EntityRepository;
class PostRepository extends EntityRepository
{
public function findOneRatingByUser($post, $user)
{
$query = $this->createQueryBuilder('p')
->select('r')
->innerJoin('p.ratings', 'r')
->where('p.id = :post')
->andWhere('r.user = :user')
->setParameter('post', $post)
->setParameter('user', $user)
->getQuery()
;
return $query->getOneOrNullResult();
}
}
Then in your controller:
public function thumbAction (Request $request)
{
$content = json_decode($request->getContent());
$serializer = $this->get('serializer');
$em = $this->getDoctrine()->getManager();
$postRepo = $this->getDoctrine()->getRepository(Post::class);
$me = $this->getUser()->getId();
/** #var Post $post */
$post = $postRepo->find($content->id);
$rating = $postRepo->findOneRatingByUser($post->getId(), $me);
if (null === $rating) {
$rating = new PostRating();
$rating->setUserId($me);
}
switch ($content->action) {
//NVM about those hardcoded words, they are about to be removed
case 'up':
$rating->setRating(1);
break;
case 'down':
$rating->setRating(0);
break;
}
$post->addRating($rating);
$em->persist($rating);
$em->persist($post);
$em->flush();
return new JsonResponse( $serializer->normalize( ['success' => 'Post thumbs up created'] ) );
}
If you want your custom repository to work dont forget to declare it in your entity
/**
* #ORM\Entity(repositoryClass="AppBundle\Repository\PostRepository")
*/
class Post

Symfony2 Embedded forms, How to persist objects

I am trying to implement Embedded Forms (Symfony2, 2.7), with Task and Tag entities, One2Many.
To save reference to the Task object into a Tag record, I am able to define Task's createAction() only by:
/**
* Creates a new Task entity.
*
* #Route("/", name="MyName_Task_create")
* #Method("POST")
* #Template("MyNameBundleBlogBundle:Task:new.html.twig")
*/
public function createAction(Request $request)
{
$task = new Task();
$form = $this->createCreateForm($task);
$form->handleRequest($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$tags = $task->getTags();
foreach($tags as $tg){$tg->setTask($task); $em->persist($tg);} // <-- do I really need to loop?
$em->persist($task);
$em->flush();
return $this->redirect($this->generateUrl('MyName_Task_show', array('id' => $task->getId())));
}
return array(
'entity' => $task,
'form' => $form->createView(),
);
}
EDIT: I know it should work without the loop straightforwardly, but it does not. Question is: What should I look for which I might have written wrong? See related question
Note, I have:
class Task{
....
/**
*
* #ORM\OneToMany(targetEntity="Tag", mappedBy="Task", cascade={"persist"} )
*/
private $Tags;
....
/**
* Add tag
*
* #param \MyName\Bundle\BlogBundle\Entity\Tag $tag
*
* #return Task
*/
public function addTag(\MyName\Bundle\BlogBundle\Entity\Tag $tag)
{
$this->tags[] = $tag;
$tag->setTask($this);
return $this;
}
}
No, you don't need to loop through all tags and explicitly set task, Symfony will do that for you if you configure it correctly.
The only thing you need to add is set by_reference to false inside your form builder. In this case, symfony will explicitly will call setTask on every tag.
For more info 'by_reference'
According to #Cerad comment, the only thing you have to do is persist the Task.
All related tags will be automatically persisted, thanks to cascade={"persist"} in your association mapping.
Your code should be :
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($task);
$em->flush();
return $this->redirect($this->generateUrl('MyName_Task_show', array('id' => $task->getId())));
}
See the Etablishing associations and Cascade operations chapters of Doctrine documentation.

Symfony - Avoid store related entities in preFlush Doctrine

I have an entity Subject:
/**
*
* #ORM\Table()
* #ORM\HasLifecycleCallbacks()
*/
class Subject
{
//... Some fields
/**
* #ORM\OneToMany(targetEntity="Subject", mappedBy="mark", cascade={"persist", "remove"})
*/
private $subjects;
private function calculateMarks()
{
//... Do something
// return array with (abilities => marks);
}
/**
* #ORM\PrePersist()
*/
public function prePersist(){
$now = new \DateTime();
$this->setCreatedAt( $now );
$this->setModifiedAt( $now );
}
/**
* #ORM\PreUpdate()
*/
public function preUpdate(){
$this->setModifiedAt( new \DateTime() );
$this->setUpdated(true);
}
/**
* #ORM\PreFlush()
*/
public function preFlush(){
$marks = calculateMarks();
foreach($marks as $ability => $score){
$mark = new Mark();
$mark->setSubject( $this );
$this->addMark( $score );
$mark->setAbility( $ability );
}
}
}
and the class Mark:
class Mark{
// Many fields
/**
* #ORM\ManyToOne(targetEntity="Subject", inversedBy="subjects")
* #ORM\JoinColumn(name="subject_id", referencedColumnName="id")
*/
private $subject;
}
My problem is that I calculate and I create the Marks in the preFlush event (this is done this because in the official documentation is said this about preUpdate event: "Changes to associations of the updated entity are never allowed in this event, since Doctrine cannot guarantee to correctly handle referential integrity at this point of the flush operation"). When I save one subject, all work fine, but when I save many Subjects at the same time in a webservice, some marks are stored in the database many times.
The webservice action below:
public function setSubjects(Request $request)
{
//... Do something
$subjects = $request["Subjects"];
foreach($subjects as $s){
$em = $this->getDoctrine()->getManager();
//... Do something
$em->persist($s);
$em->flush();
}
return new JsonResponse($response);
}
Has anybody an idea of how could I avoid this behavior in the preFlush event?
Thanks in advance.
I always try to avoid LifecycleCallbacks unless it's simple and i'm only changing properties in the same entity.
to solve your issue i would create a function calculateMarks() inside the entity and tweak my loop to be something like
$em = $this->getDoctrine()->getManager();
foreach($subjects as $s){
//... Do something
$s->calculateMarks();
$em->persist($s);
}
$em->flush();
NOTICE
avoid $em = $this->getDoctrine()->getManager(); & $em->flush(); inside the loop

Cascading an association

I am trying to embed a collection of forms, but I am having trouble persisting the newly created objects.
A Customer has many Emails. In my controller,
// CustomersController.php
$customer = new Customer();
$customer->setCreatedBy(0);
$blankEmail = new Email();
$customer->addEmail($blankEmail);
$form = $this->createForm(new CustomerType(), $customer);
I did remember to set the cascade option in my Customer class:
// Customer.php
...
/**
* #ORM\OneToMany(targetEntity="Email", mappedBy="customer", cascade={"persist"})
*/
protected $emails;
My Email class also has the required information:
// Email.php
...
/**
* #ORM\ManyToOne(targetEntity="Customer", inversedBy="emails", cascade={"persist"})
* #ORM\JoinColumn(name="customers_id", referencedColumnName="id")
*/
protected $customer;
For some reason, this doesn't work:
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($customer);
$em->flush();
It adds the customer alright, but when it tries to add the Email, it says no customerId has been set. So, I tried this, and it works:
if ($request->isMethod('POST')) {
$form->bind($request);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
foreach ($customer->getEmails() as $email)
$email->setCustomer($customer);
$em->persist($email);
}
$em->persist($customer);
$em->flush();
But I'd really like to be able to get it in one fell swoop, just by persisting the $customer object (as I know it can).
Try to change the default "addEmail" method in your Customer class.
This method should look like:
public function addEmail($email)
{
$email->setCustomer($this);
$this->emails[] = $email;
return $this;
}

Resources