ManyToMany Relationship With Type Symfony Doctrine - php

I have this structure of Entities
class Group
{
/**
* #var int
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private int $id;
/**
* #var string
* #ORM\Column(name="name", type="string", length=50, nullable=false)
*/
private string $name;
}
class GroupUser
{
/**
* #var int
*
* #ORM\Id()
* #ORM\GeneratedValue()
* #ORM\Column(type="integer")
*/
private int $id;
/**
* #var Group
* #ORM\ManyToOne(targetEntity="Group", inversedBy="GroupUser")
* #ORM\JoinColumn(name="group_id", referencedColumnName="id", nullable=false)
*/
private Group $group;
/**
* #var string
* #ORM\Column(type="string", length=50, nullable=false)
*/
private string $type;
/**
* #var int
* #ORM\Column(type="integer", nullable=false)
*/
private int $user;
}
And there are two types of users. Admins and Clients. There's a ManyToMany relationship between Group and User. And the property in GroupUser $type is saving the Class of either Admin or Client and the property $user is saving the id.
id
group_id
user
type
1
1
1
Entity\Admin
2
2
1
Entity\Client
How do I join it using doctrine from the Admin and Client-side? Or maybe someone could point out to some resources how this kind of relationship works on doctrine? As I'm having a hard time googling anything out.
I imagine it could be like a conditional leftJoin, but I can't seem to figure it out.

Normaly, you cannot do that on database, because it not safe.
Why is it not safe? because you can have an id in the user column of userGroup table that refer to nothing as it is not linked.
I will write what you should have done, and how you can achieve what you want using your own method:
What you should have done:
In your UserGroup entities, have 2 columns (admin and client) which are linked to the related entities. They can be null (Client is null and admin contain the id of admin entity if it is an admin and vice versa). Then you can delete the type column
How to achieve what you using your method:
As it cannot be done in the database, you will have to do it in some manager. Have a method getUser which will check on your type attribute and return the associated entity from the current id stored in $user
example:
public function getUserFromGroupUser(GroupUser $groupUser){
if('Entity\Admin' ===$groupUser->getType()){
return $this->adminRepository->find($groupUser->getUser());
}
if('Entity\Client' ===$groupUser->getType()){
return $this->ClientRepository->find($groupUser->getUser());
}
throw new \RuntimeException('the type does not exist');
}

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;
...
}

Symfony 2 Doctrine persist Entity with related entities

I have a problem trying to persist a new entity with Symfony 2.7.11 that have a related Entity.
I need to create a Landing that can have many universities so I created 3 tables
landing
landingId (primary Key)
university
universityId (primary Key)
landingUniversity
landingId (both are primary Key) (foreign Key Landing)
universityId (foreign Key University)
And I have just 2 Entities (Landing & University) and a Many To Many relation (unidirectional, because I just want to know the universities added to a landing, so University hasn't got anything about landing)
First, I find each University on my database and I save them. Then I create the new Landing and I add all of them.
$universityRepository = $this->em->getRepository('University');
$universities = array();
foreach ($listUniversities as $universityId){
$university= $cursosRepository->findById($universityId);
$universities[] = $university[0];
}
$newLanding = new Landing();
$newLanding->setName($landing["name"]);
foreach ($universities as $university){
$newLanding->addUniversity($university);
}
$em = $this->getEntityManager();
$em->persist($newLanding);
$em->flush();
And I'm getting this error when symfony executes flush():
Could not resolve type of column "landingId" of class University
What I'm doing wrong?
My Entity:
/**
* Landing
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="LandingRepository")
*/
class Landing
{
/**
* #var integer
*
* #ORM\Column(name="landingid", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var string
*
* #ORM\Column(name="name", type="string", length=250)
*/
private $name;
/**
* #var integer
*
* #ORM\ManyToOne(targetEntity="Language")
* #ORM\JoinColumn(name="languageId", referencedColumnName="languageId")
*/
private $languageId;
/**
* #ORM\ManyToMany(targetEntity="University")
* #ORM\JoinTable(name="landingUniversity",
* joinColumns={#ORM\JoinColumn(name="landingId", referencedColumnName="landingId")},
* inverseJoinColumns={#ORM\JoinColumn(name="unversityId", referencedColumnName="unversityId")}
* )
*/
private $universities;
Thank you so much!!!
The error message is really explicit. Your error is here :
joinColumns={#ORM\JoinColumn(name="landingId", referencedColumnName="landingId")}
should be
joinColumns={#ORM\JoinColumn(name="landingId", referencedColumnName="landingid")}
because your Landing entity doesn't contain any landingId database field, but landingid.
But you'd rather edit the column name of your $id property :
/**
* #var integer
*
* #ORM\Column(name="landingId", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;

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.

Joining tables in Symfony Doctrine and accessing all the variables from Twig template

I'm new to Symfony and Doctrine and I'm trying to join two tables so I can then access the associated values from Twig template easily.
Here is my db scheme:
+--------------------------------------+--------------------+
|Messages | User |
|id user text user_id | id name |
|1 testuser something 1 | 1 John |
+--------------------------------------+--------------------+
This is my Message entity:
/**
* #ORM\Entity
* #ORM\Table(name="Messages")
*/
class Message {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
protected $user_id;
/**
* #ORM\Column(name="text", type="text")
*/
protected $text;
/**
* #ORM\ManyToOne(targetEntity="User")
* */
private $user;
}
And this is my User entity:
/**
* #ORM\Entity
* #ORM\Table(name="Users")
*/
class User {
/**
* #ORM\Id
* #ORM\Column(type="integer")
* #ORM\GeneratedValue(strategy="AUTO")
*/
protected $id;
/**
* #ORM\Column(name="name", type="string", length=255)
*/
protected $name;
}
Then in controller I send $messages variable to Twig template:
$messages = $this->getDoctrine()->getEntityManager()->getRepository('MeMyBundle:Message')->findAll()
And the question is: Is the joing made properly? How can I access name property through message in Twig? Thanks.
Because of your many to one relationship, the variable $user in the Message class should be an object of type User. Because your variables $user and $name are private or protected, you should make getters and setter for them or make Doctrine generate them for you. After that $messages[i]->getUser()-getName() should work. (Generating getters and setters)
For more info about accessing attributes in relationships, take a deeper look at Fetching Related Objects section of the documentation.
From the same symfony documentation page "Of course, if you know up front that you'll need to access both objects, you can avoid the second query by issuing a join in the original query."
If you want a true JOIN instead of a lazily loaded query you can write your own sql query following the documentation.

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