Persisting object with relationship, database not updating - symfony

I have 2 entities with relationship OneToMany.
entity Question:
/**
* #ORM\OneToMany(targetEntity="Quiz\CoreBundle\Entity\Answer", mappedBy="question", cascade={"persist"})
*/
private $answers;
entity answer:
/**
* #ORM\ManyToOne(targetEntity="Quiz\CoreBundle\Entity\Question", inversedBy="answers")
*/
private $question;
Here I try to persist :
$em = $this->getDoctrine()->getManager();
$question = new Question();
$answer = new Answer();
$answer2 = new Answer();
$answer->setAnswerText('Roterdam');
$answer2->setAnswerText('Amsterdam')
->SetCorrect(true);
$question->setQuestionText('What\'s the capital of Netherlands? ');
$question->addAnswer($answer);
$question->addAnswer($answer2);
$em->persist($question);
$em->flush();
When I run this code everything updates in the db except the foreign key in answer table, the question_id is null.
Any idea what am I doing wrong ?

This has got to be one of the top five most popular Doctrine 2 questions. But I'm too lazy to look up one to link to.
Ask yourself how the answer knows to which question does it belong? Where is the link at the object level?
class Question
{
function addAnswer($answer)
{
$this->answers[] = $answer;
$answer->setQuestion($this);
}
}

Related

Doctrine many-to-many relationship: failed to get data automatically from database

