I'm building a database application using Doctrine2. I'm getting somewhat confused by the foreign key mappings. I'm wondering, have I got these examples correct:
One-To-One: An X has exactly one Y.
One-To-Many: An X can have multiple Ys.
Many-To-One: Multiple Xs can have the same Y.
Many-To-Many: Multiple Xs can have multiple Ys.
This is the specific situation that got me confused:
A User has exactly one HomeTown. Many users can belong to the same home town, so the link for the User is:
/**
* #ORM\ManyToOne(targetEntity="HomeTown", inversedBy="localUsers")
*/
$homeTown;
And, the corresponding HomeTown link is:
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="homeTown")
*/
$localUsers;
OR is it:
/**
* #ORM\OneToMany(targetEntity="User", mappedBy="homeTown")
*/
$localUsers;
Some clarification would be much appreciated!
I've been looking at http://doctrine-orm.readthedocs.org/en/latest/reference/association-mapping.html
When you have OneToMany association, the inverted has to be ManyToOne. Saying that, your second option is correct.
TIP: Using Doctrine CLI command orm:validate-schema might also help to identify this issue.
The full path in Symfony app: php app/console doctrine:schema:validate
If you want one city to have many users the mapping should be as it follows
Entity City
/**
* #ORM\OneToMany(targetEntity="User", mappedBy="homeTown")
*/
private $users;
...
public function __construct()
{
$this->users = new ArrayCollection();
}
...
Entity User
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="users")
* #ORm\JoinColumn(name="home_town", referencedColumnName="id")
*/
private $homeTown;
This mapping shows us that the City which is owning side has On-To-Many relation with User(target Entity). Respectively the User which is inversed side has to be annotated with ManyToOne relation because many users have same city. Of course, here the target entity should be City. Its important to specify which column is pointing the foreignkey with the referencedColumnName attribute in JoinColumn annotation. It shows what column of other table points this key. In this example in table User there is column named "home_town" which is a foreign key pointing to column id of table City
In ManyToOne relation you shod use JoinColumn annotation
This mapping is also Bidirectional.
You can make id Unidirectional as in the User Entity do not use "inversedBy=" attribute and remove OneToMany annotation with $user property from the City entity. It is something like when you have to know the city of a particular user, but you do not need to know all users for a specific city
Related
I'm on a project where I have a many to many relationship between team and agent. Because my teams can have multiple agents and my agents can have multiple teams.
I'm in a situation where I'm doing a patch so I can add multiple agents to a team (which is working) but I cannot do a working patch to add multiple teams to an agent.
Is it because of mapped by and inversed by?
UPDATE
In my TEAM entity here is the relation
/**
* #ORM\ManyToMany(targetEntity="MyBundle\Entity\Agent", inversedBy="teams")
*/
private $agents;
Here is the relation in my AGENT entity
/**
* #ORM\ManyToMany(targetEntity="MyBundle\Entity\Team", mappedBy="agents")
*/
private $teams;
In my team controller, when I want to give my team some new agents I'm using this piece of code and it works. I can see all the agents associated to the team in the database.
$team->setAgents($theAgents);
But when I want to do the opposite in my agent controller (assigning some teams to a new agent) the agent is created in the database but it's not assigned to any team in the association table. Even if I'm using this:
$agent->setTeams($theTeams);
Hence, is it maybe because it's not possible with Doctrine? Or perhaps I'm missing something.
This is the expected behavior.
For your ManyToMany relation you have the owning side:
/**
* #ORM\ManyToMany(targetEntity="MyBundle\Entity\Agent", inversedBy="teams")
*/
private $agents;
and the inverse side:
/**
* #ORM\ManyToMany(targetEntity="MyBundle\Entity\Team", mappedBy="agents")
*/
private $teams;
which are defined by the settings inversedBy and mappedBy respectively.
For a ManyToMany relation, you can chose which entity is the owning and which the inverse side, either of them can be defined.
Doctrine only checks the owning side for association changes. Check Working with associations, which means on your case, only $agents of Teams is checked for any changes to be persisted in the database.
On Agents, any changes in teams are ignored.
Changes made only to the inverse side of an association are ignored. Make sure to update both sides of a bidirectional association (or at least the owning side, from Doctrine's point of view)
It is your responsibility to include these changes on the owning side also.
So in setTeams, do something like:
public function setTeams($teams) {
$this->teams = $teams;
foreach($teams as $team) {
$team->addAgent($this);
}
}
Note that in addAgent you have to check if the agent already exists in the collection.
In the end the solution I found is similar to Jannes'.
In my Agent entity I added this function:
/**
* #param mixed $team
*/
public function addTeam(Team $team)
{
$this->teams[] = $team;
$team->addAgent($this);
}
and in my Agent controller:
$teams = $request->get('teams');
foreach ($teams as $team){
$myTeam = $em->getRepository('MyBundle:Team')
->find($team["id"]);
$agent->addTeam($myTeam);
}
By doing so, I was I able to have a working post on both sides!
Thank you all again for your help!
I have two separate entities that I want to link by a one-to-many relationship. But I want this relationship to be ordered, meaning every time I call on the first entity, the elements of the second entity come in a pre-ordered way. I cannot use 'order by' calls because this order has nothing to do with the fields of the second entity. I thought about having one field of the first entity be an array of entities, but I'm not sure how to accomplish that either..
EDIT
So far I have something like this:
/**
* #ORM\OneToMany(targetEntity="FusionDesign\BlogBundle\Entity\Element", mappedBy="page")
*/
private $elements;
and
/**
* #ORM\ManyToOne(targetEntity="FusionDesign\BlogBundle\Entity\Page", inversedBy="elements")
* #ORM\JoinColumn(name="page_id", referencedColumnName="id")
*/
private $page;
I'm aware that I can put "ORDER BY whatever ASC" somewhere in there but that orders according to a column in Element, and that's not what I need, because Element entities and Page entities would never be persisted at the same time, nor by the same process. What I want to do is constructing a basic CMS where the user could generate new pages. First choose the kind of elements a page could potentially have (like header image, banner, title, and so on) and persist Element entities with fields describing the html, routing and controller content according to those choices. Then, when a new page is created, give the user the choice to order those potential elements at will, and bind Element entities following an order that reflects the layout desired.
I thought about having something like this
/**
* #var array
*
* #ORM\Column(name="structure", type="array")
*/
private $structure;
Where the array stores Element entities but I have no idea how to do that.
You just need to define the orderBy attribute in doctrine's mapping configuration for the relation.
YAML Mapping Example:
'Example\Entity\Article':
# [..]
oneToMany:
images:
targetEntity: 'Example\Entity\Article\Image\ArticleImage'
mappedBy: 'article'
orderBy: # <--- here
position: 'ASC'
Hoping for some help thinking this through. Say I'm working with two entities here - User and Group. For the purposes of this example, say each Group has a senior moderator and a junior moderator, both of whom are Users and are mutually exclusive (a user can only be in one group, and cannot be both the senior and junior moderator). So in Group, I might have something like:
class Group
{
/**
*
* #OneToOne(targetEntity="User")
*/
private $seniorModerator;
/**
*
* #OneToOne(targetEntity="User")
*/
private $juniorModerator;
}
Now in User, I don't care whether the user is a senior moderator or junior moderator. I just want to be able to return what group a user is in. I know I'll have to add mappedBys in the Group properties, but I'm sort of stumped because I know I can't have two mappedBys assigned to the same thing ($group for example).
Would the best solution here be to just create two separate inverse properties in User (like $group1 and $group2) and then create a getGroup() getter method that just checks whether either of those properties is populated and returns the one that is?
Is there a less hacky method I'm not thinking of? Appreciate any advice, thanks in advance.
I have similar situation so I have created a third table with manyToOne to User, manyToOne to Group and one field that says if this user for this group is manager. So you can do the same add and two fields that states if user is senior or junior.
i think the best solution is to do a "one to one Unidirectional" association in both group and user entities, so the code for your group will be :
class Group
{
private idGroup;
/**
*
* #OneToOne(targetEntity="User")
* #JoinColumn(name="seniorModerator_id", referencedColumnName="idUser")
*/
private $seniorModerator;
/**
*
* #OneToOne(targetEntity="User")
* #JoinColumn(name="juniorModerator_id", referencedColumnName="idUser")
*/
private $juniorModerator;
}
For your User :
class User{
private idUser;
/**
*
* #oneToOne(targetEntity="Group")
* #JoinColumn(name="group_id", referencedColumnName="idGroup")
*/
private $group;
}
if you want more information on Association Mapping hir are the documentation : http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html#one-to-one-unidirectional
I have some bit of troubles with delete constraint in an entity.
I have an entity merchandise and an entity vehicle with a relation many to one in merchandise, so a merchandise only could be in one vehicle, and a vehicle could have many merchandise. So I have:
class Merchandise{
/**
* #ORM\ManyToOne(targetEntity="Vehicle",inversedBy="merchandise")
* #ORM\JoinColumn(name="vehicle", referencedColumnName="id")
*/
private $vehicle;
}
class Vehicle{
/**
* #ORM\OneToMany(targetEntity="Merchandise",mappedBy="vehicle")
*/
private $merchandise;
}
What I want to get is that when I try to delete a Merchandise which have a vehicle, the Merchandise couldn't be deleted.
But I don't know how can I put an ORM Level restrict constraint. I tried restrict={"remove"} but it doesn't exist in #ORM\OneToMany.
I also try to put a preRemove function which return false, but it doesn't work :(
Any idea?
Thanks!!!
ManyToOne / inversedBy is the OWNING side of the bidirectional relation from doctrine's point of view - which can lead to confusion.
To resolve your issue add cascade operation to your merchandise entity. example:
/**
* #ORM\ManyToOne(targetEntity="Vehicle",mappedBy="merchandise", cascade={"all"})
*/
cascade can be set to a combination of :
persist
remove
merge
detach
all
Improve further by adding cascade ( ORM-level ) to your Vehicle entity aswell. example:
/**
* #ORM\OneToMany(targetEntity="Merchandise", mappedBy="vehicle", cascade={"persist","remove"})
*/
... or use onDelete ( database-level ) with one of
SET NULL
CASCADE
... like this
/**
* #ORM\OneToMany(targetEntity="Merchandise", inversedBy="vehicle", onDelete="CASCADE")
*/
Now if you remove a Vehicle - the related Merchandise entities will be removed. Added Merchandises will automatically be saved.
... finally update your schema and drop -> re-create your database if constraints have not been updated and errors occur. Make sure both sides use the cascade option.
Read more in the documentation chapter Transitive persistence / Cascade Operations.
I am trying to develop a friends system, and I need a Many-To-Many relation on my User entities ; for now, this is what I've done :
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="friends")
*/
protected $friendsWith;
/**
* #ORM\ManyToMany(targetEntity="User", inversedBy="friendsWith")
* #JoinTable(name="friends",
* joinColumns={#JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="friend_user_id", referencedColumnName="id")}
* )
*/
protected $friends;
But I would like to have some extra fields for these relations, for example the creation date or the state (accepted, pending, ...) ; I've created another entity "Friend", and I would like this entity to be used as a link between friends. But I don't really know how to manage this...
Do you have some ideas ?
Thanks !
I'm afraid you need an extra class to make such an association.
Here is the tip from doctrine documentation:
Why are many-to-many associations less common? Because frequently you
want to associate additional attributes with an association, in which
case you introduce an association class. Consequently, the direct
many-to-many association disappears and is replaced by
one-to-many/many-to-one associations between the 3 participating
classes.
http://www.doctrine-project.org/docs/orm/2.1/en/reference/association-mapping.html#many-to-many-unidirectional
I guess it should be Friend -> Special Association Class (with fileds: user_id, friend_id, date created) ->Friend.
And you associate Friend to special class in two filed $myFriends and $imFriendOf :)