Doctrine 2 Inheritance Mapping Strategy Advice needed - php

I'll try to explain my problem. Shortly, I have address table, rooms table, buildings table and users tables. Address table should be able to keep address for rooms, buildings and users by their ID's of course. Since I can't use direct Association Mapping I need an inheritance mapping. Let me make this clear that I'm not good at inheritance mapping issue. My solution is to create an Alias table which keeps the ID of the rooms, buildings and users (also their class names) and address table can interact directly with this Alias table. So in SQL style this would be join Alias and users then join the address table find the user's address. However, I couldn't decide which mapping style would fit. Can you give me some advice about it and give me a short explanation why it is like that?
Thanks.

I suggest you to use relations like
I suppose this structure (user can own address, building have address, room have building)
users
address
buildings
rooms
And code for it
/**
* #orm:Table(name="rooms")
* #orm:Entity
*/
class Room {
....
/**
* #orm:InheritanceType("JOINED")
* #orm:OneToOne(targetEntity="Building")
* #orm:JoinColumn(name="bid", referencedColumnName="id", onDelete="CASCADE", nullable=false)
*/
private $building;
....
}
/**
* #orm:Table(name="Buildings")
* #orm:Entity
*/
class Building {
....
/**
* #orm:InheritanceType("JOINED")
* #orm:OneToOne(targetEntity="Address")
* #orm:JoinColumn(name="aid", referencedColumnName="id", onDelete="CASCADE", nullable=false)
*/
private $address;
....
}
/**
* #orm:Table(name="rooms")
* #orm:Entity
*/
class Address {
....
/**
* #orm:InheritanceType("JOINED")
* #orm:OneToOne(targetEntity="User")
* #orm:JoinColumn(name="uid", referencedColumnName="id", onDelete="CASCADE", nullable=false)
*/
private $user;
....
}
/**
* #orm:Table(name="users")
* #orm:Entity
*/
class User {
....
}
#orm: needs when using doctrine with symfony

Related

Doctrine CTI eagerly load associated entities defined in a subclass of an association

I am trying to eagerly load associated entities defined in a subclass of an association (in DQL) so that this association is not lazy-loaded instead (which caused many individual queries.)
Example situation
Suppose I have a business with people (which I will call persons for simplicity) of which several are employees. An employee can have books in its possession while all other persons can not.
/**
* #Entity
*/
class Business
{
/**
* #OneToMany(targetEntity="Person", mappedBy="business")
*/
private $persons;
}
/**
* #Entity
* #InhertianceType("JOINED")
* #DiscriminatorColumn(name="discr", type="string")
* #DiscriminatorMap({"person" = "Person", "employee" = "Employee"})
*/
class Person
{
/**
* #ManyToOne(targetEntity="business", inversedBy="persons")
* #JoinColumn(name="business_id", referencedColumnName="id")
*/
private $business;
}
/**
* #Entity
*/
class Employee extends Person
{
/** ... */
private $books;
}
/**
* #Entity
*/
class Book { /* ... */ }
Entities representing the situation in PHP.
Problem
I want to obtain a business entity and all its persons while also eagerly loading the books of all employees so that they aren't lazy-loaded for each indivudual employee.
If I were to do this via a DQL query I would have to down-cast the Person association to Employee in order to join the books. This is however not possible in DQL. (see relevant issue about this topic)
Possible non-working example in DQL:
SELECT
business, employee, employeeBook
JOIN
CAST(business.persons AS Entity\Employee) AS employee
JOIN
employee.books AS employeeBook
Since this is a non-working example... How would/should I do this and how?

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

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

Resolving [Doctrine\ORM\ORMException] ManyToOne Column name `id` referenced for relation from Comment towards User does not exist

Class Comment
/**
* #var \Caerus\AppBundle\Entity\Users
*
* #ORM\ManyToOne(targetEntity="User" , inversedBy="comment")
*
* #ORM\JoinColumn(name="user_id", referencedColumnName="user_id")
*
*/
protected $user;
Class User
/**
* #var mixed
*
* #ORM\OneToMany(targetEntity="Comment", mappedBy="user")
*/
protected $comment;
Basically quite simple. I need the comments class to have a user_id field which is a direct copy of the original user_id field from the users class.
The error is as following:
[Doctrine\ORM\ORMException] ManyToOne Column name id referenced for relation from Comment towards User does not exist
Why exactly is it still saying doesn't exist and how do I solve that ?
Referenced Column name should be the "id" property of the User class.
/**
* #var \Caerus\AppBundle\Entity\Users
*
* #ORM\ManyToOne(targetEntity="User" , inversedBy="comment")
* #ORM\JoinColumn(name="user_id", referencedColumnName="id")
*
*/
protected $user;
P.S.
I would also name the OneToMany property "comments" as it holds many Comment objects.
"#var \Caerus\AppBundle\Entity\Users" should be ...\User as your class is called User
Running php bin/console doctrine:schema:validate will give you more insight into which entity classes and columns are involved in the error condition.
Also you can use this code:
/**
* #var \Caerus\AppBundle\Entity\Users
*
* #ORM\ManyToOne(targetEntity="User" , inversedBy="comment")
*
* #ORM\JoinColumn(nullable=true , referencedColumnName="variable_id_of_comment")
*/
protected $user;

doctrine query builder join adding table twice

