Looping through entities and updating them causes error on flush - symfony

I am new to symfony and doctrine. And I am compeleting a code that someone else has started. I mainly have a form for which I wrote a validation function in my controller. In this form a BusReservation object along with its BusReservationDetails are created and saved to the db. so at the end of the form validation function, after the entities are saved in DB, I call a BusReservation Manager method which is transformBusReservationDetailIntoBusTicket which aim is to take each BusReservationDetail in the BusReservation oject and create a a new entity BusTicket based on it.
so I created this loop (please let me know if there is something wrong in my code so that i can write in a good syntax). I tried to put the 3 persist that you see at the end of the code but I got : Notice: Undefined index: 0000000..
I tried to merge (the last 3 lines in code ) I got the following :
A new entity was found through the relationship 'MyBundle\Entity\CustomInfo#busTicket' that was not configured to cascade persist operations for entity: . 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"}).
I got this same error when i commented all theh 6 lines of merge and flush.
PS: I am not expecting the flush to fully work. There are some properties that are nullable=false so I assume that I must set them as well so that the entities can be saved to DB. But the error i got is by far different than this.
PS : I noticed that there is a onFlush where the customInfo is updated and persisted again and other things happen, but i am trying to debug step by step. I tried to detach this event but still got the same errors. so I want to fix my code and make sure that the code part that i wrote in the manager is correct and if that's the case then I can move to debugging the event Listener. so please I would like to know if the following code is correct and why the flush is not working.
/**
* #param $idBusReservation
* #return bool
* #throws \Doctrine\ORM\NonUniqueResultException
*/
public function transformBusReservationIntoBusTicket($idBusReservation): bool
{ $result = "into the function";
/** #var BusReservation $busReservation */
$busReservation = $this->em->getRepository('MyBundle:BusReservation')->find($idBusReservation);
if ($busReservation !== null) {
/** #var BusReservationDetail $busReservationDetail */
foreach ($busReservation->getBusReservationDetails() as $busReservationDetail) {
$busTicket = new BusTicket($busReservationDetail->getBusModel(), $busReservation->getPassenger());
$busReservationDetail->setBusTicket($busTicket);
$busTicket->setBusReservationDetail($busReservationDetail);
$busTicket->setOwner($busreservation->getPassenger()->getName());
if ($busReservationDetail->getBusModel()->getCode() === 'VIPbus') {
// perform some logic .. later on
} else {
$customInfo = new CustomInfo();
$customInfo->setNumber(1551998);
// $customInfo->setCurrentMode(
// $this->em->getRepository('MyBundle:Mode')
// ->find(['code' => 'Working'])
// );
$customInfo->setBusTicket($busTicket);
// Bus ticket :
$busTicket->addCustomInfo($customInfo);
$busTicket->setComment($busReservation->getComment());
}
/** #var Mode $currentMode */
$currentMode = $this->em->getRepository('MyBundle:Mode')
->findOneBy(['code' => 'Working']);
$busTicket->setCurrentMode($currentMode);
// $this->em->merge($customInfo);
// $this->em->merge($busReservationDetail);
// $this->em->merge($busTicket);
// $this->em->persist($customInfo);
// $this->em->persist($busReservationDetail);
// $this->em->persist($busTicket);
}
$this->em->flush();
// $this->em->clear();
}
return $result;
}
// *************** In BusReservation.php ********************
/**
* #ORM\OneToMany(targetEntity="MyBundle\Entity\BusReservationDetail", mappedBy="busReservation")
*/
private $busReservationDetails;
/**
* Get busReservationDetails
*
*#return Collection
*/
public function getBusReservationDetails()
{
return $this->busReservationDetails;
}
// ---------------------------------------------------------------------
// *************** In BusReservationDetail.php ********************
/**
* #ORM\ManyToOne(targetEntity="MyBundle\Entity\BusReservation", inversedBy="busReservationDetails")
* #ORM\JoinColumn(name="id_bus_reservation", referencedColumnName="id_bus_reservation", nullable=false)
*/
private $busReservation;
/**
* #ORM\ManyToOne(targetEntity="MyBundle\Entity\BusModel")
* #ORM\JoinColumn(name="bus_model_code", referencedColumnName="bus_model_code", nullable=false)
*/
private $busModel;
/**
* #ORM\OneToOne(targetEntity="MyBundle\Entity\BusTicket", inversedBy="busReservationDetail", cascade={"merge","remove","persist"})
* #ORM\JoinColumn(name="id_bus_ticket", referencedColumnName="id_bus_ticket")
*/
private $busTicket;
/**
* #return BusModel
*/
public function getBusModel()
{
return $this->busModel;
}
//-------------------------------------------------------------------------
// ************ IN BusTicket.php *****************************
/**
* #ORM\OneToMany(targetEntity="MyBundle\Entity\CustomInfo", mappedBy="busTicket")
*/
private $customInfos;
/**
*
* #param CustomInfo $customInfo
*
* #return BusTicket
*/
public function addCustomInfot(CustomInfo $customInfo)
{
if (!$this->customInfos->contains($customInfo)) {
$this->customInfos[] = $customInfo;
}
return $this;
}
/**
* #ORM\OneToOne(targetEntity="MyBundle\Entity\busReservationDetail", mappedBy="busTicket")
*/
private $busReservationDetail;
// --------------------------------------------------------------------
// CUSTOMINFO ENTITY
/**
* #ORM\ManyToOne(targetEntity="MyBundle\Entity\BusTicket", inversedBy="customInfos")
* #ORM\JoinColumn(name="id_bus_ticket", referencedColumnName="id_bus_ticket", nullable=false)
*/
private $busTicket;

