Association mapping in Doctrine - php

I have 2 tables: jobs and categories. The first table has a field called cat_id which is a reference to categories.id. In my Entity Class Job I have annotations like this:
/**
* #ManyToOne(targetEntity="Category")
* #JoinColumn(name="cat_id", referencedColumnName="id")
**/
private $category;
public function __construct()
{
$this->category = new \Doctrine\Common\Collections\ArrayCollection();
}
And in my Category Class I have:
/**
* #OneToMany(targetEntity="Job", mappedBy="job")
* #JoinColumn(name="id", referencedColumnName="cat_id")
*/
private $jobs;
public function __construct()
{
$this->jobs = new \Doctrine\Common\Collections\ArrayCollection();
}
All I want is to get all jobs with their categories and all jobs by category. But I'm still new to Doctrine.

You appear to be overlooking some elements of the owning vs the inverse side of Doctrine relationship mappings. I suggest you read 12. Association Updates: Owning Side and Inverse Side in the Doctrine manual for more details.
Essentially, one side of a 1:N relationship will be the Owning side, and the other the Inverse side. The owning side is the one that actually maps the relationship, while the inverse side simply reflects that mapping. - In your code, you've put the JoinColumn on both sides, as if both are supposed to be the owning side.
Your code should have the Job.category property as the owning side, and the Category.jobs property as the inverse side. So start by changing the Job entity to look more like this:
/**
* #var Category
*
* #ManyToOne(targetEntity="Category", inversedBy="jobs")
* #JoinColumn(name="cat_id", referencedColumnName="id")
**/
private $category;
public function __construct()
{
// $category would be a single instance of Category,
// not a collection. Otherwise you'd be looking at a
// ManyToMany relationship.
}
And then change the Category entity to look like this:
/**
* #var ArrayCollection
*
* #OneToMany(targetEntity="Job", mappedBy="category")
*/
private $jobs;
public function __construct()
{
$this->jobs = new \Doctrine\Common\Collections\ArrayCollection();
}
Note that in the Job entity, I've added the inversedBy attribute to the ManyToOne annotation, indicating the Category.jobs property as the inverse side of the mapping. Then I've removed the JoinColumn from the Category.jobs attribute, since the inverse side shouldn't actually specify a mapping directly; it is reflects the mapping of the owning side.

Related

Doctrine ManyToOne relationship - auto-remove on "set"

