У меня есть три объекта, назовем их Site, Category и Tag. В этом сценарии Category и Tag имеют составной идентификатор, сгенерированный из объекта Site, и внешний идентификатор, который не является уникальным (site и id вместе уникальны). Category имеет отношение "многие ко многим" на Tag. (Хотя моя проблема также воспроизводится с отношениями ManyToOne.)
Сущность Site:
/**
* @ORM\Entity
*/
class Site
{
/**
* @ORM\Id
* @ORM\GeneratedValue
* @ORM\Column(type = "integer")
*/
private $id;
/**
* @ORM\Column(type = "string")
*/
private $name;
}
Сущность Category:
/**
* @ORM\Entity
*/
class Category extends AbstractSiteAwareEntity
{
/**
* @ORM\Column(type = "string")
*/
private $name;
/**
* @ORM\ManyToMany(targetEntity = "Tag")
*/
private $tags;
}
Сущность Tag:
/**
* @ORM\Entity
*/
class Tag extends AbstractSiteAwareEntity
{
/**
* @ORM\Column(type = "string")
*/
private $name;
}
Последние две сущности наследуются от класса AbstractSiteAwareEntity, который определяет составной индекс:
abstract class AbstractSiteAwareEntity
{
/**
* @ORM\Id
* @ORM\ManyToOne(targetEntity = "Site")
*/
public $site;
/**
* @ORM\Id
* @ORM\Column(type = "integer")
*/
public $id;
}
В качестве примера отношения ManyToOne представим себе объект Post, который имеет идентификатор автоинкремента и ссылку на 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;
}
При обновлении схемы с помощью bin/console doctrine:schema:update --dump-sql --force --complete я получаю следующее исключение:
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")
Я не уверен, что может быть не так ... есть ли ошибка в определениях моих объектов? Или это ошибка Доктрины?
ПРИМЕЧАНИЕ: я использую doctrine / dbal 2.6.3 на Symfony 3.4.






Вы не можете использовать составной ключ PrK для отношений. Вместо этого определите идентификатор и составной ключ UNI (или просто ключ UNI в этом случае)
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;
}
Не уверен насчет MySQL, но точно, Symfony не позволяет использовать составной первичный ключ для отношений. Я сам столкнулся с этой проблемой.
Вы должны определить соединенные столбцы в аннотации, чтобы он работал:
Для отношения ManyToOne:
/**
* @ORM\ManyToOne(targetEntity = "Tag")
* @ORM\JoinColumns({
* @ORM\JoinColumn(name = "tag_site_id", referencedColumnName = "site_id"),
* @ORM\JoinColumn(name = "tag_id", referencedColumnName = "id")
* })
**/
protected $tag;
Для отношения ManyToMany:
/**
* @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;
Как это работает: 2 столбца создаются как ИНОСТРАННЫЙ КЛЮЧ, что РЕКОМЕНДАЦИИ - составной внешний ключ.
Примечание. В настройке «@ORM \ JoinColumn»:
Параметр «referencedColumnName» - это имя столбца идентификатора в связанной таблице, поэтому он уже должен существовать.
«name» - это имя столбца, в котором хранится внешний ключ и которое будет создано, и в случае ManyToOne оно не должно конфликтовать ни с одним из столбцов объекта.
Порядок ваших определений joinColumns важен. Составной внешний ключ должен начинаться со столбца, имеющего индекс в указанной таблице. В этом случае "@ORM \ JoinColumn" на "site_id" должно быть первый. Справка
Ваш код для ManyToMany работает нормально, просто у меня проблемы с примером ManyToOne. Не могли бы вы изменить его, чтобы показать, как было бы, если бы тег был связан с категорией с помощью ManyToOne, где тег имеет автоматически увеличивающийся идентификатор, а категория - это сочетание сайта и внешнего идентификатора?
Я отредактировал отношение ManyToOne несколько минут назад, потому что оно было неверным, вы можете попробовать еще раз? Какая у вас ошибка?
Я уже пробовал это. Мои настоящие объекты названы немного иначе, но это ошибка Foreign key constraint is incorrectly formed, запрос, сгенерированный D2, - ALTER TABLE Tag ADD CONSTRAINT FK_… FOREIGN KEY (category_id, category_site_id) REFERENCES Category (id, site_id) ON DELETE CASCADE. Я не уверен, правильно ли я «перевел» названия столбцов в вашем примере.
Вы правильно перевели, но, может быть, в таблице тегов уже есть столбцы category_id или category_site_id? Вы можете это проверить?
@lxg было бы полезно, если бы вы могли отредактировать свой вопрос и добавить отношение ManyToOne, которое у вас есть, а также OneToMany, если он определен как двунаправленный в категории?
Спасибо, я ценю твои старания. Я добавил пример (однонаправленного) отношения ManyToOne. Я пробовал разные варианты предложенного вами решения, но все они дают мне либо Foreign key constraint is incorrectly formed, либо Key reference and table reference don't match. Я думаю, что могу отказаться от простого автоматического увеличения идентификатора и просто сохранить составной ключ в качестве дополнительного индекса.
@lxg Попробуйте изменить порядок в вашем определении @ORM \ JoinColumns, поместив сначала "category_site_id". Можете ли вы добавить определение, которое вы использовали в своем примере?
@lxg, на самом деле проблема заключается в порядке, Категория (id) не имеет индекса, а Категория (site_id) имеет. Измените порядок определений "@ORM \ JoinColumn", и он будет работать. stackoverflow.com/questions/27150911/…
Я тоже пробовал ... тоже не сработало. Может быть, с моими сущностями есть какая-то другая проблема. Извини, чувак, я отказываюсь от этого. Я реализовал решение с автоинкрементным идентификатором и составным ключом в качестве дополнительного индекса, которое работает безупречно. Я даю вам +1 за ваши усилия, но, к сожалению, мы не смогли решить проблему. Еще раз спасибо.
Я решил дать ему последнюю попытку, и - хотите верьте, хотите нет - я смог решить эту проблему: видимо, порядок свойств $id и $shop по какой-то причине важен! Я опубликовал ответ с окончательным решением.
Я никогда не мог себе этого представить! Подумайте о создании проблемы в репозитории doctrine, самое меньшее, что они могут сделать, - это предоставить лучшую документацию по этому поводу.
На основе Ответ Янниса я наконец смог найти решение для этого. Решение состоит из двух частей:
Первая часть немного странная: очевидно, что Doctrine нуждается в $id перед ссылкой на $site в составном первичном ключе, определенном в AbstractSiteEntity. Звучит странно, но это определенно правда, потому что я несколько раз пробовал менять их местами туда и обратно, и почему-то работает только этот порядок.
abstract class AbstractSiteAwareEntity
{
// $id must come before $site
/**
* @ORM\Id
* @ORM\Column(type = "integer")
*/
public $id;
/**
* @ORM\Id
* @ORM\ManyToOne(targetEntity = "Site")
*/
public $site;
}
Для отношений ManyToMany индексы в JoinTable должны быть объявлены явно, поскольку Doctrine не смогла создать составной внешний ключ самостоятельно. Для этого я использовал предложение Янниса с некоторыми изменениями (названия столбцов на самом деле не обязательны):
/**
* @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;
Спасибо за ваши предложения. Вы уверены, что PK нельзя использовать для отношений? Согласно документации MySQL, это должно быть возможно: dev.mysql.com/doc/refman/5.5/en/create-table-foreign-keys.ht мл.