Need help understanding Doctrine many to many self referencing code - php

i am having trouble deciphering this block of code from doctrine documentation
/** #Entity */
class User
{
// ...
/**
* #ManyToMany(targetEntity="User", mappedBy="myFriends")
*/
private $friendsWithMe;
/**
* #ManyToMany(targetEntity="User", inversedBy="friendsWithMe")
* #JoinTable(name="friends",
* joinColumns={#JoinColumn(name="user_id", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="friend_user_id", referencedColumnName="id")}
* )
*/
private $myFriends;
// ...
}
below is how i decipher a one to many bidirectional relationship
(source: tumblr.com)
but if i use the same method, ... below is what i get
alt text http://img514.imageshack.us/img514/2918/snagprogram0000.png
UPDATE
i shld clarify my question. basically, i dont understand how is the opposite of myFriends, friendsWithMe. how i shld make sense of this code and more importantly know how to code such relationships myself.

i give a try at answering my question, i am still quite blur with this, hope someone can really give a better answer,
so 1st to answer the question abt how do i derive with $friendsWithMe
basically, i started off with "decoding" a simpler, more common, many to many bidirectional relationship.
1 user can be in many groups
$user->groups
1 group can have many users
$group->users
very straight forward. but how does this make sense in SQL?
code to implement
# select groups user is in
select group_id from users_groups
where user_id = 1
#select users of group
select user_id from users_groups
where group_id = 1
now to the actual model ... in SQL
in code
# select friends of given user
# $user->myFriends
select friend_id from friends
where user_id = 1;
# select users that are friends of given user
# $user->friendsWithMe
select user_id from friends
where friend_id = 1;
ah ha! select users that are friends of given user. so this is how i get $friendsWithMe. then to fill up the inversedBy & mappedBy & the rest of the class?
1st look at the bottom note.
not clear without so much and deep thinking, abt 2 days. i guess
then as practice how do i create a many to many self referencing relationship from scratch?
the example i am going to work on is... hmm, quite crappy i think but, i'll try :) ... 1 user/student can have many teachers. 1 teacher can have many users/students. 1 user can be a teacher and student here. u know like in forums such as these, when u answer someones questions, you are a teacher. when u ask, u are a student
the ERD will look like
some code to select, students of teachers, teachers of students
# select students of teacher
# $teacher->students
select student from teacher_student
where teacher = 1;
# select teachers of student
# $student->teachers
select teacher from teacher_student
where student = 2;
ok, the doctrine part?
/** #Entity #Table(name="users")) */
class User {
/**
* #Id #Column(type="integer")
* #GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #Column(type="string", length="30")
*/
private $name;
/**
* #ManyToMany(targetEntity="User", inversedBy="teachers")
* #JoinTable(name="Teachers_Students",
* joinColumns={#JoinColumn(name="teacher", referencedColumnName="id")},
* inverseJoinColumns={#JoinColumn(name="student", referencedColumnName="id")}
* )
*/
private $students;
/**
* #ManyToMany(targetEntity="User", mappedBy="students")
*/
private $teachers;
}
which generated this tables for me
# users
CREATE TABLE `users` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(30) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
#teachers_students
CREATE TABLE `teachers_students` (
`teacher` int(11) NOT NULL,
`student` int(11) NOT NULL,
PRIMARY KEY (`teacher`,`student`),
KEY `student` (`student`),
CONSTRAINT `teachers_students_ibfk_2` FOREIGN KEY (`student`) REFERENCES `users` (`id`),
CONSTRAINT `teachers_students_ibfk_1` FOREIGN KEY (`teacher`) REFERENCES `users` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1
at last i done it! lets test it ... erm i am getting
Fatal error: Class 'Entities\User' not
found in
D:\ResourceLibrary\Frameworks\Doctrine\tools\sandbox\index.php
on line 61
when i try to do a
$user = new User;
zzz ...
i have also blogged abt this question and my explaination on my tumblr

The question is, having the M:N table:
friend_user_id
user_id
with two users id 1 and 2. Do you have only:
friend_user_id = 1 and user_id = 2
or both
friend_user_id = 1 and user_id = 2
user_id = 2 and friend_user_id = 1
You can implement both ways, depending on how you code the management of the collection of the owning side.
Case A:
public function addFriend(User $friend)
{
$this->myFriends[] = $friend;
}
Case B:
public function addFriend(User $friend)
{
$this->myFriends[] = $friend;
$friend->myFriends[] = $this; // php allows to access private members of objects of the same type
}

Related

Doctrine ManyToMany Unidirectional with shared attribute. Symfony 3.4

I've 2 entities: Objetct and Product with a extra ID in each table: officeId
This id is in every table (I cant modify the database )
And 3 tables:
Object
Product
ObjectProduct
I want a manyTomany unidirectional relation.
Entity Object:
class Object
{
/**
* #var Products[]|ArrayCollection
*
* #ORM\ManyToMany(targetEntity="AppBundle\Entity\Products")
* #ORM\JoinTable(name="ObjectProduct",
* joinColumns={#ORM\JoinColumn(name="objectId", referencedColumnName="id"),
* #ORM\JoinColumn(name="officeId", referencedColumnName="officeId")},
* inverseJoinColumns={#ORM\JoinColumn(name="productId", referencedColumnName="id"),
* #ORM\JoinColumn(name="officeId", referencedColumnName="officeId")}
* )
*
*/
private $products;
}
My problem is when try to insert, insert the officeId attribute twice:
like:
INSERT INTO ObjectProduct (objectId, officeId, productId, officeId) VALUES (?, ?, ?, ?)
When you use a many to many relation ship (either uni or bi directionnal) it creates a join table. You seems to be aware of this part.
This join table contains tow foreign key that reprensent a composite primary key (to be sure of the unicity). This is automatique
If you wish to change anything (ad an extra ID , extra columns...) then you will need to map it as an entity inside you project and define one to many relation ship instead of many to many

Symfony subquery for entity

Have a problem with subquery with symfony.
What I try to do - I have a table with users and a table with posts.
Posts Users
id|author|content id|username
I want create subquery to get user name by id.
/**
* #return array
*/
public function findAll()
{
return $this->getEntityManager()->createQuery(
'SELECT a, (SELECT u.username
FROM BackendBundle:User u WHERE u.id = a.author) as authorName
FROM BackendBundle:Article a'
)->getResult();
}
Result:
What am I doing wrong? What is the best way to join column from other table by id? Maybe i can use annotations?
Thx for any help.
You don't need a subquery here, what you need is a simple (INNER) JOIN to join Users with their Articles.
$em->createQuery("SELECT a FROM Article JOIN a.author'");
You don't even need an on clause in your join, because Doctrine should already know (through annotations on your entities or a separate yaml file), that the article.author field relates to user.id.
Edit:
I assume you have a User entity that is One-To-Many related to the Article entity.
class User
{
// ...
/**
* #OneToMany(targetEntity="Article", mappedBy="author")
*/
private $articles;
// ...
}
class Article
{
// ...
/**
* #var User
* #ManyToOne(targetEntity="User", inversedBy="articles")
*/
private $author;
// ...
}
Please refer to doctrines association mapping documentation: http://docs.doctrine-project.org/projects/doctrine-orm/en/latest/reference/association-mapping.html

