Symfony2: Doctrine does not load related entities in many-to-many relation - php

I have a many-to-many-relation, and when I load an entity that is on one side this relation, I expect to see as its property the ArrayCollection of related entities on another side. However, this does not happen - the ArrayCollection loaded has no elements in it, while in the database I can see the related entries. What could be the reason?
Here is my code:
One side of the relation, ConsolidatedReport class:
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="P24\Response", inversedBy="consolidatedReports")
* #ORM\JoinTable(name="con_rprt_responses")
*/
private $responses;
Another side of the relation, Response class:
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="P24\ConsolidatedReport\ConsolidatedReport", mappedBy="responses")
*/
private $consolidatedReports;
Here is the function I run to get an instance of ConsolidatedReport. This function sits inside a service that is being called from container:
/**
* Picks the consolidated report with given id.
*
* #param string $id
*
* #return ConsolidatedReport
*
* #throws NonExistentConsolidatedReportException if the survey doesn't exist
*/
public function pick($id)
{
$report = $this->repository->findOneBy(array('id' => $id));
if (!$report) {
throw new NonExistentConsolidatedReportException($id);
}
return $report;
}'
In the database, there is "con_rprt_responses" table with two columns "consolidated_reports_id" and "response_id". However, in profiler I do not see any queries to that table.
What could go wrong here?
UPDATE:
Please see my answer to this question below, that worked for me.

I added fetch="EAGER" to the $responses property of ConsolidatedReport class, and it worked.
The code now looks like this:
/**
* #var ArrayCollection
*
* #ORM\ManyToMany(targetEntity="P24\Response", inversedBy="consolidatedReports", fetch="EAGER")
* #ORM\JoinTable(name="con_rprt_responses")
*/
private $responses;
More info here:
http://doctrine-orm.readthedocs.org/en/latest/reference/working-with-objects.html#by-eager-loading
Still if someone knows why the collection of related entity would not load without explicitly specifying EAGER fetching - please share your knowledge, it is highly appreciated!

If you specify the joinColumns, does this solve your problem?
/**
* #ORM\ManyToMany(targetEntity="P24\Response", inversedBy="consolidatedReports")
* #ORM\JoinTable(name="con_rprt_responses",
* joinColumns={#ORM\JoinColumn(name="consolidated_reports_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="response_id", referencedColumnName="id")}
* )
*/

The *toMany properties have to be initialized with an ArrayCollection.
public function __construct() {
$this->responses = new \Doctrine\Common\Collections\ArrayCollection();
$this-> consolidatedReports = new \Doctrine\Common\Collections\ArrayCollection();
}

In case you have more then single query to fetch the same objects using Doctrine try to use:
$entityManager->clear();
in between, to fix "missing" entities. It isn't solution "as is", however can give you an idea something wrong in chain of your queries.

Related

Doctrine Constraint on a Many-to-Many Relation

