How to add additional columns to a join table in Doctrine2? - php

I would like to create a notification system. There is a Notification class. A notification can be assigned to more than one users, not just one.
There is a joint table user_notifications, with two columns: user_id and notification_id
The definition of the $notifications in the user class is this:
/**
* #ManyToMany(targetEntity="Notification")
* #JoinTable(name="user_notifications",
* joinColumns={#JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="notification_id", referencedColumnName="id", unique=true)}
* )
**/
private $notifications;
Everything works fine. But I would like to add a new column to the user_notifications table, where I would like to store, if the notification is read by the given user, or not. How should I manage it in Doctrine2?

You will have to refactor your entities to introduce a new and transform your user_notifications adjacency table into an entity.
Solution
Transform you table as follows:
Then refactor your associations as follows:
User entity
...
/**
* #OneToMany(targetEntity="UserNotification", mappedBy="notification_id")
**/
private $notifications;
Notification entity
...
/**
* #OneToMany(targetEntity="UserNotification", mappedBy="user_id")
**/
private $users;
UserNotification entity
/** #Entity **/
class UserNotification {
...
/**
* #ManyToOne(targetEntity="User", inversedBy="notifications")
* #JoinColumn(name="user_id", referencedColumnName="id")
**/
private $user_id;
/**
* #ManyToOne(targetEntity="Notification", inversedBy="users")
* #JoinColumn(name="notification_id", referencedColumnName="id")
**/
private $notification_id;
/** #Column(type="boolean") */
private $read;

You'll need to create new entity with this extra column.
You can find details in this answer: https://stackoverflow.com/a/15630665/1348344

Related

How to establish one-to-one relation through IDs between doctrine entities

I was hoping this be a straight forward process but it seems Doctrine doesn't really like the idea of linking entities through their IDs.
All I intended to do was normalising a table by shipping some fields from it to a new table and instead of adding a new reference field to the original table to hold the ID of the new corresponding record in the, make sure the new record in the child table will have identical ID to its parent row.
Here is an example of what I have:
A User entity, with annotated field $user to reference column ID in the UserDetail entity to itself's ID
/**
* #ORM\Table(name="user", options={"collate"="utf8_general_ci", "charset"="utf8", "engine"="InnoDB"})
* #ORM\Entity
*/
class User extends Entity
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
* #ORM\GeneratedValue(strategy="IDENTITY")
*/
private $id
/**
* #ORM\OneToOne(targetEntity="UserDetail", cascade={"persist"})
* #ORM\JoinColumn(name="id", referencedColumnName="id", nullable=true)
*/
private $userDetail;
...
}
and here is the UserDetail with its ID's #GeneratedValue removed
/**
* #ORM\Table(name="user_detail", options={"collate"="utf8_general_ci", "charset"="utf8", "engine"="InnoDB"})
* #ORM\Entity
*/
class UserDetail extends Entity
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer", nullable=false)
* #ORM\Id
*/
private $id;
...
}
At this point what my expectation was to be able to do something like:
$user = new User();
$userDetail = new UserDetail();
$user->setUserDetail($userDetail)
$entityManager->persist($user);
$entityManager->flush();
And get two records persisted to the user and user_detail tables with identical IDs, but the reality is, not having any strategy defined for the UserDetail's identifier, doctrine will complaint about the missing ID, Entity of type UserDetail is missing an assigned ID for field 'id'.
Of course it is possible to do the job manually and in more than one call
$user = new User();
$entityManager->persist($user);
$entityManager->flush();
$userDetail = new UserDetail();
$userDetail->setId($user->getId)
$user->setUserDetail($userDetail)
$entityManager->persist($user);
$entityManager->flush();
But I'm still hoping there is a correct configuration (annotation) that can help me to avoid such extra steps and leave handling of a one-to-one relationship through the entity's IDs to Doctrine.
This is untested but I think the following might work, according to the docs (http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/tutorials/composite-primary-keys.html):
/**
* #ORM\Table(name="user_detail", options={"collate"="utf8_general_ci", "charset"="utf8", "engine"="InnoDB"})
* #ORM\Entity
*/
class UserDetail extends Entity
{
/**
* #var integer
*
* #ORM\OneToOne(targetEntity="User")
* #ORM\JoinColumn(name="id", referencedColumnName="id")
* #ORM\Id
*/
private $user;
...
}

