I'm using Doctrine2 inside Symfony and I have the following setup:
An Item class:
/**
* Class Item
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="OneShortly\CommonBundle\Entity\ItemRepository")
*/
class Item
{
/**
* #ORM\ManyToOne(targetEntity="Category")
* #ORM\JoinColumn(name="primaryCategory", referencedColumnName="foreignId")
*/
private $primaryCategory;
}
And a Category class:
/**
* Category
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="OneShortly\CommonBundle\Entity\CategoryRepository")
*/
class Category
{
/**
* #var integer
*
* #ORM\Column(name="foreignId", type="integer", unique=true)
*/
private $foreignId;
}
Now when I do this:
$item = new Item();
$item->setPrimaryCategory($category);
$this->em->persist($item);
$this->em->flush();
I get this error:
[Symfony\Component\Debug\Exception\ContextErrorException] Notice:
Undefined index: foreignId in
home/www/project/vendor/doctrine/orm/lib/Doctrine/ORM/Persisters/BasicEntityPersister.php
line 692
Now, I've been looking at this from all angles and still cannot see what is wrong with this code. Can you help?
After some more digging I figured out myself by using doctrine:schema:validate:
[Mapping] FAIL - The entity-class
'Acme\CommonBundle\Entity\Item' mapping is invalid:
* The referenced column name 'foreignId' has to be a primary key column on the target entity class
'Acme\CommonBundle\Entity\Category'.
[Database] FAIL - The database schema is not in sync with the current
mapping file.
So, I changed the foreign key from foreignId to id (which happens to be the primary key) and it works. I could, of course, just use foreignId as a primary key, but I realized actually I don't need that.
Take a look at http://symfony.com/doc/current/book/doctrine.html#relationship-mapping-metadata.
You should rather have:
/**
* Class Item
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="OneShortly\CommonBundle\Entity\ItemRepository")
*/
class Item
{
/**
* #ORM\ManyToOne(targetEntity="Category", inversedBy="items")
* #ORM\JoinColumn(name="category_id", referencedColumnName="id")
*/
private $primaryCategory;
}
and:
/**
* Category
*
* #ORM\Table()
* #ORM\Entity(repositoryClass="OneShortly\CommonBundle\Entity\CategoryRepository")
*/
class Category
{
/**
* #ORM\OneToMany(targetEntity="Item", mappedBy="primaryCategory")
*/
private $items;
}
Forget ID in ORM.
Related
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;
...
}
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;
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.
I'm trying to create a UniqueEntity with 2 fields (both are ManyToOne fields).
The code is as follow:
/*
* #ORM\Table()
* #ORM\Entity
* #ORM\HasLifecycleCallbacks()
* #UniqueEntity(fields={"user", "connect"})
*/
class UserConnect
{
/**
* #var integer $id
*
* #ORM\Column(name="id", type="integer")
* #ORM\Id
* #ORM\GeneratedValue(strategy="AUTO")
*/
private $id;
/**
* #var boolean $isLeader
*
* #ORM\Column(name="isLeader", type="boolean")
*/
private $isLeader;
/**
* #var date $joinedDate
*
* #ORM\Column(name="joinedDate", type="date")
*/
private $joinedDate;
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="userConnects")
*
*/
private $user;
/**
* #ORM\ManyToOne(targetEntity="Connect", inversedBy="userConnects")
*
*/
private $connect;
The goal is to ensure that I've got only one Entity that link a USER with a CONNECT.
Should I write something else in my #UniqueEntity declaration?
I understand you want to get an error only when both user and connect fields for one record are duplicated in other record in the database.
The #UniqueEntity annotation is rightly declared for your purpose (multiple column index) but only will be triggered in the form validation and doesn't affects the DDBB schema.
If you want to add the same check at database level you should use the #UniqueConstraint annotation in the Table() declaration and give a name to the new index. Something like:
/*
* #ORM\Table(uniqueConstraints={#ORM\UniqueConstraint(name="IDX_USER_CONNECT", columns={"user_id", "connect_id"})})
* #ORM\Entity
* #ORM\HasLifecycleCallbacks()
* #UniqueEntity(fields={"user", "connect"})
*/
class UserConnect
{
In the other hand, if you declare #ORM\Column(unique=true) in each attribute you will get a very different behavior, it won't be a multiple column index but you will have two independent unique columns, if you enter twice the same user_id you will get an error independently of the connect_id value, and the same will happens if you enter twice the same connect_id value.
This works:
/**
* State
*
* #ORM\Table(
* name="general.states",
* uniqueConstraints={
* #ORM\UniqueConstraint(name="states_country_name_code_key", columns={"idcountry", "name","code"}),
* })
* #ORM\Entity(repositoryClass="Fluency\Bundle\GeneralBundle\Entity\Repository\StateRepository")
*/
class State
{.......
Taken from an entity on my system. This way affects Database schema. See where i put #\ORM\UniqueConstraint annotation. Sorry #estopero... next time i must read first the other answers.
you should add the unique declaration in your attributes annotations too.
/**
* #ORM\ManyToOne(targetEntity="User", inversedBy="userConnects")
* #ORM\Column(unique=true)
*/
private $user;
/**
* #ORM\ManyToOne(targetEntity="Connect", inversedBy="userConnects")
* #ORM\Column(unique=true)
*/
private $connect;
See this symfony doc and this StackOverflow answer.
I have a reference table album_content which has: album_id, content_id, and sort_key. I set it up as an entity with #ManyToOne relations:
/**
* #ManyToOne(targetEntity="Album")
* #JoinColumns({
* #JoinColumn(name="album_id", referencedColumnName="id")
* })
*/
private $albumId;
/**
* #ManyToOne(targetEntity="Content")
* #JoinColumns({
* #JoinColumn(name="content_id", referencedColumnName="id")
* })
*/
private $contentId;
/**
* #Column(name="sort_key", type="integer", nullable=false)
*/
private $sortKey;
Right now Doctrine is complaining No identifier/primary key specified. What's the correct annotation to reference these without adding an extra ID column?
First, you probably shouldn't be naming things $contentId or $albumId, but instead just call them $content and $album.
That said, the quick solution is to add #Id annotations to both of your associations.
The manual goes into further detail about using composite keys in Doctrine 2.