We have an Entity 'User' that has a Many-to-Many Relation to different 'Types'. The MySQL Relation is handled via a Cross Table.
class User {
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Type")
* #ORM\JoinTable(name="user_type",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="type_id", referencedColumnName="id")}
* )
*/
protected $types;
}
class Type {
/**
* #ORM\ManyToMany(targetEntity="App\Entity\User")
* #ORM\JoinTable(name="user_type",
* joinColumns={#ORM\JoinColumn(name="type_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")}
* )
*/
protected $users;
/**
* #ORM\Column(type="datetime")
*/
protected $publishedAt;
}
How can I limit the Responses in this many-to-many relation to only show Types that have already been published?
That is the factual SQL should include a WHERE that filters the corresponding items that have not been published yet. Can I do that with an annotation or something like that?
I know I can do this by filtering the returned collection. But is there anything more efficient than that?
This question is kind of a douplicate.
It has been answered here: php - Doctrine2 association mapping with conditions - Stack Overflow
One of the comments tells, that this results in an error for Many-to-Many Relations. As of Doctrine 2.5 this no longer is the case.
So what you can do in doctrine is hand over a query condition when you request the entities of the relation:
So you do not change the Annotation, but the getter for the Entity:
class User {
/**
* #ORM\ManyToMany(targetEntity="App\Entity\Type")
* #ORM\JoinTable(name="user_type",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="type_id", referencedColumnName="id")}
* )
*/
protected $types;
public function getTypes()
{
$criteria = Criteria::create()->where(Criteria::expr()->lte('publishedAt', date('Y-m-d')));
return $this->types->matching($criteria);
}
}
This will result in a Lazy-Fetch (depending on your settings) of the required items. All the doctrine magic still works, like caching and the like. So the collection will only be fetched, if it has not been fetched...
You can use Criteria.
Add a function to your User class called eg getPublishedTypes.
public function getPublishedTypes()
{
return $this->getTypes()->matching(TypeRepository::createPublishedCriteria());
}
And in your TypeRepository add the function createPublishedCriteria.
static public function createPublishedCriteria()
{
return Criteria::create()
->andWhere(Criteria::expr()->lte('publishedAt', date('Y-m-d')); //your logic
}
Note: function has to be static
You can also use that criteria later on in your query builder with following line
$qb->addCriteria(self::createPublishedCriteria()).
Another solution with bad practise could be collection filtering. Add in your User class:
public function getPublishedTypes()
{
return $this->getTypes()->filter(function(Type $type) {
return $type->getPublishedAt < date('Y-m-d');
}
This version is not that great, because it produces way more queries (bad with large data in your database).

[Doctrine][Symfony] Multiple Joins in a single annotation request (is it possible ?)

I'm working on a Symfony project (V3.4) and I'm using an existing DB (can't change it).
To interact with it I use doctrine annotations and the work is well done!
I manage to submit requests using JoinTable and JoinColumns but there is one last thing I don't know how to deal with ...
I have the following tables :
tables
I have the id from table A and I'm trying to get the libelle from the E table.
Is there a way to do it using annotations? For now I've already done it between 3 tables but I don't know how to do it for more :
#ORM\ManyToMany(targetEntity="")
* #ORM\JoinTable(name="",joinColumns={#ORM\JoinColumn(
name="",referencedColumnName="")}, inverseJoinColumns={#ORM\JoinColumn(
name="",referencedColumnName="", unique=true)})
If it's not possible I'm open to suggestions.
Thanks!
By looking at your need and your schema, i can tell you that you don't need many to many relationship. You should bidirectional relation across the entities(tables).
I don't know which kind of relation you have right now, but assuming one-to-one, you can setup relations following way:
EntityA
/**
* #OneToOne(targetEntity="EntityB", mappedBy="entityA")
*/
private $entityB;
EntityB
/**
* #OneToOne(targetEntity="EntityA", inversedBy="entityB")
* #JoinColumn(name="entityA_id", referencedColumnName="id")
*/
private $entityA;
/**
* #OneToOne(targetEntity="EntityC", mappedBy="entityB")
*/
private $entityC;
EntityC
/**
* #OneToOne(targetEntity="EntityB", inversedBy="entityC")
* #JoinColumn(name="entityB_id", referencedColumnName="id")
*/
private $entityB;
/**
* #OneToOne(targetEntity="EntityD", mappedBy="entityC")
*/
private $entityD;
EntityD
/**
* #OneToOne(targetEntity="EntityC", inversedBy="entityD")
* #JoinColumn(name="entityC_id", referencedColumnName="id")
*/
private $entityC;
/**
* #OneToOne(targetEntity="EntityE", mappedBy="entityE")
*/
private $entityE;
EntityE
/**
* #OneToOne(targetEntity="EntityD", inversedBy="entityE")
* #JoinColumn(name="entityD_id", referencedColumnName="id")
*/
private $entityD;
I didn't have time to test it. But it should be straight forward.
You can get libelle from E table following way:
$entityA = $entityRepositoryForA->find(a_id_here);
$entityA->getEntityB()->getEntityC()->getEntityD()->getEntityE->getLibelle();

Doctrine - Symfony2 | Many to Many targeting the same entity

I am trying to create a many-to-many foreign key table relation targeting the same entity.
I have successfully created relations with other entities but I'm having trouble when targeting the same entity.
I read a few stack overflow questions and answers and saw what I was doing was possible..
I based my class of this example - the table unit_relations is empty when i add a parent, but works when I add a child to a unit. I'm assuming because the field has the inversedBy annotation.
What do I need to add to allow bi-drectional updating/inserting?
I tried swapping the joinColums like this answer - nothing...
--
/**
* Unit
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Acme\DemoBundle\Entity\UnitRepository")
*/
class Unit
{
....
/**
* #ORM\ManyToMany(targetEntity="Unit", inversedBy="parents")
* #ORM\JoinTable(name="unit_relations",
* joinColumns={#ORM\JoinColumn(name="unit_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="child_unit_id", referencedColumnName="id")}
* )
*/
private $children;
/**
* #ORM\ManyToMany(targetEntity="Unit", mappedBy="children")
*/
private $parents;
}
is adding inversedBy instead of having mappedBy, with the join columns also swapped, the proper way to do it?
Can someone explain this?
/**
* #ORM\ManyToMany(targetEntity="Unit", inversedBy="parents")
* #ORM\JoinTable(name="unit_relations",
* joinColumns={#ORM\JoinColumn(name="unit_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="child_unit_id", referencedColumnName="id")}
* )
*/
private $children;
/**
* #ORM\ManyToMany(targetEntity="Unit", inversedBy="children")
* #ORM\JoinTable(name="unit_relations",
* joinColumns={#ORM\JoinColumn(name="child_unit_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="unit_id", referencedColumnName="id")}
* )
*/
private $parents;
EDIT
as requested here's the code showing how I create the relation. I just realized it may because the entity doesn't have an id yet since I'm adding the relation on its creation...
$unit = new \Acme\DemoBundle\Entity\Unit();
/* #var $parentUnit \Acme\DemoBundle\Entity\Unit */
$parentUnit = $this->getDoctrine()->getRepository('AcmeDemoBundle:Unit')->findById($request->get('unitId'));
$unit->addParent($parentUnit);
$entityManager = $this->getDoctrine()->getManager();
$entityManager->persist($unit);
$entityManager->flush();
It doesn't matter, that there is no ID, when you add a parent relations.
I cant in detail explain why this happens, but i think the main problem is, that in Many-To-Many Self-Referencing the attribute with JoinTable annotation is potentially the Master-Entity. You can say, that it "holds" all other relations to this Entity.
You can recieve the bi-directional updating/inserting while changing the function $unit->addParent($parent). Change it as follows:
public function addParent($parent)
{
$this->parents[] = $parent;
$parent->addChild($this); // Add the relation in the proper way
}
That should work fine!
Regards!