One-To-Many relationship deletes foreign key in Doctrine

This is driving me crazy.
A Client can have many Vehicles.
This is a one to many relationship. When trying to save the entities I get an error saying that the foreign key is null. When I remove the Doctrine relation and store the Vehicle separately everything is working fine.
This is how I created the relation:
class Vehicle {
...
/**
* #ORM\ManyToOne(targetEntity="Client", inversedBy="vehicles")
* #ORM\JoinColumn(name="client_id", referencedColumnName="id")
*/
public $client;
}
class Client {
...
public function __construct()
{
parent::__construct();
$this->vehicles = new \Doctrine\Common\Collections\ArrayCollection();
}
/**
* #ORM\OneToMany(targetEntity="Vehicle", mappedBy="client", cascade={"persist"})
*/
private $vehicles;
}
I try to save the entities like this:
$client = new Client();
$vehicle = new Vehicle();
$client->getVehicles()->add($vehicle);
$em->persist($client);
$em->flush();
Next I get a PDO exception saying that client_id can't be null on the Vehicle table.
It seems like Doctrine is not copying the foreign key correctly.
What am I doing wrong?
According to their docs:
It is not possible to use join columns pointing to non-primary keys.
Doctrine will think these are the primary keys and create lazy-loading proxies with the data, which can lead to unexpected results. Doctrine can for performance reasons not validate the correctness of this settings at runtime but only through the Validate Schema command.
Obviously you need a different approach.
A solution is given in their example:
CREATE TABLE product (
id INTEGER,
name VARCHAR,
PRIMARY KEY(id)
);
CREATE TABLE product_attributes (
product_id INTEGER,
attribute_name VARCHAR,
attribute_value VARCHAR,
PRIMARY KEY (product_id, attribute_name)
);
This schema should be mapped to a Product Entity as follows:
class Product
{
private $id;
private $name;
private $attributes = array();
}
Where the attribute_name column contains the key and attribute_value contains the value of each array element in $attributes.

Doctrine nullable one-to-one relationship still wants to create unique index