adding created timestamp to join table in doctrine2

I have the following property in my User entity to track followers and following. Basically a user can follow other user as well. I have a join column called app_user_follow_user, however I also wanted to add a timestamp of whenever someone follows another user, when did it happen. How can I specify a created timestamp via this ORM?
/**
* #ORM\ManyToMany(targetEntity="User", mappedBy="following")
*/
protected $followers;
/**
* #ORM\ManyToMany(targetEntity="User", inversedBy="followers")
* #ORM\JoinTable(name="app_user_follow_user",
* joinColumns={#ORM\JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#ORM\JoinColumn(name="follow_user_id", referencedColumnName="id")}
* )
*/
protected $following;
Doctrine ManyToMany relationships are used when your join table has two columns. If you need to add another column you have to convert the relationship to OneToMany on both sides and ManyToOne on the joined entity.
This is entirely untested but it will hopefully give you the gist.
User Entity
/**
* #ORM\OneToMany(targetEntity="AppUserFollowUser", mappedBy="appUser")
*/
protected $followers;
/**
* #ORM\OneToMany(targetEntity="AppUserFollowUser", mappedBy="followUser")
*/
protected $following;
AppUserFollowUser Entity
/**
* #ORM\Table(name = "app_user_follow_user")
*/
class AppUserFollowUser
{
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="User", inversedBy="followers")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
* })
*/
private $appUser;
/**
* #ORM\Id
* #ORM\ManyToOne(targetEntity="User", inversedBy="following")
* #ORM\JoinColumns({
* #ORM\JoinColumn(name="follow_user_id", referencedColumnName="id")
* })
*/
private $followUser;
/**
* #ORM\Column(name="created_date", type="datetime", nullable=false)
*/
private $createdDate;
}
I think that you will have to create a link entity manually (entiy1 onetomany linkEntity manytoone entity2.
Because, the usual link entity are automated and should be as simple and (data less) as possible, so doctrine can take all the controle over it,
imagine you need to get the timestamp, how can you do it on an (none hard coded) entity, you will need a getter, and the annotations are not supposed to contains code.

ManyToOne association mapping to a Class Table Inheritance entity in Doctrine 2

I have an Author entity, which is a Class Table Inheritance containing an AuthorUser and an AuthorGroup.
/**
* Author
*
* #ORM\Table
* #ORM\Entity
* #ORM\InheritanceType("JOINED")
* #ORM\DiscriminatorColumn(name="type", type="string")
* #ORM\DiscriminatorMap({"user" = "AuthorUser", "group" = "AuthorGroup"})
*/
class Author {
// ...
}
AuthorUser relates to my User entity and AuthorGroup to my Group entity.
class AuthorUser extends Author
{
/**
* #var User
*
* #ORM\ManyToOne(targetEntity="User", inversedBy="?????")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*/
protected $user;
}
class AuthorGroup extends Author
{
/**
* #var Group
*
* #ORM\ManyToOne(targetEntity="Group", inversedBy="?????")
* #ORM\JoinColumn(name="group_id", referencedColumnName="id")
*/
protected $user;
}
I have no idea how to inverse this. Anyway, the problem is that i have to add this CTI to my Article entity field. How can i relate using ManyToOne to this Article entity field?
class Article
{
/**
* #var Author
*
* #ORM\ManyToOne(targetEntity="Author", inversedBy="?????????")
* #ORM\JoinColumn(name="author_id", referencedColumnName="id")
*/
protected $author;
}
I'm not sure how to make this as transparent as possible. When i create a new Article, i need to provide either an User or Group object to the author field. I followed this behavior, but it doesn't seem to help. It gets even more complicated.
One solution could be to always have AuthorGroups, even when there's only one Author.
Otherwise, take a look at https://github.com/FabienPennequin/DoctrineExtensions-Rateable
You might be able to use that code to provide a similar Authored interface that can discriminate between the AuthorUser and AuthorGroup.

Doctrine 2 entity association does not set value for foreign key

I'm encountering a problem with a Doctrine 2 entity association.
I have a User entity and an Agency entity. One Agency can employ multiple Users (the entities are simplified to show only my problem)
User Entity
/**
* #Entity
* #Table(name="users")
**/
class User
{
/**
* #Id
* #Column(type="integer")
* #GeneratedValue
* #var integer
**/
protected $id;
/**
* #ManyToOne(targetEntity="Agency", inversedBy="users"})
* #JoinColumn(name="agency_id", referencedColumnName="id")
* #var Agency
*/
protected $agency;
}
Agency Entity
/**
* #Entity
* #Table(name="agencies")
**/
class Agency
{
/**
* #Id
* #Column(type="integer")
* #GeneratedValue
* #var integer
**/
protected $id;
/**
* #OneToMany(targetEntity="User", mappedBy="agency", cascade={"all"})
* #JoinColumn(name="id", referencedColumnName="agency_id")
* #var User[]
*/
protected $users;
/**
* Add a user to the agency
*
* #param User $user
* #return void
*/
public function addUser(User $user) {
$this->users[] = $user;
}
}
When I now use the following code to create an agency along with a user, Doctrine does not set the agency_id for the user which results in a mysql contraint error that the user's agency_id must not be null.
// $em is the Doctrine EntityManager
$agency = new Agency;
$user = new User;
$agency->addUser($user);
$em->persist($agency);
$em->flush();
so far, the only way I found to make Doctrine set the agency_id for the user is assigning the agency to the user additionally to adding the user to the agency. In my understanding of an ORM it should already set the agency_id when the user is in the users collection of teh agency and it's beeing saved.
$user->agency = $agency;
$agency->addUser($user);
Is there anything wrong or missing with my annotations/metadata?
I found something in the Doctrine 2 documentation:
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)
As in my case the owning side is the User I must update it. Doctrine 1 was able to manage it automatically... too bad.

