ManyToMany relation is not persisted - php

I have Post And PostTag entities. Only custom users are allowed create PostTags. While creating a post user may select post tags.
I have created many to many relationship between Post And PostTag entities using the make:entity command.
The problem is that after creating the post and attaching to it the selected tags the relation table is empty and nothing is returned by post.getPostTags() method.
PostController - create method:
...
$em = $this->getDoctrine()->getManager();
$post = $form->getData();
$post->setAuthor($this->getUser());
$post->setCreatedAt(new DateTime);
foreach ($form->get('post_tags')->getData() as $postTag) {
$post->addPostTag($postTag);
}
$em->persist($post);
$em->flush();
...
Post entity:
/**
* #ORM\ManyToMany(targetEntity="App\Entity\PostTag", mappedBy="post", cascade={"persist"})
*/
private $postTags;
public function __construct()
{
$this->postTags = new ArrayCollection();
}
/**
* #return Collection|PostTag[]
*/
public function getPostTags(): Collection
{
return $this->postTags;
}
public function addPostTag(PostTag $postTag): self
{
if (!$this->postTags->contains($postTag)) {
$this->postTags[] = $postTag;
$postTag->addPost($this);
}
return $this;
}
public function removePostTag(PostTag $postTag): self
{
if ($this->postTags->contains($postTag)) {
$this->postTags->removeElement($postTag);
$postTag->removePost($this);
}
return $this;
}
PostTag entity:
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Post", inversedBy="postTags", cascade={"persist"})
*/
private $post;
public function __construct()
{
$this->post = new ArrayCollection();
}
/**
* #return Collection|Post[]
*/
public function getPost(): Collection
{
return $this->post;
}
public function addPost(Post $post): self
{
if (!$this->post->contains($post)) {
$this->post[] = $post;
}
return $this;
}
public function removePost(Post $post): self
{
if ($this->post->contains($post)) {
$this->post->removeElement($post);
}
return $this;
}

You have set the PostTag entity as the owning side (by virtue of setting inversedBy parameter on that entity), so you have to persist the PostTag entity, not the Post entity, to make things stick. You can make a simple modification to your Post entity to make this work:
public function addPostTag(PostTag $postTag): self
{
if (!$this->postTags->contains($postTag)) {
$this->postTags[] = $postTag;
$postTag->addPost($this);
// set the owning side of the relationship
$postTag->addPost($this);
}
return $this;
}

Related

Passing POST json object for ArrayCollection data by ajax in Symfony

