I have an entity called Game which has a ManyToMany connection with a JoinTable to an entity called Question
This works pretty well. The problem is, that I need the questions in the exact order as they are chosen, not sorted by question id, as I get them now when I call getQuestions() on the Game class.
Is there a way to do that?
The Questions are all added with $game->addQuestion($question);. The Questions are existing, the game is persisted, after the questions are added.
...
class Game {
...
/**
* #ORM\ManyToMany(targetEntity="Question")
* #ORM\JoinTable(name="Games_to_Questions",
* joinColumns={#ORM\JoinColumn(name="game_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="question_id", referencedColumnName="id")}
* )
**/
private $questions;
...
}
...
class Question {
...
}
...
you're going to have to add an intermediary entity with a sort order column. Let's call it GameQuestion.
/**
* #ORM\Table(name="game_question")
* #ORM\Entity(repositoryClass="Gedmo\Sortable\Entity\Repository\SortableRepository")
*/
class GameQuestion {
private $game;
private $question;
/**
* #Gedmo\SortablePosition
*/
private $sortOrder;
}
Related
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).
EDIT 2018-05-22: no answer fully fixed issue - can no longer replicate issue as no longer have access. Not removing based on this meta discussion
Please do not spend time/effort creating an answer
Discussion in #Wilt's answer led me to what I know about using discriminators now, it might help future questioners. In my case it helped, but did not provide an answer.
I've got a bit of complex problem where the hydration of data received from client-side gets hydrated incorrectly. I've been trying to fix the problem for close to a week now, so I thought to ask you guys.
We've got this application that allows for the creation of assignments for students. Assignments may contain Questions, Text items, Media items, and more. The problem is with the Questions and associated Answers.
Scenario
Assignment
QuestionSheet 1 (L1)
Question 1 (L1 - V1)
Answer A (L1 - V1 - A1)
Answer B (L1 - V1 - A2)
Question 2 (L1 - V2)
Answer A (L1 - V2 - A1) (just 1 answer)
QuestionSheet 2 (L2)
Question 1 (... and so on)
Answer A
Answer B
Question 2
Answer A
Answer B
Question 3
Answer A
Answer B
The above gets send properly from the client-side. Screenshot of snipped of that data:
Important to note is that, as you can see above, a Question is in fact a GridElements entity. It might've also been Text or Image, this is based on the property type = question which is a Discriminator.
After hydrating the data we get the following Entity structure:
As you can see, the data is no longer correct after hydration. This is done during the $form->isValid(). QuestionSheet 1 contains the first Question for QuestionSheet 2 and that Question has the first Answer from the third Question of the second QuestionSheet.
When reading through the full hydrated dataset, I see that the Answers created for the first QuestionSheet have been dropped. The Answers from the second QuestionSheet have been duplicated and have overwritten the Answers in the first QuestionSheet. In essence, what you see in the picture above.
Worse still
The below is all the data that is saved to the database after the above, with the mentioned scenario of 2 lists, 5 questions and 9 answers.
So of the first Question, no Q&A left. The second QuestionSheet's Questions have been used to overwrite them. Also , only the last 2 Answers are there, filling the space of what should've been 9!.
Btw, the query returning this is completely LEFT JOIN so as to show all empty data as well, this is all there's left.
It seems that it grabs the last set of whatever child entities there are to fill up previous entities, or something. I've gotten lost.
How is this possible?
As I mentioned, I've been at it a good long while, but cannot find a solution. Hope you guys can help.
If you need any info on code I'll do my best to either show it here or explain it as best as possible in case of some proprietary code.
Update - Entities
Assignment entity
/**
* #ORM\Entity(repositoryClass="Wms\Admin\Assignment\Repository\AssignmentRepository" )
* #ORM\HasLifecycleCallbacks
* #ORM\Table(name="ass_assignment")
* #Gedmo\SoftDeleteable(fieldName="deletedAt", timeAware=false)
*/
class Assignment extends SeoUrl
{
//Traits and properties
/**
* #ORM\OneToMany(targetEntity="Wms\Admin\Assignment\Entity\QuestionSheet", mappedBy="assignment", cascade={"persist", "remove"}, orphanRemoval=true)
**/
protected $questionSheets;
public function __construct()
{
$this->abstractEntity_entityCategories = new ArrayCollection();
$this->questionSheets = new ArrayCollection();
$this->documents = new ArrayCollection();
}
public function __toString()
{
return (string)$this->id;
}
//More getters/setters
}
QuestionSheet entity
/**
* #ORM\Entity(repositoryClass="Wms\Admin\Assignment\Repository\QuestionSheetRepository")
* #ORM\Table(name="ass_questionsheet")
**/
class QuestionSheet extends AbstractEntity
{
/**
* #ORM\ManyToOne(targetEntity="Wms\Admin\Assignment\Entity\Assignment", inversedBy="questionSheets")
* #ORM\JoinColumn(name="assignment_id", referencedColumnName="id", onDelete="CASCADE")
**/
protected $assignment;
/**
* #ORM\OneToOne(targetEntity="Wms\Admin\LayoutGrid\Entity\Grid", cascade={"persist", "remove"})
* #ORM\JoinColumn(name="grid_id", referencedColumnName="id")
**/
protected $grid;
public function __construct()
{
$this->gridElements = new ArrayCollection();
}
//More getters/setters
}
Grid entity
/**
* #ORM\Entity
* #ORM\Table(name="lg_grid")
**/
class Grid extends AbstractEntity
{
/**
* #ORM\OneToMany(targetEntity="Wms\Admin\LayoutGrid\Entity\Element\AbstractElement", mappedBy="grid", cascade={"persist", "remove"}, orphanRemoval=true)
* #ORM\OrderBy({"y" = "ASC", "x" = "ASC"})
*/
protected $gridElements;
public function __construct()
{
$this->gridElements = new ArrayCollection();
}
}
Grid Element entity
/**
* #ORM\Table(name="lg_grid_element")
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\HasLifecycleCallbacks
**/
class AbstractElement extends AbstractEntity implements GridElementInterface
{
/**
* #ORM\ManyToOne(targetEntity="Wms\Admin\LayoutGrid\Entity\Grid", inversedBy="gridElements")
* #ORM\JoinColumn(name="grid_id", referencedColumnName="id", onDelete="CASCADE")
**/
protected $grid;
public $type = ''; //This is a discriminator
}
Question entity
/**
* #ORM\Entity
* #ORM\Table(name="lg_grid_question")
**/
class Question extends AbstractElement
{
/**
* #ORM\OneToMany(targetEntity="Wms\Admin\LayoutGrid\Entity\Element\Answer", mappedBy="question", cascade={"persist"})
*/
protected $answers;
public $type = 'question'; //Inherited property, now filled in with discriminator value
public function __construct()
{
$this->answers = new ArrayCollection();
}
}
Answer entity
/**
* #ORM\Entity
* #ORM\Table(name="lg_grid_answer")
**/
class Answer extends AbstractEntity
{
/**
* #ORM\ManyToOne(targetEntity="Wms\Admin\LayoutGrid\Entity\Element\Question", inversedBy="answers", cascade={"persist"})
* #ORM\JoinColumn(name="question_id", referencedColumnName="id", onDelete="CASCADE")
**/
protected $question;
public function __toSting() {
return (string) $this->getId();
}
}
Update 2
Updated AbstractElement entity based on #Wilt's answer.
/**
* #ORM\Table(name="lg_grid_element")
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\HasLifecycleCallbacks
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({
* "abstractElement"="AbstractElement",
* "question"="Question",
* //Others
* })
**/
class AbstractElement extends AbstractEntity implements GridElementInterface
{
//Same as above
}
This update created some problems with the NonUniformCollection which handles getting the correct Entity. This used to be based on the $type property.
However, having a $type property in an Entity which has * #ORM\DiscriminatorColumn(name="type", type="string") as a notation, is not allowed. Therefore all the classes making use of a discriminator have also been updated with the following.
const ELEMENT_TYPE = 'question'; //Overwritten from AbstractElement
/**
* #return string
*/
public function getType() //Overwritten from AbstractElement
{
return self::ELEMENT_TYPE;
}
Alas, the original problem remains.
I am not sure if this is causing your issue, but it seems to me that your inheritance mapping is not setup correctly:
You need to declare your discriminator column inside your entity definitions as written in the docs, they should not be set as properties, doctrine takes care of setting them inside your database:
/**
* #InheritanceType("JOINED")
* #DiscriminatorColumn(name="type", type="string")
* #DiscriminatorMap({"element"="AbstractElement", "question"="Question", "text"="TextItem", "media"="MediaItem"})
*/
class AbstractElement extends AbstractEntity implements GridElementInterface
{
//...
}
And is your abstract entity properly mapped as a #MappedSuperClass?
/**
* #MappedSuperclass
*/
class AbstractEntity
{
//...
}
This might be part of your solution, please come back with feedback after you updated accordingly...
At the beginning sorry for my poor English, I hope you understand me. I'm writing a simple portal in Symfony2 and came to the point where it needs to make relationships between tables with MySQL, all the ways of the internet browsed, tested and nothing came of it. The tables below.
http://i.stack.imgur.com/vR77x.png
http://i.stack.imgur.com/GDXDw.png
Now yes, by getting the user from the database, I would like to once stretched to the profession (vocation), but together with its name, is even an option?
If I understand correctly you want to create a OneToOne relationship between your Entities?!
On the Player entity:
/**
* Player
*
* #ORM\Table(name="players")
* #ORM\Entity()
*/
class Player
{
/**
* #ORM\OneToOne(targetEntity="Vocation", inversedBy="player")
* #ORM\JoinColumn(name="vocation", referencedColumnName="id")
*/
private $vocation;
...
}
And at the Vocation one
/**
* Vocation
*
* #ORM\Table(name="vocations")
* #ORM\Entity()
*/
class Vocation
{
/**
* #ORM\OneToOne(targetEntity="Player", mappedBy="vocation")
*/
private $player;
/**
* #var string
*/
private $vocationName;
/**
* #var integer
*/
private $id;
...
}
Something like this?
Also (from looking at your tables) maybe you possibly want a ManyToOne relationship instead of a OneToOne?
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!
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).