I have three entites, let's call them Site
, Category
and Tag
. In this scenario, Category
and Tag
have a composite ID generated from the Site
entity and an external ID which is not unique ( site
and id
together are unique). Category
has a many-to-many relation on Tag
. (Although my issue is also reproducible with ManyToOne relations.)
The Site
entity:
/**
* @ORM\Entity
*/
class Site
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
private $id;
/**
* @ORM\Column(type="string")
*/
private $name;
}
The Category
entity:
/**
* @ORM\Entity
*/
class Category extends AbstractSiteAwareEntity
{
/**
* @ORM\Column(type="string")
*/
private $name;
/**
* @ORM\ManyToMany(targetEntity="Tag")
*/
private $tags;
}
The Tag
entity:
/**
* @ORM\Entity
*/
class Tag extends AbstractSiteAwareEntity
{
/**
* @ORM\Column(type="string")
*/
private $name;
}
The last two entities inherit from the AbstractSiteAwareEntity
class, which defines the composite index:
abstract class AbstractSiteAwareEntity
{
/**
* @ORM\Id
* @ORM\ManyToOne(targetEntity="Site")
*/
public $site;
/**
* @ORM\Id
* @ORM\Column(type="integer")
*/
public $id;
}
As an example of a ManyToOne relation, let's imagine a Post
entity, which has an auto-increment ID and a reference to Category
:
/**
* @ORM\Entity
*/
class Post
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type="integer")
*/
public $id;
/**
* @ORM\ManyToOne(targetEntity="Category")
*/
private $category;
/**
* @ORM\Column(type="string")
*/
private $title;
/**
* @ORM\Column(type="string")
*/
private $content;
}
When updating the schema with bin/console doctrine:schema:update --dump-sql --force --complete
, I get the following exception:
An exception occurred while executing 'ALTER TABLE category_tag ADD CONSTRAINT FK_D80F351812469DE2 FOREIGN KEY (category_id) REFERENCES Category (id) ON DELETE CASCADE':
SQLSTATE[HY000]: General error: 1005 Can't create table
xxxxxxx
.#sql-3cf_14b6
(errno: 150 "Foreign key constraint is incorrectly formed")
I'm not sure what might be wrong … is there an error in my entity definitions? Or is this a Doctrine bug?
NOTE: I'm using doctrine/dbal 2.6.3 on Symfony 3.4.
You have to define the joined columns in the annotation to make it work:
For a ManyToOne relation:
/**
* @ORM\ManyToOne(targetEntity="Tag")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name="tag_site_id", referencedColumnName="site_id"),
* @ORM\JoinColumn(name="tag_id", referencedColumnName="id")
* })
**/
protected $tag;
For a ManyToMany relation:
/**
* @ORM\ManyToMany(targetEntity="Tag")
* @ORM\JoinTable(name="category_tags",
* joinColumns={
* @ORM\JoinColumn(name="category_site_id", referencedColumnName="site_id"),
* @ORM\JoinColumn(name="category_id", referencedColumnName="id")
* },
* inverseJoinColumns={
* @ORM\JoinColumn(name="tag_site_id", referencedColumnName="site_id"),
* @ORM\JoinColumn(name="tag_id", referencedColumnName="id")
* }
* )
**/
protected $tags;
How it works: 2 columns are created as a FOREIGN KEY that REFERENCES the composite foreign key.
Note: In "@ORM\\JoinColumn" setting:
"referencedColumnName" setting is an id column name on the related table, so this should already exist.
"name" is the column name that stores the foreign key and to be created and in case of ManyToOne, it should not conflict with any of the columns of the entity.
The order of your joinColumns definitions is important. The composite foreign key should start with a column that has an index in the referenced table. In this case, "@ORM\\JoinColumn" to "site_id" should be first . Reference
Based on Jannis' answer , I was finally able to find a solution for this. The solution consists of two parts:
The first part is bit weird: Apparently Doctrine needs the $id
before the $site
reference in the composite primary key defined in AbstractSiteEntity
. This sounds strange, but it's definitely true, because I tried swapping them several times back and forth, and only this order works for some reason.
abstract class AbstractSiteAwareEntity
{
// $id must come before $site
/**
* @ORM\Id
* @ORM\Column(type="integer")
*/
public $id;
/**
* @ORM\Id
* @ORM\ManyToOne(targetEntity="Site")
*/
public $site;
}
For ManyToMany relations, the indices in the JoinTable must be declared explicitely, because Doctrine failed to create a composite foreign key by itself. I used Jannis' proposal for this with a few modifications (column names aren't actually neccessary):
/**
* @ORM\ManyToMany(targetEntity="Tag")
* @ORM\JoinTable(
* joinColumns={
* @ORM\JoinColumn(referencedColumnName="id"),
* @ORM\JoinColumn(referencedColumnName="site_id")
* },
* inverseJoinColumns={
* @ORM\JoinColumn(referencedColumnName="id"),
* @ORM\JoinColumn(referencedColumnName="site_id")
* }
* )
*/
private $tags;
You can't use composite PrK key for relations.
Instead, define an ID, and a composite UNI key (or just UNI key in this case)
Composite UNI key example
/**
* SiteAware
*
* @ORM\Table(name="site_aware",
* uniqueConstraints={@ORM\UniqueConstraint(columns={"site_id", "random_entity_id"})})
* @UniqueEntity(fields={"site", "randomEntity"}, message="This pair combination is already listed")
*/
abstract class AbstractSiteAwareEntity
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
*/
public $id;
/**
* @ORM\ManyToOne(targetEntity="Site")
*/
public $site;
/**
* @ORM\ManyToOne(targetEntity="RandomEntity")
*/
public $randomEntity;
}
Simple unique key
abstract class AbstractSiteAwareEntity
{
/**
* @ORM\Id
* @ORM\Column(type="integer")
*/
public $id;
/**
* @ORM\ManyToOne(targetEntity="Site")
* @ORM\JoinColumn(unique=true)
*/
public $site;
}
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.