I have a Person entity which has two relations (hometown and current) to Location table. Both of these fields can be null, otherwise they must exist in the Location table:
class Person {
.....
/**
* #var Location
* #ORM\OneToOne(targetEntity="Location")
* #ORM\JoinColumn(name="hometown_id", referencedColumnName="id",nullable=true)
**/
protected $hometown;
/**
* #var Location
* #ORM\OneToOne(targetEntity="Location")
* #ORM\JoinColumn(name="current_id", referencedColumnName="id", nullable=true)
**/
protected $current;
....
}
Now, I want to update my db schema, based on doctrine:schema:update --dump-sql output, but it creates problems:
CREATE UNIQUE INDEX UNIQ_8D93D6494341EE7D ON person (hometown_id);
CREATE UNIQUE INDEX UNIQ_8D93D649B8998A57 ON person (current_id);
I cannot define these indexes as there are more than one null row in the table.
Would you please help me?
A OneToOne relationship is unique as it would mean that only one person could be assigned to one location and one location to one person.
In your scenario you would want one person to have multiple locations and one location could have multiple person(s). This would be a ManyToMany relationship.
In Doctrine when you use a ManyToMany you will specify a JoinTable that Doctrine will manage (You don't have to create an entity for a JoinTable). The JoinTable breaks down the ManyToMany to something like a OneToMany such as one person to many location(s) as shown in example below. The JoinTable will store the values you want when they apply.
/**
* #ORM\ManyToMany(targetEntity="Location")
* #ORM\JoinTable(name="hometown_location",
* joinColumns={#ORM\JoinColumn(name="person_id", referencedColumnName="id", unique=true)},
* inverseJoinColumns={#ORM\JoinColumn(name="location_id", referencedColumnName="id")}
* )
**/
protected $hometown;
/**
* #ORM\ManyToMany(targetEntity="Location")
* #ORM\JoinTable(name="current_location",
* joinColumns={#ORM\JoinColumn(name="person_id", referencedColumnName="id", unique=true)},
* inverseJoinColumns={#ORM\JoinColumn(name="location_id", referencedColumnName="id")}
* )
**/
protected $current;
public function __construct() {
$this->hometown = new \Doctrine\Common\Collections\ArrayCollection();
$this->hometown = new \Doctrine\Common\Collections\ArrayCollection();
}
If there is no location to assign to hometown or current that is fine, no space is taken up.
When you do have a location to assign to either hometown or current it will have to be a valid location from the location table.
It looks like you are looking for FOREIGN KEY
http://dev.mysql.com/doc/refman/5.6/en/create-table-foreign-keys.html
ALTER TABLE `Person` ADD INDEX ( `hometown_id` ) ;
ALTER TABLE `Person` ADD FOREIGN KEY ( `hometown_id` ) REFERENCES `Location` ( `id` )
ON DELETE RESTRICT ON UPDATE RESTRICT ;
ALTER TABLE `Person` ADD INDEX ( `current_id` ) ;
ALTER TABLE `Person` ADD FOREIGN KEY ( `current_id` ) REFERENCES `Location` ( `id` )
ON DELETE RESTRICT ON UPDATE RESTRICT ;

Is it possible to reference a column other than 'id' for a JoinColumn?

I have an Item entity that has a ManyToOne relationship to a Category entity. I want them to be joined by a field other than Category's id (in this case, a field called id2). My schema is listed below.
class Item {
/**
* #ORM\Id
* #ORM\Column(name = "id", type = "integer")
* #ORM\GeneratedValue(strategy = "AUTO")
*/
protected $id;
/**
* #ORM\ManyToOne(targetEntity = "Category")
* #ORM\JoinColumn(name = "category_id", referencedColumnName = "id2")
*/
protected $category;
}
class Category {
/**
* #ORM\Id
* #ORM\Column(name = "id", type = "integer")
* #ORM\GeneratedValue(strategy = "AUTO")
*/
protected $id;
/**
* #ORM\Column(name = "id2", type = "string", length = "255", unique = "true")
*/
protected $id2;
When I try saving an Item I get this error:
Notice: Undefined index: id2 in vendor/doctrine/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php line 511
Sure enough, if I change id2 to id in the JoinColumn annotation, everything works fine, but I need the entities to be connected through id2. Is this possible?
Edit
What I want to achieve is impossible according to the official Doctrine 2 docs.
It is not possible to use join columns pointing to non-primary keys.
Doctrine will think these are the primary keys and create lazy-loading
proxies with the data, which can lead to unexpected results. Doctrine
can for performance reasons not validate the correctness of this
settings at runtime but only through the Validate Schema command.
source: https://www.doctrine-project.org/projects/doctrine-orm/en/2.6/reference/limitations-and-known-issues.html#join-columns-with-non-primary-keys
I think Doctrine wants these to be primary keys, from the docs:
name: Column name that holds the foreign key identifier for this relation.
Another thing that jumps out at me from your code sample is category.id2 being type string, I would at least expect it to be an integer, but it may also need to be for #JoinColumn to work properly.
You may be able to get away with just #Index on category.id2 and leave it as a string though; worth a shot anyway.
Just to report. I was able to join non-PKs in Many2One (undirectional) relation, BUT my object can't be loaded the normal way. It must be loaded with DQL like:
SELECT d,u FROM DEntity d
JOIN d.userAccount u
this way I stopped getting error: Missing value for primary key id on ....

Categories