Symfony4 Doctrine ODM - ManyToMany get empty - symfony

I have a problem with my many to many relations
I have a User class that can be on multiple Teams
and a class Team that can have multiple Users
class User
/**
* #var Collection
* #ODM\ReferenceMany(
* targetDocument="App\Model\Document\Team",
* mappedBy="members",
* strategy="setArray"
* )
*/
protected $teams;
class Team
/**
* #var Collection|null
* #ODM\ReferenceMany(
* targetDocument="App\Model\Document\User",
* inversedBy="teams",
* strategy="setArray",
* sort={"username": "asc"},
* cascade={"persist"}
* )
*/
protected $members;
With these annotations, when I add members User to the team and I do a getMembers on an instance of Team, it works.
BUT, when I have an instance of a User (that's a member of a Team) the getTeams return nothing (empty PersistentCollection)
The reference is stored on the Teams Document, as an array of references
"members" : [
{
"$ref" : "User",
"$id" : ObjectId("XXX"),
"$db" : "readDat"
}
],
I don't understand, with the annotations as they are now, I thought Doctrine would do something like this db.getCollection('Team').find({"members.$id":ObjectId("XXX")})
to have the teams but it seems not.
On the other hand, I tried to inverse the mappedBy and inversedBy, I can't even getMembers (on a Team object) or getTeams (on a User object) anymore, both are empty Collection.
How can I have a good Many to Many (without duplicate the references in both Document) ?

Related

Doctrine throw error: "A new entity was found through the relationship" after removing entity

Let's say I have two entities, Project and User with relation.
Project.php
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="User")
* #ORM\JoinColumn(onDelete="SET NULL")
*/
private $creator;
When I remove the User entity, the doctrine leaves the User object(without ID) in the Project entity. In a normal situation, this is fine but I am using DomainEvents. In this scenario, after removing the User entity, DomainEvent triggers saving some data in the DB and secondary saving data(after removing) throw this error. This happens because of now in the Project entity we have the detached(from the EM) User object without ID.
I thought about a listener, that will remove empty objects in the entity after removing, but I am not sure that is a good variant
What is the best variant for solving this error?
The onDelete option doesn't apply a cascade removing.
If you want to do so I think you should have to add the cascade={"remove"} option to the ManyToOne.
Try as following :
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="User", cascade={"remove"})
* #ORM\JoinColumn(onDelete="SET NULL")
*/
private $creator;
Removing entity in doctrine

Symfony2 + Doctrine2 onDelete="restrict"

I want to restrict a delete action on my object, but it's not working.
My code from my first entity:
/**
* #ORM\ManyToMany(targetEntity="Season", inversedBy="clubs")
* #ORM\JoinTable(
* name="clubs_to_seasons",
* joinColumns={
* #ORM\JoinColumn(name="club_id", referencedColumnName="id", onDelete="cascade")
* },
* inverseJoinColumns={
* #ORM\JoinColumn(name="season_id", referencedColumnName="id", onDelete="restrict")
* }
* )
**/
private $seasons;
And second:
/**
* #ORM\ManyToOne(targetEntity="League")
* #Assert\NotBlank(message="validation.custom.not_blank")
*/
private $league;
On database side everything is fine - I cannot delete a Season object, because it has a reference to Club object, but when I use a remove fuction from Entity Manager, Season object is deleted.
I know that on ORM side is a cascade={..} but I need a restrict, not cascade.
BUMP:
If I'm using one-to-many, many-to-one or one-to-one, everything is ok - when I try to delete an object (and I'm using onDelete="restrict" on the database side), then exceptions appears with message, that I cannot delete because I have other object connected with. But when I have many-to-many (as in my example), and I have a season associated with any club (and I have onDelete="restrict"), still I can delete a season using entity manager. But when I'm deleting season directly on the db side, message appears, and I cannot delete this.
What is wrong with many-to-many?
EDIT:
OK, now it's working.
My solution for the future:
I removed many-to-many relation from both entities. I have made a third entity - clubToSeason. Previously this entity was autogenerated, now it's not. And inside file of this third entity I have two many-to-one relations:
class ClubToSeason {
/**
* #ORM\ManyToOne(targetEntity="season", inversedBy="clubs")
**/
private $season;
/**
* #ORM\ManyToOne(targetEntity="club", inversedBy="seasons")
**/
private $club;
...
}