Unable to guess how to get a Doctrine instance from the request information

I've got this "500 Internal Server Error - LogicException: Unable to guess how to get a Doctrine instance from the request information".
Here is my controller's action definition:
/**
* #Route("/gatherplayer/{player_name}/{gather_id}")
* #Template()
*/
public function createAction(Player $player, Gather $gather)
{
// ...
}
And it doesn't work, probably because Doctrine 2 can not "guess"... So how do I make Doctrine 2 guess, and well?
The Doctrine doesn't know how to use request parameters in order to query entities specified in the function's signature.
You will need to help it by specifying some mapping information:
/**
* #Route("/gatherplayer/{player_name}/{gather_id}")
*
* #ParamConverter("player", options={"mapping": {"player_name" : "name"}})
* #ParamConverter("gather", options={"mapping": {"gather_id" : "id"}})
*
* #Template()
*/
public function createAction(Player $player, Gather $gather)
{
// ...
}
/**
* #Route("/gatherplayer/{name}/{id}")
* #Template()
*/
public function createAction(Player $player, Gather $gather)
I didn't find any help in paramconverter's (poor?) documentation, since it doesn't describe how it works, how it guesses with more than one parameters and stuff. Plus I'm not sure it's needed since what I just wrote works properly.
My mystake was not to use the name of my attributs so doctrine couldn't guess right. I changed {player_name} to {name} and {gather_id} to {id}.
Then I changed the names of my id in their entities from "id" to "id_gather" and "id_player" so I'm now able to do that :
/**
* #Route("/gatherplayer/{id_player}/{id_gather}")
* #Template()
*/
public function createAction(Player $player, Gather $gather)
which is a lot more effective than
* #Route("/gatherplayer/{id}/{id}")
Now I'm wondering how I can make this work
/**
* #Route("/gatherplayer/{player}/{gather}")
* #Template()
*/
public function deleteAction(Gather_Player $gather_player)
try this:
/**
* #Route("/gatherplayer/{player_name}/{gather_id}")
* #ParamConverter("player", class="YourBundle:Player")
* #ParamConverter("gather", class="YourBundle:Gather")
* #Template()
*/
public function createAction(Player $player, Gather $gather)
The parameters on the signature of the #Route annotation must match the entities fields, so that Doctrine makes automatically the convertion.
Otherwise you need to do the convertion manually by using the annotation #ParamConverter as it's mentionned on the other responses.
#1ed is right, you should define a #paramConverter in order to get a Player instance or a Gather instance.

Many To Many Mapping Issue

I'm currently trying to map music related data using Doctrine2's POPO Annotations.
I haven't had problems mapping any other many-to-many relations, but one specific relation is giving me trouble. It does not throw an error, but the mapping does not get inserted into the mapping table (artist_album)
Artist:
<?php
/**
* #orm:Entity
* #orm:Table(name="artist")
*/
class Artist
{
...
/**
* #orm:ManyToMany(targetEntity="Company\MusicBundle\Entity\Album", inversedBy="artists", cascade={"persist"})
* #orm:JoinTable(name="artist_album",
* joinColumns={#orm:JoinColumn(name="artist_id", referencedColumnName="id")},
* inverseJoinColumns={#orm:JoinColumn(name="album_id", referencedColumnName="id")})
*
* #var ArrayCollection
*/
private $albums;
...
}
Album
....
/**
* #orm:ManyToMany(targetEntity="Company\MusicBundle\Entity\Artist", mappedBy="albums", cascade={"persist"})
*
* #var ArrayCollection
*/
private $artists;
...
}
I'm sure it's just something in I've done wrong in the mapping, but I just can't put my proverbial finger on it.
My problem was that I was setting the artist on the inverse side of the relationship. It appears you must set the relationship on the owning side (in this case, Artist).

Categories