The answer is in your error message. You either have to add cascade={"persist"} to your entity annotation, or explicitly call persist. I don't believe you need em->merge() in this situation as you're never taking the entities out of context.
Where you have all your persist lines commented out, just try putting this in
$this->em->persist($busTicket);
$this->em->persist($busReservationDetail);
$this->em->persist($customInfo);
and if you're looping through a ton of entities, you could try adding the flush inside the loop at the end instead of a huge flush at the end.

Related

The identifier generation strategy for this entity requires the ID field to be populated before EntityManager#persist() is called

I'm tying to create one to many relations
A have class
class Interview {
/**
* #OneToMany(targetEntity="Question", mappedBy="question")
*/
private $questions;
public function __construct() {
$this->questions = new ArrayCollection();
}
public function __toString() {
return $this->id;
}
/**
* #return Collection|Question[]
*/
public function getQuestions() {
return $this->questions;
}
/**
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
......
}
another
class Question {
/**
* #ManyToOne(targetEntity="Interview", inversedBy="interview")
* #JoinColumn(name="interview_id", referencedColumnName="id")
*/
private $interview;
public function getInterview() {
return $this->interview;
}
public function setInterview(Interview $interview) {
$this->interview = $interview;
return $this;
}
/**
* #ORM\Column(type="integer")
* #ORM\Id
*/
private $interview_id;
......
}
and Controller for all this
if ($form->isSubmitted() && $form->isValid()) {
$interview = new Interview();
$question = new Question();
$em->persist($interview);
$question->setInterview($interview);
$question->setTitle($request->get('title'));
$em->persist($question);
$em->flush();
return $this->redirectToRoute('homepage');
}
i'm receiving an error:
Entity of type AppBundle\Entity\Question is missing an assigned ID for
field 'interview_id'. The identifier generation strategy for this
entity requires the ID field to be populated before
EntityManager#persist() is called. If you want automatically generated
identifiers instead you need to adjust the metadata mapping
accordingly.
Don't understand what the problem and how to fix it.
To enforce loading objects from the database again instead of serving them from the identity map. You can call $em->clear(); after you did $em->persist($interview);, i.e.
$interview = new Interview();
$em->persist($interview);
$em->clear();
It seems like your project config have an error in doctrine mapped part.
If you want automatically generated identifiers instead you need to
adjust the metadata mapping accordingly.
Try to see full doctrine config and do some manipulation with
auto_mapping: false
to true as example or something else...
Also go this , maybe it will be useful.
I am sure, its too late to answer but maybe someone else will get this error :-D
You get this error when your linked entity (here, the Interview entity) is null.
Of course, you have already instantiate a new instance of Interview.But, as this entity contains only one field (id), before this entity is persited, its id is equal to NULL. As there is no other field, so doctrine think that this entity is NULL. You can solve it by calling flush() before linking this entity to another entity

Doctrine inserts twice when adding one related entity and using $em->clear()