I want to insert Order data in my Symfony 5 application. OrderDetails ArrayCollection data of an Order entity class. Order and OrderDetails ArrayCollection data get by JSON object ajax post. How to passing POST json object for ArrayCollection data by ajax in Symfony.
Entity Code:
class Order
{
public const NUM_ITEMS = 10;
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $orderNo;
/**
* #ORM\Column(type="datetime")
*/
private $orderDate;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\OneToMany(targetEntity=OrderDetail::class, mappedBy="orders")
*/
private $orderDetails;
public function __construct()
{
$this->orderDetails = new ArrayCollection();
}
public function getId(): ?int
{
return $this->id;
}
public function getOrderNo(): ?string
{
return $this->orderNo;
}
public function setOrderNo(string $orderNo): self
{
$this->orderNo = $orderNo;
return $this;
}
public function getOrderDate(): ?\DateTimeInterface
{
return $this->orderDate;
}
public function setOrderDate(\DateTimeInterface $orderDate): self
{
$this->orderDate = $orderDate;
return $this;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
/**
* #return Collection|OrderDetail[]
*/
public function getOrderDetails(): Collection
{
return $this->orderDetails;
}
public function addOrderDetail(OrderDetail $orderDetail): self
{
if (!$this->orderDetails->contains($orderDetail)) {
$this->orderDetails[] = $orderDetail;
$orderDetail->setOrders($this);
}
return $this;
}
public function removeOrderDetail(OrderDetail $orderDetail): self
{
if ($this->orderDetails->contains($orderDetail)) {
$this->orderDetails->removeElement($orderDetail);
// set the owning side to null (unless already changed)
if ($orderDetail->getOrders() === $this) {
$orderDetail->setOrders(null);
}
}
return $this;
}
}
JS File Code:
// Creating Order Json Object
var orderObj = { "orderNo":"", "orderDate":"", "name":"" };
orderObj.orderNo = $("#text_name").val();
orderObj.orderDate = $("#text_mobileno").val();
orderObj.name = $("#text_email").val();
// Set 2: Ajax Post
// Here i have used ajax post for saving/updating information
$.ajax({
type: 'POST',
contentType: 'application/json;',
url:'/cart/ordersave',
data: JSON.stringify(orderObj),
dataType: 'json',
success: function (response)
{
// alert(response['data']);
//alert(1);
},
error:function(){
alert('ajax failed');
}
});
Controller Code:
/**
* #Route("/cart/ordersave", name="cart_order_save", methods={"POST"})
*
*/
public function ordersave(Request $request, SessionInterface $session)
{
if ($request->isXMLHttpRequest()) {
$content = $request->getContent();
if (!empty($content)) {
$params = json_decode($content, true);
$order = new Order();
$order->setOrderNo('ON-101/20');
$order->setOrderDate(new \DateTime());
$order->setName($params['name']);
$order->setMobileNo($params['mobileno']);
$order->setEmail($params['email']);
$order->setDeliveryAddress($params['address']);
$order->setCity($params['city']);
$order->setState($params['state']);
$order->setZipcode($params['zipcode']);
$order->setPaymentBy(1);
$order->setDeliveryDate(new \DateTime());
$em = $this->getDoctrine()->getManager();
$em->persist($order);
$em->flush();
$lastId = $order->getId();
$session->set('lastOrderIDSession', $lastId);
}
$this->addFlash('notice', 'Order created successfully!');
return new JsonResponse(array('data' => $lastId));
// return new JsonResponse(array('data' => $params));
}
return new Response('Error!', 400);
}
How to get ArrayCollection data in the controller and insert its database table.
I'd advise you using a symfony form, with a form collection, and it will work by itself. You seem to want to use ajax, and even with forms, you can submit the form in javascript without reloading the page.
This will help : https://symfony.com/doc/current/form/form_collections.html
If you really don't want to do that, well you totally can submit an array with the order details data, then iterate on it, create an OrderDetail entity for each, persist them, etc...

How to fix error with multiple upload with Symfony 4 NoSuchPropertyException

I want to add a field for upload many attached files in a form with Symfony 4. I've two Entity, Member and Documents with a One-To-Many relation but I've an error who doesn't mean anything for me.
In my Entity Document I've this :
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Member", inversedBy="document")
*/
private $member;
/**
* #return mixed
*/
public function getFile()
{
return $this->file;
}
/**
* #param mixed $file
*/
public function setFile($file): void
{
$this->file = $file;
}
public function getMember(): ?Member
{
return $this->member;
}
public function setMember(?Member $member): self
{
$this->member = $member;
return $this;
}
In my Member Entity :
/**
* #ORM\Entity(repositoryClass="App\Repository\MemberRepository")
*/
class Member
{
/**
* #ORM\OneToMany(targetEntity="App\Entity\Documents", mappedBy="member", cascade={"persist"}, orphanRemoval=true)
*/
private $document;
public function __construct()
{
$this->years = new ArrayCollection();
$this->document = new ArrayCollection();
$this->atelier = new ArrayCollection();
}
/**
* #return Collection|Documents[]
*/
public function getDocument(): Collection
{
return $this->document;
}
public function addDocument(Documents $document): self
{
if (!$this->document->contains($document)) {
$this->document[] = $document;
$document->setMember($this);
}
return $this;
}
public function removeDocument(Documents $document): self
{
if ($this->document->contains($document)) {
$this->document->removeElement($document);
// set the owning side to null (unless already changed)
if ($document->getMember() === $this) {
$document->setMember(null);
}
}
return $this;
}
}
I've try to dump the value of my form but when I try to send my form, I've this error when I don't understand
Could not determine access type for property "document" in class "App\Entity\Member": The property "document" in class "App\Entity\Member" can be defined with the methods "addDocument()", "removeDocument()" but the new value must be an array or an instance of \Traversable, "App\Entity\Documents" given.

Symfony ManyToMany setter return wrong data

I have relation User to Coupon ManyToMany.
User have many coupons and coupon may belong to many users.
When I call the method $coupon->getUsers(), I get coupon (PersistentCollection).
And when I call the method $user->getCoupon(), I get user (PersistentCollection).
User entity:
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Coupon", inversedBy="users")
*/
private $coupon;
public function __construct()
{
$this->coupon = new ArrayCollection();
}
/**
* #return Collection|Coupon[]
*/
public function getCoupon(): Collection
{
return $this->coupon;
}
public function addCoupon(Coupon $coupon): self
{
if (!$this->coupon->contains($coupon)) {
$this->coupon[] = $coupon;
}
return $this;
}
public function removeCoupon(Coupon $coupon): self
{
if ($this->coupon->contains($coupon)) {
$this->coupon->removeElement($coupon);
}
return $this;
}
Coupon entity:
/**
* #ORM\ManyToMany(targetEntity="App\Entity\User", mappedBy="coupon")
*/
private $users;
public function __construct()
{
$this->users = new ArrayCollection();
}
/**
* #return Collection|User[]
*/
public function getUsers(): Collection
{
return $this->users;
}
public function addUser(User $user): self
{
if (!$this->users->contains($user)) {
$this->users[] = $user;
$user->addCoupon($this);
}
return $this;
}
public function removeUser(User $user): self
{
if ($this->users->contains($user)) {
$this->users->removeElement($user);
$user->removeCoupon($this);
}
return $this;
}
When I run this code:
namespace App\Controller;
use App\Entity\Coupon;
use Symfony\Bundle\FrameworkBundle\Controller\AbstractController;
use Symfony\Component\Routing\Annotation\Route;
class TestController extends AbstractController
{
/**
* #Route("/test", name="test")
*/
public function index()
{
$coupon = $this->getDoctrine()->getRepository(Coupon::class)->find(1);
dump($coupon->getUsers());die;
}
}
I get:
screenshot
Why I get a coupon and not a list of users?
On top of what Jakumi wrote, in the controller you can also do
$coupon = $this->getDoctrine()->getRepository(Coupon::class)->find(1);
$users = $coupon->getUsers();
$users->initialize();
Now when you dump($users) the collection should not be empty.
To add to that, I believe you have your mapping wrong. In your Many-To-Many relation the User is the owning side and Coupon is the inversed side, however it is the public function addUser(User $user) in the Coupon entity that does the owning side's job. You should either change the sides (change the mappedBy in Coupon to inversedBy and the other way around in the User) or make sure that User does:
public function addCoupon(Coupon $coupon): self
{
if (!$this->coupon->contains($coupon)) {
$coupon->addUser($this);
$this->coupon[] = $coupon;
}
return $this;
}
and the Coupon does:
public function addUser(User $user): self
{
if (!$this->users->contains($user)) {
$this->users[] = $user;
}
return $this;
}
Of course the removeUser and removeCoupon methods should be dealth with accordingly.
PersistentCollections conceptually are supposed to work like arrays and are the way of doctrine to realize lazy loading (the default). There are certain operations that will trigger the collection to be loaded from the database (such as iterating over the collection). Before that, it's property initialized will be false (as in your screenshot)
ManyToMany and OneToMany should always be realized as ArrayCollection (or some other collection, such as PersistentCollection) and should not be leaked to the outside. Instead call ->toArray() (or ->asArray(), I always forget) to return them (so, inside getUsers() or getCoupons() respectively). Inside the entity you can just foreach over the PersistentCollection.
If you mark the ManyToMany to fetch as EAGER, it will be loaded immediately, but that might have performance impact...
And the Collection holds a reference to the object it belongs to, so you're not getting a Coupon per se, you get a collection, that still references its owner ;o)

Symfony4 setter where getter match id route?

I'm kindly new on Symfony
I'm doing a vote system but i guess this should work for like,
At the moment my controller function is this, this only create a new row with 1vote, but not update any $id created before.
/**
* #Route("/public/{id}/vote", name="poll_vote", methods="GET|POST")
*/
public function vote(Request $request, Poll $poll): Response
{
$inc = 1;
$em = $this->getDoctrine()->getManager();
$entity = new Poll();
$entity->setVotes($inc++);
$em->persist($entity);
$em->flush();
}
return $this->redirectToRoute('poll_public');
}
This is my button from twig template
<a href="{{ path('poll_vote', {'id': poll.id}) }}">
An this is my entity
class Poll
{
/**
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private $id;
/**
* #ORM\Column(type="string", length=255)
*/
private $name;
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $votes;
public function getId(): ?int
{
return $this->id;
}
public function getName(): ?string
{
return $this->name;
}
public function setName(string $name): self
{
$this->name = $name;
return $this;
}
public function getVotes(): ?int
{
return $this->votes;
}
public function setVotes(?int $votes): self
{
$this->votes = $votes;
return $this;
}
}
I have no idea about how can match my getID from my entity and match for the $id from the #Route.
Any guide or suggestion would be really appreciate.
Thanks
EDIT:
Updated with the correct function after Arne answer:
/**
* #Route("/public/{id}", name="poll_vote", methods="GET|POST")
*/
public function vote($id)
{
$entityManager = $this->getDoctrine()->getManager();
$poll = $entityManager->getRepository(Poll::class)->find($id);
if (!$poll) {
throw $this->createNotFoundException(
'No polls found for id '.$id
);
}
$poll->setVotes($poll->getVotes()+1);
$entityManager->flush();
return $this->redirectToRoute('poll_public', [
'id' => $poll->getId()
]);
}
basically you have to get the ID from your request, query the Entitty Repository for your Poll Entity, update the votes and persist it back to your database.
Get the ID from your request
$id = $request->query->get('id');
Query the repository:
$entityManager = $this->getDoctrine()->getManager();
$poll= $entityManager->getRepository(Poll::class)->find($id);
Update the votes:
$poll->setVotes($poll->getVotes()+1);
Persist to the DB:
$entityManager->persist($poll);
$entityManager->flush();
Alternatively you could also use the ParamConverter to let Symfony get the Poll object for you. More information about updating objects can be found in the Doctrine Guide.
Note that yor route will only match existing polls, since id is a required parameter in the URL. You might add another route without an ID which is being used for creating new Poll entities.