How to define multiple many-to-one relations between two Doctrine tables?

I've got a problem with doctrine with Symfony when I try to define to relations Many-to-one between the table Challenge and User getting the next error.
Error when I try to define:
The table User has the attribute: id, challengesMaked, challengesReceived and the table Challenge has these atributes: id, idUser1, idUser2 where idUser1 I want to relation with id from User and idUser2 I want to relation with id from User too. The relation between User and Challenge are One to Many (One user can challenge to another user) and between Challenge to User are Many to One (One challenge is received only by a User)
So, how can I define this attributes in my entity to fix my bug. Right now, in my User Entity I have defined these attributes like ...
/**
* #var Challenge ArrayCollection
* #ORM\OneToMany(targetEntity="\MQL\PlayerBundle\Entity\Challenge", mappedBy="idUser1", cascade={"persist"})
*/
protected $challengesMaked;
/**
* #var Challenge ArrayCollection
* #ORM\OneToMany(targetEntity="\MQL\PlayerBundle\Entity\Challenge", mappedBy="idUser2", cascade={"persist"})
*/
protected $challengesReceived;
And in the Challenge Entity I have defined ...
/**
* #var User ArrayCollection
* #ORM\ManyToOne(targetEntity="\FOS\UserBundle\Entity\User", inversedBy="challengesMaked")
**/
protected $idUser1; //Challenger
/**
* #var User ArrayCollection
* #ORM\ManyToOne(targetEntity="\FOS\UserBundle\Entity\User", inversedBy="challengesReceived")
**/
protected $idUser2; //Challenged
What am I doing wrong?

Doctrine2 OneToMany without mappedBy

I have an entity 'listing' with OneToMany to entity 'view', the key between these to is view.content_id which holds the ID of listing, however, it also relates to other entities, so by adding
/**
* #var Listing
*
* #ORM\ManyToOne(targetEntity="\Acme\Bundle\Entity\Listing", inversedBy="views")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="content_id", referencedColumnName="id")
* })
*/
private $listing;
To the view it brakes because when saving the view entity the content_id becomes null.
How can I fix it?
Relation on listing side:
/**
* #var views
*
* #ORM\OneToMany(targetEntity="\Acme\Bundle\Entity\View", mappedBy="listing")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="id", referencedColumnName="content_id")
* })
*/
private $views;
I'm making queries by joining the Listing.views and adding WITH content_type = :ContentType which discriminates some 'view' results.
I'm coming pretty late to the party, but thought I would answer in case it helps someone else.
Because of the relation you've established between the two entities, you now have to assign an object to that property rather than the FK value itself. Like so:
$listing = $em->getRepository('Acme\Bundle\Entity\Listing')
->find($content_id);
$view->setListing($listing);
And not:
$view->setListing($content_id);
Of course, if it turned out to be something else, I'd be curious to know.
:^)

Composite key and form