When I try to add new info item it either inserts two of them or none. In the state of code below it inserts two. If i comment something it inserts none. When I comment the line with $em->clear() it inserts exactly one, as I need it. What I don't understand and what I'm doing wrong?
$limit = 10;
$offset = 0;
do {
$products = $selectedModelQuery->getQuery()
->setFirstResult($limit * $offset)
->setMaxResults($limit)->getResult();
$offset++;
$count = count($products);
/** #var \Nti\ApiBundle\Entity\Product $product */
foreach ($selectedModelQuery->getQuery()->getResult() as $product) {
if (!$product->getCollections()->contains($productCollection)) {
$product->addCollection($productCollection);
$productInfo = new ProductInfo;
$productInfo->setProduct($product);
$productInfo->setData($productCollection->getMessage());
$productInfo->setInfoType(ProductInfoInterface::TYPE_CUSTOM);
//$em->merge($product);
//$em->persist($productInfo);
}
}
$em->flush();
$em->clear();
} while ($count);
Main Entity:
class Product {
/**
* #ORM\OneToMany(targetEntity="ProductInfo", mappedBy="product", cascade={"persist", "remove"})
* #var ProductInfoInterface[]|ArrayCollection
*/
protected $infoList;
....
}
Related Entity:
class ProductInfo {
/**
* #ORM\ManyToOne(targetEntity="\Nti\ApiBundle\Entity\Product", inversedBy="infoList")
* #ORM\JoinColumn(name="product_id", referencedColumnName="id", onDelete="CASCADE", nullable=false)
*/
protected $product;
...
}
Doctrine is based on Identity Map that keeps reference to all handled object (ie.: if you try to query two times for the same record by id, first time you hit the db but the second not and object is loaded from Identity Map).
When you use $em->clear() you are "discarding" all objects into Identity Map so, next iteration of do ... while will result in a brand new object (from ORM point of view) that will be flagged for writing operation.

my Doctrine association does not work properly