I'm working with relationships in Doctrine (using Symfony 5).
What I have is these 2 relations:
User
Availability
User has an ID and has Many Availabilities.
So Entity User has
/**
* #ORM\OneToMany(targetEntity="UserAvailability", mappedBy="user")
*/
private $availability;
and the reverse on Entity Availability.
Availability is a relation with:
id, user_id, day_name, start_time and end_time, that simple.
What I already achieved with ManyToMany and I want to achieve in this case too is:
I need to receive the entire set of availabilities for a User from the client and use it to update the availabilities of my User, so I defined a setAvailability method which receives a Collection of Availability entities and simply does
$this->availabilities = $availabilities.
This works when I add new availabilities but the ones that are on the DB and not in the collection are not dropped when I persist the entity.
The same method works flawlessly with ManyToMany Relationship.
What am I missing?
*** UPDATE **
public function setAvailability($availability): self
{
$this->availability = $availability;
return $this;
}
this same code works when removing relations in ManyToMany relationship but not in ManyToOne, the attribute "availability" is correctly set, but when using persist/flush the availability which was removed is not removed on the DB.
Thanks
Try to set the attributes as in the example from the doctrine documentation below :
<?php
use Doctrine\Common\Collections\ArrayCollection;
/** #Entity */
class User
{
// ...
/**
* One user has many availabilities. This is the inverse side.
* #OneToMany(targetEntity="Availability", mappedBy="user")
*/
private $availabilities;
// ...
public function __construct() {
$this->availabilities = new ArrayCollection();
}
}
/** #Entity */
class Availability
{
// ...
/**
* Many availabilities have one user. This is the owning side.
* #ManyToOne(targetEntity="User", inversedBy="availabilities")
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
// ...
}
the attribute mappedBy and inversedBy are necessary for relations

Doctrine's Many-To-Many Self-Referencing and reciprocity

By default, self-referencing ManyToMany relationships under Doctrine involve an owning side and an inverse side, as explained in the documentation.
Is there a way to implement a reciprocal association whithout difference between both sides?
Following the example in the docs:
<?php
/** #Entity **/
class User
{
// ...
/**
* #ManyToMany(targetEntity="User")
**/
private $friends;
public function __construct() {
$this->friends = new \Doctrine\Common\Collections\ArrayCollection();
}
// ...
}
So, adding entity1 to entity2s friends implies that entity2 will be in entity1s friends.
There are a number of ways to solve this problem, all depending on what the requirements for the "friends" relation are.
Unidirectional
A simple approach would be to use a unidirectional ManyToMany association, and treat it as if it where a bidirectional one (keeping both sides in sync):
/**
* #Entity
*/
class User
{
/**
* #Id
* #Column(type="integer")
*/
private $id;
/**
* #ManyToMany(targetEntity="User")
* #JoinTable(name="friends",
* joinColumns={#JoinColumn(name="user_a_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="user_b_id", referencedColumnName="id")}
* )
* #var \Doctrine\Common\Collections\ArrayCollection
*/
private $friends;
/**
* Constructor.
*/
public function __construct()
{
$this->friends = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #return array
*/
public function getFriends()
{
return $this->friends->toArray();
}
/**
* #param User $user
* #return void
*/
public function addFriend(User $user)
{
if (!$this->friends->contains($user)) {
$this->friends->add($user);
$user->addFriend($this);
}
}
/**
* #param User $user
* #return void
*/
public function removeFriend(User $user)
{
if ($this->friends->contains($user)) {
$this->friends->removeElement($user);
$user->removeFriend($this);
}
}
// ...
}
When you call $userA->addFriend($userB), $userB will be added to the friends-collection in $userA, and $userA will be added to the friends-collection in $userB.
It will also result in 2 records added to the "friends" table (1,2 and 2,1). While this can be seen as duplicate data, it will simplify your code a lot. For example when you need to find all friends of $userA, you can simply do:
SELECT u FROM User u JOIN u.friends f WHERE f.id = :userId
No need to check 2 different properties as you would with a bidirectional association.
Bidirectional
When using a bidirectional association the User entity will have 2 properties, $myFriends and $friendsWithMe for example. You can keep them in sync the same way as described above.
The main difference is that on a database level you'll only have one record representing the relationship (either 1,2 or 2,1). This makes "find all friends" queries a bit more complex because you'll have to check both properties.
You could of course still use 2 records in the database by making sure addFriend() will update both $myFriends and $friendsWithMe (and keep the other side in sync). This will add some complexity in your entities, but queries become a little less complex.
OneToMany / ManyToOne
If you need a system where a user can add a friend, but that friend has to confirm that they are indeed friends, you'll need to store that confirmation in the join-table. You then no longer have a ManyToMany association, but something like User <- OneToMany -> Friendship <- ManyToOne -> User.
You can read my blog-posts on this subject:
Doctrine 2: How to handle join tables with extra columns
More on one-to-many/many-to-one associations in Doctrine 2

Doctrine Compund Keys

I have these 3 table
"Business" with these fields: Id, Name, etc..
"City" with these fields: Id, Name, etc..
And then I have a table called BusinessCity (given that a bussines can be related to many cities). This table has the fields "BusinessId" and "CityId".
Im trying to relate the CityId to the City entity, and BusinessId to the business entity, on the class BusinessCity. I've been googling this for the past 3 days and couldnt find an answer, if this has been asked before im sorry i didnt see it. Could anyone help me or give me some pointers on how to get this done. Thanks in advance
What you are trying to achieve is a bi-directional many-to-many relation with a joinTable.
Many businesses can reside in multiple cities and in one city there can be multiple businesses.
In a many-to-many relationship either side can be the owning side. JoinTable definition can be left out and has sensible defaults but if you want to specify it concretely i included it in the example.
Business (in this example: owning side = inversedBy = JoinTable definition)
/**
* #ORM\ManyToMany(targetEntity="Your/Bundle/City", inversedBy="businesses",cascade="{persist,merge}" fetch="EAGER")
* #ORM\JoinTable(name="BusinessCity",
* joinColumns={#JoinColumn(name="business_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="city_id", referencedColumnName="id")}
* )
*/
protected $cities;
public function __construct()
{
$this->cities = new ArrayCollection();
}
public function getCities()
{
return $this->cities;
}
public function setCities(Collection $cities)
{
// using map with closure to have dublicate/type-checking provided by addCity
$this->cities->map(function($city) {
$this->addCity($city);
});
return $this;
}
public function addCity(CityInterface $city)
{
// ... you don't want dublicates in your collection
if (!$this->cities->contains($city)) {
$this->cities->add($city);
}
return $this;
}
public function removeCity(CityInterface $city)
{
$this->cities->removeElement($city);
return $this;
}
// other properties and methods ..
City (inverse side = mappedBy)
/**
* #ORM\ManyToMany(targetEntity="Your/Bundle/Business", mappedBy="cities")
*/
protected $businesses;
// getters & setters ...
// other properties and methods ...
This is actually pretty simple, all you have to do is define your JoinTable. It's not easy to find in the docs, but there is an example in the section Composite Primary Keys.
In short, all you have to do is use oneToMany/manyToOne-associations with the class representing your JoinTable instead of directly associating both Business and City with ManyToMany-associations.

OneToMany Relation with reference on a third table?

I got two entities with a simple OneToMany relation and anything works as expected.
The user entity:
class User {
/**
* #ORM\OneToMany(targetEntity="Vita", mappedBy="owner")
*/
}
and the vita entity:
class Vita {
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="vitas")
*/
}
So far, so good. But now I changed the vita entity and added the meta entity with a OneToOne relation. This entity knows the owner and other stuff, but the vita does not have longer a owner property.
What I am looking for is the oppotunity to use the third table as reference. Is there a common doctrine way? Something like that?
class User {
/**
* #ORM\OneToMany(targetEntity="Vita", mappedBy="meta.owner")
*/
}
EDIT:
User
- id
- username
- password
Vita
- id
- meta_id
- [other fields]
Meta
- id
- owner_id (User)
- modifier_id (User)
- [other fields]
One User has many Vita
One Vita has one Meta
First the owner_id was a property of vita and now it moved into meta.
According to the relationship schema, it should look like this:
class User {
/**
* #ORM\OneToMany(targetEntity="Meta", mappedBy="owner")
*/
protected $metasOwned;
/**
* #ORM\OneToMany(targetEntity="Meta", mappedBy="modifier")
*/
protected $metasModified;
}
class Vita {
/**
* #ORM\OneToOne(targetEntity="Meta", inversedBy="vita")
*/
protected $meta;
}
class Meta {
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="metasOwned")
*/
protected $owner;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="metasModified")
*/
protected $modifier;
/**
* #ORM\OneToOne(targetEntity="Vita", mappedBy="meta")
*/
protected $vita;
}
Notice that variable names correlate with the inverse-side of the relationships.
Then you just run the command
php app/console doctrine:generate:entities Your/AwesomeBundle/Entity
and it will create getters & setters for you and you will be all set.
Your/AwesomeBundle/Entity: the real path is or could be src/Your/AwesomeBundle/Entity.