Set a list of images to an entity with VichUploaderBundle

I have an entity "Comment", and a "Comment" could have one or more images associated.
How do i achieve that.
Now i have this(for just one image):
/**
* #Assert\File(
* maxSize="1M",
* mimeTypes={"image/png", "image/jpeg"}
* )
* #Vich\UploadableField(mapping="comment_mapping", fileNameProperty="imageName")
*
* #var File $image
*/
protected $image;
Thanks in advance
You have to create a ManyToOne relationship between your Comment and Image entities.
Read more on associations with doctrine 2 here.
Comment
/**
* #ORM\ManyToOne(targetEntity="Image", inversedBy="comment")
*/
protected $images;
public function __construct()
{
$this->images = new ArrayCollection();
}
public function getImages()
{
return $this->images;
}
public function addImage(ImageInterface $image)
{
if (!$this->images->contains($image)) {
$this->images->add($image);
}
return $this;
}
public function removeImage(ImageInterface $image)
{
$this->images->remove($image);
return $this;
}
public function setImages(Collection $images)
{
$this->images = $images;
}
// ...
Image
protected $comment;
public function getComment()
{
return $this->comment;
}
public function setComment(CommentInterface $comment)
{
$this->comment = $comment;
return $this;
}
// ...
Then add a collection form field to your CommentFormType with "type" of ImageFormType ( to be created ).

Categories