Hiho,
what i want to achieve: i have Projects, and within Project-edit, there is a form where i can drop images on (with dropzone.js) and those images are saved and assigned to the given Project.
Image upload works, the Image entities are being saved to the images table and they have the right project_id. But if i access the Project Enity, "images" is "null" in the project array. not an Collection of Image Entites.
It looks like a simple private Variable with no default value.
I guess my OneToMany and ManyToOne associations don't seem to work.
Some Code:
Project.php
/**
* #var ArrayCollection
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Image", mappedBy="project", cascade={"persist, remove"})
*/
private $images;
/**
* #return \Doctrine\Common\Collections\Collection
*/
public function getImages()
{
return $this->images;
}
public function addImage(Image $image)
{
$this->images[] = $image;
}
public function removeImage(Image $image) {
$this->images->removeElement($image);
}
Image.php
/**
* #var \AppBundle\Entity\Project
*
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Project", inversedBy="images")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="project_id", referencedColumnName="id")
* })
*/
private $project;
/**
* Set project
*
* #param \AppBundle\Entity\Project $project
*
* #return Image
*/
public function setProject(\AppBundle\Entity\Project $project = null)
{
$this->project = $project;
return $this;
}
/**
* Get project
*
* #return \AppBundle\Entity\Project
*/
public function getProject()
{
return $this->project;
}
Everything is saved to DB
but images is "null" (not an ArrayCollection :( )
Perhaps, there is something missing. But i dont see it somehow (although its soooo basic stuff)
Cheers
Adrian
Do the following in Project.php
In the constructor, add the following:
public function __construct()
{
// ...
$this->images = new ArrayCollection();
}
*Change addImage function to :
public function addImage(Image $image)
{
if(!$this-images->contains($image))
$this-images->add(image);
}
And then in Image.php, change setProject to:
public function setProject(\AppBundle\Entity\Project $project = null)
{
$this->project = $project;
if($project != null)
$project->addImage($this);
return $this;
}
Persist a few images and check if "Images" is not null.
I agree with Medard about the constructor and you also can try to set the fetch parameter to lazy on the oneToMany annotation.
Maybe a miss with the paramConverter
Oh Boy...
as i generated the Entities from DB (with doctrine:generate) there were Mapping files in the middle of the generating process (see http://symfony.com/doc/current/doctrine/reverse_engineering.html)
as soon as i deleted the src/AppBundle/Resources/config/doctrine folder (with orm.config.xml files in it) the Images were showing as persistent ArrayCollection.
but still the results where empty.
so i had to additionally put fetch="EAGER" into the OneToMany Mapping, as Lazyloading didnt seem to work properly (while dumping in twig, the result project.images was not initialized)
So thanks alot for your help. in the end, my images are showing up properly.
it would be nice, if symfony would write mapping errors in the dev.log, so i wouldnt have to search for 2 days.
Cya
Adrian

Add dynamic property on entity to be serialized

I have this REST API. Whenever request comes to get a resource by id ( /resource/{id}) I want to add a permissions array on that object on the fly (entity itself does not have that field).
What I came up with is this event listener. It checks the result the controller has returned:
class PermissionFinderListener {
...
public function onKernelView(GetResponseForControllerResultEvent $event) {
$object = $event->getControllerResult();
if (!is_object($object) || !$this->isSupportedClass($object)) {
return;
}
$permissions = $this->permissionFinder->getPermissions($object);
$object->permissions = $permissions;
$event->setControllerResult($object);
}
....
}
The problem is that the JMS Serializer opts out this dynamic property on serialization. I tried making the onPostSerialize event subscriber on JMS serializer, but then there are no clear way to check if this is a GET ONE or GET COLLECTION request. I don't need this behaviour on GET COLLECTION and also it results a huge performance hit on collection serialization. Also I don't want to create any base entity class with permission property.
Maybe there is some other way to deal with this scenario?
What I could imagine is a combination of Virtual Property and Serialization Group:
Add a property to your entity like:
/**
* #Serializer\VirtualProperty
* #Serializer\SerializedName("permissions")
* #Serializer\Groups({"includePermissions"}) */
*
* #return string
*/
public function getPermissions()
{
return $permissionFinder->getPermissions($this);
}
Only thing you need to do then is to serialize 'includePermissions' group only in your special case (see http://jmsyst.com/libs/serializer/master/cookbook/exclusion_strategies)
If you don't have access to $permissionFinder from your entity you could as well set the permission attribute of an entity from a Controller/Service before serializing it.
EDIT:
This is a bit more code to demonstrate what I mean by wrapping your entity and using VirtualProperty together with SerializationGroups. This code is not tested at all - it's basically a manually copied and stripped version of what we're using. So please use it just as an idea!
1) Create something like a wrapping class for your entity:
<?php
namespace Acquaim\ArcticBundle\Api;
use JMS\Serializer\Annotation as JMS;
/**
* Class MyEntityApi
*
* #package My\Package\Api
*/
class MyEntityApi
{
/**
* The entity which is wrapped
*
* #var MyEntity
* #JMS\Include()
*/
protected $entity;
protected $permissions;
/**
* #param MyEntity $entity
* #param Permission[] $permissions
*/
public function __construct(
MyEntity $entity,
$permissions = null)
{
$this->entity = $entity;
$this->permissions = $permissions;
}
/**
* #Serializer\VirtualProperty
* #Serializer\SerializedName("permissions")
* #Serializer\Groups({"includePermissions"})
*
* #return string
*/
public function getPermissions()
{
if ($this->permissions !== null && count($this->permissions) > 0) {
return $this->permissions;
} else {
return null;
}
}
/**
* #return object
*/
public function getEntity()
{
return $this->entity;
}
}
2) In your controller don't return your original Entity, but get your permissions and create your wrapped class with entity and permissions.
Set your Serialization Context to include permissions and let the ViewHandler return your serialized object.
If you don't set Serialization Context to includePermissions it will be excluded from the serialized result.
YourController:
$myEntity = new Entity();
$permissions = $this->get('permission_service')->getPermissions();
$context = SerializationContext::create()->setGroups(array('includePermissions'));
$myEntityApi = new MyEntityApi($myEntity,$permissions);
$view = $this->view($myEntityApi, 200);
$view->setSerializationContext($context);
return $this->handleView($view);

Found the public method "add", but did not find a public "remove" in symfony2 entity

I get this exeption when I submit my form:
Found the public method "addRemote", but did not find a public "removeRemote" on class App\CoreBundle\Entity\Scene
The weired think is that the remove method exist ...
But i wrote it myself (When I did php app/console doctrine:generate:entities) doctrine didn't generated it. Did I make something wrong ?
/**
* #var array $remote
*
* #ORM\Column(name="remote", type="array", nullable=true)
*/
private $remote;
/**
* Set remote
*
* #param array $remote
* #return Scene
*/
public function addRemote($value, $key=null) {
if($key!=null){
$this->remote[$key] = $value;
}else{
$this->remote[] = $value;
}
return $this;
}
/**
* Remove remote
*/
public function removeRemote(){
unset($this->remote);
}
I allso tried:
/**
* Remove remote
*/
public function removeRemote($key=null){
if($key!=null && array_key_exists($key, $this->remote)){
unset($this->remote[$key]);
}
unset($this->remote);
return $this;
}
You have bigger problem than this; you are abusing your forms :)
Add.. and Remove... methods should be used for relations, not columns as per your code. Also, both add and remove methods must accept parameter that will be either added or removed.
If you still need an array, than getRemotes() method should return key=>value array. Adder and remover will later get that key, based on what user have picked in choice form type.

Resources