Cascading an association - symfony

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

Related

How to Persist CollectionType in 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.

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 :)

Insert current DateTime manually in phpmyadmin using symfony

So i send the data to the controller using ajax and i want to insert it in the comment table; everything works fine if i delete these two lines about inserting current datetime for the comment and setting my entity nullable
but how can i insert current datetime ;
Controller Code
if($request->get('texte')==NULL)
{
throw new AccessDeniedException('This user does not have access
to this section.');
}
$user = $this->getUser();
if (!is_object($user) || !$user instanceof UserInterface)
{
throw new AccessDeniedException('This user does not have access
to this section.');
}
$comment = new Commentaire();
$em = $this->getDoctrine()->getManager();
$veterinaire=$em->getRepository("MyAppUserBundle:User")->findOneById($request->get('cible'));
$comment->setIdCible($veterinaire);
$comment->setIdClient($user);
$comment->setTexte($request->get('texte'));
$literalTime = \DateTime::createFromFormat("d/m/Y H:i",date_default_timezone_get());
$comment->setDate($literalTime);
$em->persist($comment);
$em->flush();
return new Response("");
}
My Entity :
/**
* #var \DateTime
*
* #ORM\Column(name="date", type="datetime", nullable=false)
*/
private $date;
I even tried to set my AppKernel __construct
public function __construct($environment, $debug)
{
date_default_timezone_set( 'Africa/Tunis' );
parent::__construct($environment, $debug);
}
You just need to pass a DateTime object to setDate:
$comment->setDate(new \DateTime());
or if you want to specify timezone:
$comment->setDate(new \DateTime('now', new \DateTimeZone('Africa/Tunis')));
Of course you can extract creation of DateTime object from the setDate and have additional variable to pass.

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

Creating a new entity, and updating another at once

I've searched a lot about this, and seriously asking is my last resource, doctrine is kicking me hard.
I have an entity named "Contract" and another "Request", a Contract may have several Requests, when adding a new Request I search for an existent contract of that client and associate it if already exists or create it if not.
In RequestRepository.php:
public function findOrCreate($phone)
{
$em = $this->getEntityManager();
$contract = $this->findOneBy(array('phone' => $phone));
if($contract === null)
{
$contract = new Contract();
$contract->setPhone($phone)
->setDesDate(new \DateTime());
# save only if new
$em->persist($contract);
}
return $contract;
}
The thing is, when the contract is new it works ok, but when is "reused" from db I can't modify its attributes. I checked the OneToMany and ManyToOne already.
In Contract.php:
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
* #ORM\OneToMany(targetEntity="Request", mappedBy="contract")
*/
private $id;
In Request.php:
/**
* #var string
*
* #ORM\JoinColumn(nullable=false)
* #ORM\ManyToOne(targetEntity="Cid\FrontBundle\Entity\Contract", inversedBy="id", cascade={"persist"})
*/
protected $contract;
I also have a method which modifies an attribute within Contract.php:
public function addTime($months)
{
$days = $months * 30;
$this->des_date->add(new \DateInterval("P".$days."D"));
return $this;
}
I create the request and "findOrCreate" a contract, but if the later is not "fresh" the addTime does not save to db.
What am I doing wrong?
Edit: The controller is a common CRUD with minor modifications.
Don't worry about "request" name clash, the actual code is in spanish, Request = Solicitud
public function createAction(Request $req)
{
$entity = new Request();
$form = $this->createForm(new RequestType(), $entity);
$form->bind($req);
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$entity->setUser($this->getUser());
$data = $request->request->get('cid_frontbundle_requesttype');
$phone = $data['phone_number'];
$reqRep = $em->getRepository('FrontBundle:Request');
$entity = $reqRep->newRequest($entity, $phone);
return $this->redirect($this->generateUrl('request_show', array('id' => $entity->getId())));
}
return $this->render('FrontBundle:Request:new.html.twig', array(
'entity' => $entity,
'form' => $form->createView(),
));
}
The newRequest:
public function newRequest($request, $phone)
{
$em = $this->getEntityManager();
$contractRep = $em->getRepository('FrontBundle:Contract');
$contract = $contractRep->findOrCreate($phone);
$contract->addTime(123); # this is the problem, I use var_dump and this method works, but doesn't persists
$em->persist($request);
$em->flush();
return $request;
}
Eureka!! The issue was that doctrine seems to check the objects by reference, and all I did with the contract was adding a DateInterval to a DateTime property, so the object was the same for doctrine's matter and there was no saving. This is the code that made it.
public function addTime($months)
{
$days = $months * 30; # I know DateInterval has months but this is company policy ;)
$other = new \DateTime($this->des_date->format('Y-m-d')); # creating a brand new DateTime did the trick
$other->add(new \DateInterval("P".$days."D"));
$this->des_date = $other;
return $this;
}
Thanks for everything #cheesemacfly.

Resources