primary/foreign key relationship in doctrine

On my mysql db, I have two tables: "User" And "UserProfile". The table "UserProfile" has a foreign key column named 'user_id' which links to "User" table's "id" column. Now, when I am generating all entity classes from my db tables using doctrine, the created "UserProfile" class contains a property named 'user'(which is of "User" type) and doesn't contain any property named 'user_id'. Is it ok?
Now, if I want to find a user's profile, given the user_id, I needed to write something like this:
$user_profile_repo = $this->em->getRepository("UserProfile");
$user_profile = $user_profile_repo->findOneBy(array("user_id"=>$id));
But as the generated entity class doesn't include the "user_id" property, the above code won't work. Now I need to know how can I do the tweak to make the above code work please? Thanks.
the actual names of the tables/columns in the database are not really important. you can set them in the comments.
it should be something like this:
/**
* models\User
* #Table(name="User")
* #Entity
*/
class User
{
/**
* #Column(name="id", type="integer", precision=0, scale=0, nullable=false, unique=false)
* #Id
* #GeneratedValue(strategy="IDENTITY")
*/
private $id;
/**
* #Column(name="name", type="string", length=50, precision=0, scale=0, nullable=false, unique=false)
*/
private $name;
/**
* #OneToOne(targetEntity="models\UserProfile", mappedBy="user")
*/
private $profile;
}
/**
* models\UserProfile
* #Table(name="UserProfile")
* #Entity
*/
class UserProfile
{
/**
* #OneToOne(targetEntity="models\User", inversedBy("profile"))
* #JoinColumn(name="user_id", referencedColumnName="id")
*/
private $user;
}
in this case a user has the column id, and the userprofile has user_id
once generated, you should get in the User the method getProfile() and in the UserProfile you should get getUser()
it is similar to 5.7 oneToOne bidirectional: http://www.doctrine-project.org/docs/orm/2.0/en/reference/association-mapping.html

Categories