Working with multiple relationship in one field on Doctrine 2 - php

I'm having a little problem with a possible multiple relationship in one field in Doctrine 2. For example, i have an Article entity with one field called author. This field is a ManyToOne to the User entity. However, this field can also relate to the Group entity. So, how the hell can i create such schema?
I have considered creating a new entity, called ArticleAuthor which has two fields: user and group, and depending on form input, i populate one of the fields. This way, this table ArticleAuthor holds it's own id and the proper relationship to the correct table. Is this a correct approach?

This is what's referred to as a polymorphic association. Doctrine is able to handle these using Inheritance Mapping
So you'd define your base entity, such as Author, and then you'd have a GroupAuthor and a UserAuthor which both extend this. These both need to be configured as mapped classes on the base Author entity. It's up to you whether you opt for single table or class table inheritance; the end result would be the same.
The last thing to do is to associate the UserAuthor entity to your User entity, and your GroupAuthor to your Group entity.
Then you'd be able to use it somewhat like this:
$author = $article->getAuthor();
if ($author instanceof UserAuthor) {
$user = $author->getUser();
} elseif ($author instanceof GroupAuthor) {
$group = $author->getGroup();
$users = $group->getUsers(); // Not sure if you'd need this?
}
Edit: Example mapping
The 'parent' entity, Author.php
/**
* #ORM\Entity
* #ORM\Table(name="authors")
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap( {"user" = "UserAuthor", "group" = "GroupAuthor"} )
*/
class Author
{
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/* any other shared fields... */
}
Mapped entity, UserAuthor.php
/**
* #ORM\Entity
* #ORM\Table(name="user_authors")
*/
class UserAuthor extends Author
{
/* All columns unique to the UserAuthor entity... */
}
etc

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

How to show relational columns in grid using APYDataGridBundle?

I use APYDataGridBundle for generating a data table.
I have Person entity with a relation (1 gym can have more people):
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Gym", inversedBy="persons")
*/
private $gym;
I generate a grid with a list of people in controller:
public function indexAction(Grid $grid)
{
// Creates a simple grid based on your entity (ORM)
$source = new Entity(Person::class);
$grid->setSource($source);
return $grid->getGridResponse('Person/index.html.twig');
It shows me the grid with all non-relational columns, but the gym column not, because of the relation.
I can't find the solution in doc: https://github.com/APY/APYDataGridBundle/blob/master/Resources/doc/summary.md
Can you help me?
In Person entity:
/**
* #ORM\ManyToOne(targetEntity="App\Entity\Gym", inversedBy="persons")
*
* #GRID\Column(field="gym.id", title="Gym ID")
* #GRID\Column(field="gym.title", title="Gym Title")
*/
private $gym;

Doctrine OneToOne with multiple tables

Im using doctrine 2 and zend framework 3. I got an entity "Project" with an OneToOne Relation to "projectdetails". ProjectDetails is split between multiple tables depending on a the Project field "type".
Is there an easy way to tell doctrine which table to use for the projectdetails depending on the type field Value?
You can define "OneToOne" associations for each of the projectdetails tables:
/**
* #OneToOne(targetEntity="ProjectDetails1")
* #JoinColumn(name="project_details_id", referencedColumnName="id")
*/
private $projectDetails1;
/**
* #OneToOne(targetEntity="ProjectDetails2")
* #JoinColumn(name="project_details_id", referencedColumnName="id")
*/
private $projectDetails2;
Add any more projectdetails tables.
Then use a getter function to get the correct relation based on type:
function getProjectDetails() {
if($this->type === 'type1') {
return $this->projectDetails1;
}
elseif($this->type === 'type2') {
return $this->projectDetails2;
}
}
Update: In this case you cannot use doctrine console tool to generate your associations, as it will be impossible to set the foreign key on "project_details_id" to 2 different tables.
To solve this, the column project_details_id of integer type should be created with a migration or you can define a field in the Project entity:
/**
* #ORM\Column(type="integer", nullable=true)
*/
private $projectDetailsId;
generate & run the migration. Finally replace the latter field with the above associations.

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

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.

Categories