In my Symfony code I've used Doctrine. In an Entity ( AppBundle\Entity\Core\User ) I defined a column foodTypes, which is associated with another Entity (AppBundle\Entity\FoodRecording\FoodType). I've defined an Many-to-Many relationship between User and FoodType, with a linking table foodrecording_user, joining User.username and FoodType.foodtype_code. The code is shown below.
// Entity\Core\User
namespace AppBundle\Entity\Core;
......
class User implements AdvancedUserInterface, \Serializable {
......
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\FoodRecording\FoodType")
* #ORM\JoinTable(name="foodrecording_user",
* joinColumns={#ORM\JoinColumn(name="username", referencedColumnName="username", onDelete="CASCADE")},
* inverseJoinColumns={#ORM\JoinColumn(name="foodtype_code", referencedColumnName="code", onDelete="CASCADE")}
* )
*/
private $foodTypes;
public function getFoodTypes()
{
$this->foodTypes = new \Doctrine\Common\Collections\ArrayCollection();
return $this->foodTypes;
}
However, as I wanted to get directly all the food types of a certain user, using
$userFoodTypes = $this->get('security.token_storage')->getToken()->getUser()->getFoodTypes();
then I got
$userFoodTypes =====> array[]
I expected that as I've created the M-M relationship, Doctrine would automatically fetch the data I need, but it is not the case!
Therefore, I have to write my own code to retrieve the data from the DB / table like following:
public function fetchUserFoodTypes()
{
global $kernel;
$container = $kernel->getContainer();
$em = $container->get('doctrine.orm.entity_manager');
$conn = $em->getConnection();
$sql = 'SELECT * FROM foodrecording_user where username = :username';
$stmt = $conn->prepare($sql);
$stmt->execute([
'username' => $this->getUsername(),
]);
$data = $stmt->fetchAll();
$res = [];
foreach ($data as $item) {
$foodtype = $em->getRepository('AppBundle\Entity\FoodRecording\FoodType')->findByCode($item['foodtype_code']);
$res[] = $foodtype;
}
return $res;
}
public function getFoodTypes()
{
$this->foodTypes = $this->fetchUserFoodTypes();
//$this->foodTypes = new \Doctrine\Common\Collections\ArrayCollection();
return $this->foodTypes;
}
Only in this way I am able to get the food types associated with a user.
Could anyone explain to me, why I can't simply use the M-M definition and let doctrine do all the thing automatically for me? Why should I explicitly write my own function to retrieve data from DB? Is Doctrine not smart enough?
This part:
$this->foodTypes = new \Doctrine\Common\Collections\ArrayCollection();
Belongs to the __construct method, not getter.. You see, like this, every time you call your getter, you reset the property foodTypes to an empty instance of an ArrayCollection

Symfony add data to object on pre persist

I have a form to create documents. On the one side I can add names and descriptions and next to that I can select one or several agencies to whom the created document belongs.
Each of the agencies is assigned to one specific market (there are 7 markets in total, so one market can have several agencies but one agency belongs only to one market!)
What I want to achieve is a "prePersist" function which automatically adds the correct market(s) (depending on the number of agencies selected) to the document.
My document entity has the two entities (markets and agencies) with the according getters and setters:
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Market", inversedBy="uploadProfiles", cascade={"persist"})
* #ORM\JoinTable(name="document_uploadprofile_markets",
* joinColumns={#ORM\JoinColumn(name="uploadprofile_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="market_id", referencedColumnName="id")})
**/
private $markets;
/**
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Agency", inversedBy="uploadProfiles", cascade={"persist"})
* #ORM\JoinTable(name="document_uploadprofile_agencies",
* joinColumns={#ORM\JoinColumn(name="uploadprofile_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="iata8", referencedColumnName="iata8")})
**/
private $agencies;
public function __construct()
{
$this->agencies = new \Doctrine\Common\Collections\ArrayCollection();
$this->markets = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* Add market
*
* #param \AppBundle\Entity\Market $market
*
* #return UploadProfile
*/
public function addMarket(\AppBundle\Entity\Market $market)
{
$this->markets[] = $market;
return $this;
}
/**
* Remove market
*
* #param \AppBundle\Entity\Market $market
*/
public function removeMarket(\AppBundle\Entity\Market $market)
{
$this->markets->removeElement($market);
}
/**
* Get markets
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getMarkets()
{
return $this->markets;
}
/**
* Add agency
*
* #param \AppBundle\Entity\Agency $agency
*
* #return UploadProfile
*/
public function addAgency(\AppBundle\Entity\Agency $agency)
{
$this->agencies[] = $agency;
return $this;
}
/**
* Remove agency
*
* #param \AppBundle\Entity\Agency $agency
*/
public function removeAgency(\AppBundle\Entity\Agency $agency)
{
$this->agencies->removeElement($agency);
}
/**
* Get agencies
*
* #return \Doctrine\Common\Collections\Collection
*/
public function getAgencies()
{
return $this->agencies;
}
I know that I can add the prePersist function to my document entity and try to code what I want to achieve but I don't think that's working because I need something like that:
foreach($document->getAgencies() as $agency) {
$document->setMarket($em->getRepository('AppBundle:Agency')->getMarket($agency));
}
I'm not even sure whether that foreach loop is correct since (so far) the result is always null. I've already asked a question to that topic here: Symfony use setter for Arraycollection in CreateController
I've also tried to write an own repository function to get all the distinct markets from my agency entity but so far that's not working either.
Another idea was a POST_SUBMIT event listener in my form class but so far that didn't make sense to me either.
Any ideas? If more code is needed, let me know!
edit
I edited and changed my code above in order to have a manytomany relationship between my markets and documents. What I then tried is, adding the prePersist function to my document entity and it actually worked fine while still having the OneToMany relationship (it was just always overwriting the previous market, but that doesn't matter now)
I'm now trying to edit that function so that several markets can be added to the document.
Two ideas I had, but they both didn't work out:
if(count($this->getAgencies()) > 0){
foreach($this->getAgencies() as $agency) {
$this->addMarket($agency->getMarket());
}
}
--> market is always null
or
if(count($this->getAgencies()) > 0){
$upId = rtrim($this->getId(),"_up");
$query = $em->createQuery("SELECT DISTINCT (a.market) FROM UserBundle\Entity\User u JOIN u.agencies a WHERE u.id = $userId");
$marketIds = $query->getResult();
$em = $this->getDoctrine ()->getManager ();
$repository = $this->getDoctrine()
->getRepository('AppBundle:Market');
$markets = $repository->findOneById($marketIds);
$this->addMarket($markets);
}
}
update
here is my prepersist function within my document entity and then the getMarkets() function, that has been suggested in one of the comments. I changed the name to addMarkets instead of getMarkets
/**
* #ORM\PrePersist
*/
public function prePersist() {
if(count($this->getAgencies()) > 0){
foreach($this->getAgencies() as $agency) {
$this->addMarkets($agency->getMarket());
}
}
}
public function addMarkets(\AppBundle\Entity\Market $market)
{
$markets = array();
foreach($this->agencies as $agency) {
$market = $agency->getMarket();
$id = $market->getId();
// Skip duplicates
if (isset($markets['id'])) {
continue;
}
$markets[$id] = $market;
}
return $markets;
}
another approach
so I edited it again, now my function looks like that
$markets = $this->getMarkets();
if(count($this->getAgencies()) > 0){
foreach($this->getAgencies() as $agency) {
if(!$this->markets->contains($markets)) {
$this->addMarket($agency->getMarket());
}
return;
dump($markets);
}
}
I thought that this might work to eliminate my duplicates but it does not.. any idea why?
This is a structural error in logic. And the clue is in your question and code.
automatically adds the correct market(s)
And:
/**
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Market")
* #ORM\JoinColumn(name="market", referencedColumnName="id")
* #var \AppBundle\Entity\Market
**/
private $market;
Are incompatible.
If a document can have many agencies and agencies can have one market then your document must allow for many markets. For example:
DocumentA has Agency1 and Agency2.
Agency1 has MarketParis.
Agency2 has MarketMarrakesh.
This necessarily means that DocumentA has (Agency1's) MarketParis and (Agency2's) MarketMarrakesh -- many markets.
The question you're asking is a much larger topic than just setting or getting. If you want ONLY one market per document then you'll have to enforce uniqueness among document agencies. For example:
Your form creates DocumentA.
The user tries to set Agency1 (MarketParis) and Agency2 (MarketMarrakesh).
This throws an error because there can be ONLY ONE market.
Or another example:
Your form creates DocumentA
The user tries to set Agency1 (MarketParis) and Agency3 (MarketParis).
This is successful because the uniqueness of the Document's Market is enforced.
There are many strategies for this and is a much larger topic than your question.
EDIT
If your cardinality logic is correct (fixing any issues I described above) AND your doctrine annotations include cascading persist in all your entities, which look correct from the code above. The only thing I can think of that would not be working correctly is the "by_reference" attribute of your form. Setting "by_reference" to false in your form, with cascade-persist set in your entity, should persist all entities associated with the form. See the by_reference doc here: http://symfony.com/doc/current/reference/forms/types/collection.html#by-reference
If I understand what you said correctly, I believe Documents should not have a reference to Markets at all. But only references Agency.
A Document will have a ManyToMany relation with Agency and that's it.
Then in Document you can do something like:
public function getMarkets()
{
$markets = array();
foreach($this->agencies as $agency) {
$market = $agency->getMarket();
$id = $market->getId();
// Skip duplicates
if (isset($markets[id]) {
continue;
}
$markets[$id] = $market;
}
return $markets;
}
This looks like wrong approach. It makes more sense to have oneToMany relationship between Market and Agency and oneToMany between Agency and Document.

Symfony OneToMany - ManyToOne entity association not working

I'm training myself on Symfony and struggling with a problem with bidirectional association (very basic) because by dumping my entity in a twig template I verify that data is correct but the association is always null.
My problem is like this one but the solution is not shared.
I read the documentation here and it seems I follow the right steps.
My db contain a Parent table and a Children table related by children.parent_id as foreign key, both table are popolated and I use DOCTRINE:GENERATE:ENTITIES and DOCTRINE:GENERATE:CRUD.
In Parents class I have:
function __construct() {
$this->lastUpd = new \DateTime();
$this->children = new ArrayCollection();
}
/*
* #ORM\OneToMany(targetEntity="AppBundle\Entity\Children", mappedBy="parent_id", cascade={"persist"})
*/
private $children;
public function setChildren(ArrayCollection $children) {
return $this->children = $children;
}
public function getChildren() {
return $this->children;
}
In Children class I have:
/**
* #var \AppBundle\Entity\Parents
* #ORM\ManyToOne(targetEntity="AppBundle\Entity\Parents", inversedBy="children")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="parent_id", referencedColumnName="parent_id")
* })
*/
private $parent_id;
/**
* Set parent_id
* #param \AppBundle\Entity\Parents $parent_id
* #return Parents
*/
public function setParentID(\AppBundle\Entity\Parents $parent_id= null) {
$this->parent_id = $parent_id;
return $this;
}
/**
* Get parent_id
* #return \AppBundle\Entity\Parents
*/
public function getParentID() {
return $this->parent_id;
}
As additional info looking at Simfony profiler (of parents list page) -> Doctrine -> Entities Mapping I found (with no errors) AppBundle\Entity\Parents and AppBundle\Entity\Type (a working unidirectional OneToMany association).
I am sorry to post a so basic error and I bet the solution is simple but I can't see it.
note: Im assuming that youre not creating an ArrayCollection of children and adding them en'mass.
you dont have any addChild method (which you need to call).
this is easy with an ArrayCollection.
public function addChild(Children $child) {
$this->children->add($child);
}
you could also do with a removeChild as well.
public function removeChild(Children $child) {
$this->children->removeElement($child);
}
then when in your controller.
$child = new Children();
$parent->addChild($child);
then when you persist the parent object, the children will follow due to the cascade persist. I would also add cascade={"remove"} as well, so when you delete the parent, the children will go to.

Link 1 entity with many others kind of entities

Let say I have a Company for which I manage Employees, Cars, Contracts, Buildings, Sites, Products, etc. As you can guess, these are quite independant things, so no inheritance is possible.
For each of these elements (i.e. Entities), I want to be able to attach one or several Documents (click on a button, form opens, select one/several Document or upload a new one).
Linking Document to one kind of entity is not a problem, my problem is that there are many kinds of entities. How should I manage that? I have 2 ideas which have their own problems...:
Create a ManyToMany relationship between Document and Employee, another between Document and Car, etc.
Problem: I have to duplicate the Controller code to attach Document, duplicate the forms, etc.
Create a single join table containing the Document's ID, the related entity's ID and the related entity's class name.
Problem: it doesn't look really clean to me, I didn't really dig in this way but I feel I'll have a lot of "entity mapping" problems.
Any suggestion?
[EDIT]
In fact I have to do the same for Event as well: I need to link some Events to some Employees and/or to some Cars, etc. And in my real case, I have more than 10 Entities to be linked to Event and/or Document, which means duplicating more tha 20 times the code if I go with the solution 1!
Assuming you're using Doctrine ORM, i think you're searching for the Mapped Superclasses inheritance.
The docs are better than words :
http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/inheritance-mapping.html#mapped-superclasses
So finally I managed to solve my problem, following #Rpg600 idea about Mapped Superclasses.
This is probably not the best and cleanest solution ever, I'm not really proud of it but it does the job and it is still better than my first ideas.
I create a BaseEntity which is my a mapped superclass (Employee, Car, etc. Entities have to extend this Class):
/**
* BaseEntity
* #ORM\MappedSuperclass
*/
class BaseEntity {
/**
* #ORM\OneToOne(targetEntity="MyProject\MediaBundle\Entity\Folder")
*/
private $folder;
/**
* Set folder
* #param \Webobs\MediaBundle\Entity\Folder $folder
* #return BaseEntity
*/
public function setFolder(\Webobs\MediaBundle\Entity\Folder $folder = null){
$this->folder = $folder;
return $this;
}
/**
* Get folder
* #return \Webobs\MediaBundle\Entity\Folder
*/
public function getFolder(){
return $this->folder;
}
}
As it is not possible to have a Many-to-Many relationship in a superclass, I use a Folder which will contain one or several Document. This is the dirty part of the solution ; the folder table basically contain only one field which is the id...
class Folder
{
private $id;
/**
* Note : Proprietary side
* #ORM\ManyToMany(targetEntity="MyProject\MediaBundle\Entity\Document", inversedBy="folders", cascade={"persist"})
* #ORM\JoinTable(name="document_in_folder")
*/
private $documents;
// setters and getters
Then I create a helper class (which is declared as a service) to manage the link between any Entity and the Document:
class DocumentHelper extends Controller
{
protected $container;
/** ************************************************************************
* Constructor
* #param type $container
**************************************************************************/
public function __construct($container = null)
{
$this->container = $container;
}
/** ************************************************************************
* Attach Document(s) to an $entity according to the information given in the
* form.
* #param Entity $entity
* #param string $redirectRouteName Name of the route for the redirection after successfull atachment
* #param string $redirectParameters Parameters for the redirect route
* #return Response
**************************************************************************/
public function attachToEntity($entity, $redirectRouteName, $redirectParameters)
{
$folder = $entity->getFolder();
if($folder == NULL){
$folder = new Folder();
$entity->setFolder($folder);
}
$form = $this->createForm(new FolderType(), $folder);
// ------------- Request Management ------------------------------------
$request = $this->get('request');
if ($request->getMethod() == 'POST') {
$form->bind($request); // Link Request and Form
if ($form->isValid()) {
$em = $this->getDoctrine()->getManager();
$em->persist($folder);
$em->persist($entity);
$em->flush();
return $this->redirect($this->generateUrl($redirectRouteName, $redirectParameters));
}
}
return $this->render('MyProjectMediaBundle:Folder:addDocument.html.twig', array(
'form' => $form->createView(),
'entity' => $entity,
));
}
Doing that way, I just have to add one small action in each relevant controller, let say EmployeeController.php:
public function addDocumentAction(Employee $employee)
{
$redirectRouteName = 'MyProjectCore_Employee_see';
$redirectParameters = array('employee_id' => $employee->getId());
return $this->get('myprojectmedia.documenthelper')->attachToEntity($employee,$redirectRouteName,$redirectParameters);
}
Same principle for the display, in the helper I have the common function which I call in my already-existing seeAction() and in the TWIG file I import the common "Document list" display.
That's all folks!
I hope this can help :)

Persisting multiple new associated objects in a self-referenced table with Doctrine2 and Symfony2

I'm quite new to Doctrine2 which I'm currently using in a Symfony2 project.
I'm trying to persist entities with a self-referencing foreign key such as the category example of the Doctrine documentation (http://www.doctrine-project.org/docs/orm/2.0/en/reference/association-mapping.html#one-to-many-self-referencing). There is probably a very easy solution to this but I just couldn't find it anywhere on the web.
For some reason, the value for the parent_id is not automatically stored by Doctrine (it's null), although I can access the id value of the object I am assigning as a parent.
Some code will probably be straightforward. Here's the relevant part :
The entity definition :
class Area
{
// id, name, type, etc...
/**
* #ORM\ManyToOne(targetEntity="Area", inversedBy="sub_areas", cascade={"persist"})
* #ORM\JoinColumn(name="parent_area_id", referencedColumnName="id")
*/
private $parent_area;
/**
* #ORM\OneToMany(targetEntity="Area", mappedBy="parent_area", cascade={"persist"})
*/
private $sub_areas;
/**
* Set parent area
*
* #param obj $area
*/
public function setParentArea(Area $area)
{
$this->aera = $area;
}
// Other getters and setters, etc.
}
In the action :
$results = array();
foreach($area_types as $key => $type) {
$area = new Area();
$area->setType($type);
$area->setName($location->getAddressComponent($type));
if(isset($parent_area)) {
$area->setParentArea($parent_area);
}
$this->em->persist($area);
$parent_area = $area;
$results[] = $area->getId();
}
$this->em->flush();
The results array will output the assigned ids in Twig. I also tried using :
$area->setParentAreaId($parent_area->getId());
Could anyone explain how Doctrine manages the database persistence sequence of self-referenced objects ?

Resources