UPDATED at bottom:
I am trying to do what should be a simple join between two tables. I have a Gig table and a Venue table for a simple band site that I am building using Symfony2 (2.2). It's my first time with Symfony2 and doctrine so it is possible I am going completely in the wrong direction. I have created and populated the tables with DataFixtures and have verified that the ID relationships are correct. The problem I am getting is that the resulting DQL query has the Gig table referenced twice in the FROM section and that is causing me to get back several instances of the same record instead of the x number of records I am expecting. I don't know what I am doing wrong for that to be happening. Also, there may be an easier way of doing this but I am exploring all of my options since I am teaching myself Symfony2 in the process of building the site.
The Gig table contains a venue_id pointing to a Venue table that is defined in the Gig entity as a ManyToOne relationship (shown below). Using a doctrine findAll everything seems to work fine with the Venue class in the Gig Entity being populated correctly. I am trying to create a flat view of a few of the most recent Gigs to be displayed on the front page so I figured I would try to use a Join and include only the fields I need.
Here is the Repository Query:
public function getGigsWithLimit($maxGigs)
{
$qb = $this->createQueryBuilder('b')
->select('
g.gigDate,
g.startTime,
g.endTime,
g.message1 as gig_message1,
g.message2 as gig_message2,
g.url,
v.name,
v.address1,
v.address2,
v.city,
v.state,
v.zip,
v.phone,
v.url as venue_url,
v.message1 as venue_message1,
v.message2 as venue_message2,
v.message3 as venue_message3'
)
->from('WieldingBassBundle:Gig', 'g')
->leftJoin('g.venue', 'v')
->orderBy('g.gigDate', 'DESC')
->setMaxResults($maxGigs);
return $qb->getQuery()->getResult();
}
Here is the DQL it creates:
SELECT
g0_.id AS id0,
g0_.gig_date AS gig_date1,
g0_.start_time AS start_time2,
g0_.end_time AS end_time3,
g0_.message1 AS message14,
g0_.message2 AS message25,
g0_.url AS url6,
v1_.name AS name7,
v1_.address1 AS address18,
v1_.address2 AS address29,
v1_.city AS city10,
v1_.state AS state11,
v1_.zip AS zip12,
v1_.phone AS phone13,
v1_.url AS url14,
v1_.message1 AS message115,
v1_.message2 AS message216,
v1_.message3 AS message317
FROM
Gig g2_,
Gig g0_
LEFT JOIN
Venue v1_ ON g0_.venue_id = v1_.id
LIMIT
6
The Gig g2_ is my problem. If I delete it and execute the query everything is as expected. I don't know what is generating that.
The first table Gigs Entity looks like this (I am leaving out the getters and setters):
/**
* Gig
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="Wielding\BassBundle\Entity\GigRepository")
* #ORM\HasLifecycleCallbacks
*/
class Gig
{
/**
* #var integer
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var \DateTime
*
* #ORM\Column(name="gig_date", type="date")
*/
private $gigDate;
/**
* #var \DateTime
*
* #ORM\Column(name="start_time", type="datetime")
*/
private $startTime;
/**
* #var \DateTime
*
* #ORM\Column(name="end_time", type="datetime")
*/
private $endTime;
/**
* #var string
*
* #ORM\Column(name="message1", type="string", length=50, nullable=true)
*/
private $message1;
/**
* #var string
*
* #ORM\Column(name="message2", type="string", length=50, nullable=true)
*/
private $message2;
/**
* #var string
*
* #ORM\Column(name="url", type="string", length=128, nullable=true)
*/
private $url;
/**
* #var integer
*
* #ORM\Column(name="venue_id", type="integer")
*/
private $venueId;
/**
* #ORM\Column(type="datetime")
*/
protected $created;
/**
* #ORM\Column(type="datetime")
*/
protected $updated;
/**
* #ORM\ManyToOne(targetEntity="Venue", cascade="persist")
* #ORM\JoinColumn(name="venue_id", referencedColumnName="id")
*/
protected $venue;
The Venue table is simple and does not have any relationships defined so I will leave it out unless it is asked for.
Any ideas? Thanks for any help.
Andrew
I removed everything except what would recreate the problem and here is what I was left with:
I simplified the repository method to:
public function getGigsWithLimit2($maxGigs)
{
$qb = $this->createQueryBuilder('a')
->select('g.id')
->from('WieldingBassBundle:Gig', 'g')
->setMaxResults($maxGigs);
return $qb->getQuery()->getResult();
}
This now generates:
SELECT
g0_.id AS id0
FROM
Gig g1_,
Gig g0_
LIMIT
6
There is that darn Gig g1_ problem again. I got the "Explain Query" from the Symfony profiler and it shows:
id select_type table type possible_keys key key_len ref rows Extra
1 SIMPLE g1_ index IDX_ED7D664240A73EBA 4 9 Using index
1 SIMPLE g0_ index IDX_ED7D664240A73EBA 4 9 Using index; Using join buffer
I don't pretend to know what that means but it shows both table entries with different information about how it was used.
whats the use of "venueId" if you already got "venue" which contains the foreign key?
I found the problem. I am not used to Doctrine and was using the ->from in a repository that did not need it since the entity was automatically related through the annotations. My earlier query that worked was in the controller and not a repository so the ->from was necessary.
You are trying to do SQL. Doctrine is different. Your query fetches every field. Doctrine prefers to fetch entities. I think you probably want this:
public function getGigsWithLimit($maxGigs)
{
$qb = $this->createQueryBuilder('g')
->leftJoin('g.venue', 'v')
->orderBy('g.gigDate', 'DESC')
->setMaxResults($maxGigs);
return $qb->getQuery()->getResult();
}
The return result is a list of entities which you can call all the specific methods directly. You are still welcome to specify fields with doctrine, if you want partial objects, but I've found the normal method of fetching the entire entity covers most of my needs.
This is a fundamentally different paradigm than SQL and will take some getting used to.

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