Doctrine 2.1 Identity through foreign Entities One-To-One Bidirectional Association

I'm using Doctrine 2.1 where Identity through foreign Entities is supported (Foreign Keys as Identifiers).
http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/tutorials/composite-primary-keys.html#identity-through-foreign-entities
Is it possible to identity through foreign Entities with a bidirectional relationship? I'm trying but I'm getting the following error when I load my data fixtures:
[PDOException]
SQLSTATE[23000]: Integrity constraint violation: 1452 Cannot add or update a child row: a foreign key constraint fails (table.user, CONSTRAINT FK_187DDE86BF396750 FOREIGN KEY (id) REFERENCES address (user_id))
Here are my entities:
/**
* #Entity
*/
class User
{
/** #Id #Column(type="integer") #GeneratedValue */
private $id;
/** #OneToOne(targetEntity="Address") */
private $address;
}
/**
* #Entity
*/
class Address
{
/** #Id #OneToOne(targetEntity="User") */
private $user;
}
If I remove the reference from User to Address the error disappears!
I would like to be able to do the following:
$address = $user->getAddress();
..but it seems I will have to do:
$address = $entityManager->find('Address', $user->getId());
Is that right? Is there a way to do this?
Update 1:
I would like to be able to set the relationship from either side (bidirectional) but this does not seem possible.
To be honest I'm not sure what the point of the inverse side of the relationship is because "Changes made only to the inverse side of an association are ignored".
Taken from the docs here: http://docs.doctrine-project.org/projects/doctrine-orm/en/2.1/reference/association-mapping.html#owning-side-and-inverse-side
What is the point?
From an Application perspective it seems to make more sense to do:
$user->setAddress($address);
rather than:
$address->setUser($user);
However the first isn't possible possible because:
Making the relationship bidirectional brakes things
The User side has to be the inverse side so $user->setAddress($address); will be ignored anyway!!
Will someone explain this please :)
Matthew
Your problem is not related to "Foreign Keys as Identifiers", but to owning-side of a relation :
In Doctrine, a relation may be bidirectional, but in this case the relation must have an "onwing-side". That means :
on your "entities part" : both your entities have an attribute referencing each other. In your case you have $address in User and $user in Address, and you can use $user->getAddress() and $address->getUser()
on your "sql part" : only one table, the owning-side, have an attribute referencing the other table.
You can inform Doctrin what is the owning side by using "mappedBy" on the non-owning side.
In your situation, with Foreign Key as Identifiers, you have not the choice : the owning-side is the one with Foreign Key.
So, test this :
/**
* #Entity
*/
class User
{
/** #Id #Column(type="integer") #GeneratedValue */
private $id;
/** #OneToOne(targetEntity="Address", mappedBy="user") */
private $address;
}
/**
* #Entity
*/
class Address
{
/** #Id #OneToOne(targetEntity="User", inversedBy="address")) */
private $user;
}
Another important point :
In a bidirectional relation, Doctrine can't automaticaly update the other side when you set the non-owning side. So you have to do it manualy. For not having do think about this each time you create new users and addresses, you can automate the process in your setter :
// in class User :
public function setAddress($address) {
$address->setUser($this); // <= here it is !!!
$this->address = $address;
return $this;
}
// in class Address :
public function setUser($user) {
$this->user = $user;
return $this;
}
Now, you can set address like you want, without bothering about this owning-side-things :
$address = new Addresse();
$address->set...
$user->setAddress($address);
Do you have getter for the address field defined in your User entity?
public function getAddress()
{
return $this->address;
}
Remember that the doctrine returns always collection.

Categories