I have the following associations in my database (simplified version):
This is a Many-To-Many association but with an attribute on the joining table, so I have to use One-To-Many/Many-To-One associations.
I have a form where I can add as many relations as I want to one order item and create it at the same time (mainly inspired by the How to Embed a Collection of Forms tutorial from the documentation.
When I post the form, I get the following error:
Entity of type TEST\MyBundle\Entity\Relation has identity through
a foreign entity TEST\MyBundle\Entity\Order, however this entity
has no identity itself. You have to call EntityManager#persist() on
the related entity and make sure that an identifier was generated
before trying to persist 'TEST\MyBundle\Entity\Relation'. In case
of Post Insert ID Generation (such as MySQL Auto-Increment or
PostgreSQL SERIAL) this means you have to call EntityManager#flush()
between both persist operations.
I understand this error because Doctrine tries to persist the Relation object(s) related to the order since I have the cascade={"persist"} option on the OneToMany relation. But how can I avoid this behavior?
I have tried to remove cascade={"persist"} and manually persist the entity, but I get the same error (because I need to flush() order to get the ID and when I do so, I have the same error message).
I also tried to detach() the Relation objects before the flush() but with no luck.
This problem seems unique if 1) you are using a join table with composite keys, 2) forms component, and 3) the join table is an entity that is being built by the form component's 'collection' field. I saw a lot of people having problems but not a lot of solutions, so I thought I'd share mine.
I wanted to keep my composite primary key, as I wanted to ensure that only one instance of the two foreign keys would persist in the database. Using
this entity setup as an example
/** #Entity */
class Order
{
/** #OneToMany(targetEntity="OrderItem", mappedBy="order") */
private $items;
public function __construct(Customer $customer)
{
$this->items = new Doctrine\Common\Collections\ArrayCollection();
}
}
/** #Entity */
class Product
{
/** #OneToMany(targetEntity="OrderItem", mappedBy="product") */
private $orders;
.....
public function __construct(Customer $customer)
{
$this->orders = new Doctrine\Common\Collections\ArrayCollection();
}
}
/** #Entity */
class OrderItem
{
/** #Id #ManyToOne(targetEntity="Order") */
private $order;
/** #Id #ManyToOne(targetEntity="Product") */
private $product;
/** #Column(type="integer") */
private $amount = 1;
}
The problem I was facing, if I were building an Order object in a form, that had a collection field of OrderItems, I wouldn't be able to save OrderItem entity without having saved the Order Entity first (as doctrine/SQL needs the order id for the composite key), but the Doctrine EntityManager wasn't allowing me to save the Order object that has OrderItem attributes (because it insists on saving them en mass together). You can't turn off cascade as it will complain that you haven't saved the associated entities first, and you cant save the associated entities before saving Order. What a conundrum. My solution was to remove the associated entities, save Order and then reintroduce the associated entities to the Order object and save it again. So first I created a mass assignment function of the ArrayCollection attribute $items
class Order
{
.....
public function setItemsArray(Doctrine\Common\Collections\ArrayCollection $itemsArray = null){
if(null){
$this->items->clear();
}else{
$this->items = $itemsArray;
}
....
}
And then in my Controller where I process the form for Order.
//get entity manager
$em = $this->getDoctrine()->getManager();
//get order information (with items)
$order = $form->getData();
//pull out items array from order
$items = $order->getItems();
//clear the items from the order
$order->setItemsArray(null);
//persist and flush the Order object
$em->persist($order);
$em->flush();
//reintroduce the order items to the order object
$order->setItemsArray($items);
//persist and flush the Order object again ):
$em->persist($order);
$em->flush();
It sucks that you have to persist and flush twice (see more here Persist object with two foreign identities in doctrine). But that is doctrine for you, with all of it's power, it sure can put you in a bind. But thankfully you will only have to do this when creating a new object, not editing, because the object is already in the database.
You need to persist and flush the original before you can persist and flush the relationship records. You are 100% correct in the reason for the error.
I assume from the diagram that you are trying to add and order and the relation to the contact at the same time? If so you need to persist and flush the order before you can persist and flush the relationship. Or you can add a primary key to the Relation table.
I ended up creating a separated primary key on my Relation table (instead of having the composite one).
It looks like it is a dirty fix, and I am sure there is a better way to handle this situation but it works for now.
Here is my Relations entity:
/**
* Relation
*
* #ORM\Entity
*/
class Relation
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #ORM\ManyToOne(targetEntity="Contact", inversedBy="relation")
*/
protected $contact;
/**
* #ORM\ManyToOne(targetEntity="Order", inversedBy="relation")
*/
protected $order;
/**
* #var integer
*
* #ORM\Column(name="invoice", type="integer", nullable=true)
*/
private $invoice;
//Rest of the entity...
I then added the cascade={"persist"} option on the OneToMany relation with Order:
/**
* Orders
*
* #ORM\Entity
*/
class Order
{
/**
* #ORM\OneToMany(targetEntity="Relation", mappedBy="order", cascade={"persist"})
*/
protected $relation;
//Rest of the entity...
Et voilĂ !

Resources