Доктрина: связь ManyToX между сущностями с составными ключами

У меня есть три объекта, назовем их 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.

Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Symfony Station Communiqué - 7 июля 2023 г
Symfony Station Communiqué - 7 июля 2023 г
Это коммюнике первоначально появилось на Symfony Station .
Оживление вашего приложения Laravel: Понимание режима обслуживания
Оживление вашего приложения Laravel: Понимание режима обслуживания
Здравствуйте, разработчики! В сегодняшней статье мы рассмотрим важный аспект управления приложениями, который часто упускается из виду в суете...
Установка и настройка Nginx и PHP на Ubuntu-сервере
Установка и настройка Nginx и PHP на Ubuntu-сервере
В этот раз я сделаю руководство по установке и настройке nginx и php на Ubuntu OS.
Коллекции в Laravel более простым способом
Коллекции в Laravel более простым способом
Привет, читатели, сегодня мы узнаем о коллекциях. В Laravel коллекции - это способ манипулировать массивами и играть с массивами данных. Благодаря...
Как установить PHP на Mac
Как установить PHP на Mac
PHP - это популярный язык программирования, который используется для разработки веб-приложений. Если вы используете Mac и хотите разрабатывать...
2
0
284
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Вы не можете использовать составной ключ 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;
}

Спасибо за ваши предложения. Вы уверены, что PK нельзя использовать для отношений? Согласно документации MySQL, это должно быть возможно: dev.mysql.com/doc/refman/5.5/en/create-table-foreign-keys.ht‌ мл.

lxg 20.03.2018 15:12

Не уверен насчет MySQL, но точно, Symfony не позволяет использовать составной первичный ключ для отношений. Я сам столкнулся с этой проблемой.

Preciel 20.03.2018 15:33

Вы должны определить соединенные столбцы в аннотации, чтобы он работал:

Для отношения 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, где тег имеет автоматически увеличивающийся идентификатор, а категория - это сочетание сайта и внешнего идентификатора?

lxg 21.03.2018 09:30

Я отредактировал отношение ManyToOne несколько минут назад, потому что оно было неверным, вы можете попробовать еще раз? Какая у вас ошибка?

Jannes Botis 21.03.2018 09:37

Я уже пробовал это. Мои настоящие объекты названы немного иначе, но это ошибка 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. Я не уверен, правильно ли я «перевел» названия столбцов в вашем примере.

lxg 21.03.2018 09:42

Вы правильно перевели, но, может быть, в таблице тегов уже есть столбцы category_id или category_site_id? Вы можете это проверить?

Jannes Botis 21.03.2018 10:17

@lxg было бы полезно, если бы вы могли отредактировать свой вопрос и добавить отношение ManyToOne, которое у вас есть, а также OneToMany, если он определен как двунаправленный в категории?

Jannes Botis 21.03.2018 11:50

Спасибо, я ценю твои старания. Я добавил пример (однонаправленного) отношения ManyToOne. Я пробовал разные варианты предложенного вами решения, но все они дают мне либо Foreign key constraint is incorrectly formed, либо Key reference and table reference don't match. Я думаю, что могу отказаться от простого автоматического увеличения идентификатора и просто сохранить составной ключ в качестве дополнительного индекса.

lxg 21.03.2018 19:49

@lxg Попробуйте изменить порядок в вашем определении @ORM \ JoinColumns, поместив сначала "category_site_id". Можете ли вы добавить определение, которое вы использовали в своем примере?

Jannes Botis 21.03.2018 20:53

@lxg, на самом деле проблема заключается в порядке, Категория (id) не имеет индекса, а Категория (site_id) имеет. Измените порядок определений "@ORM \ JoinColumn", и он будет работать. stackoverflow.com/questions/27150911/…

Jannes Botis 21.03.2018 21:05

Я тоже пробовал ... тоже не сработало. Может быть, с моими сущностями есть какая-то другая проблема. Извини, чувак, я отказываюсь от этого. Я реализовал решение с автоинкрементным идентификатором и составным ключом в качестве дополнительного индекса, которое работает безупречно. Я даю вам +1 за ваши усилия, но, к сожалению, мы не смогли решить проблему. Еще раз спасибо.

lxg 21.03.2018 21:41

Я решил дать ему последнюю попытку, и - хотите верьте, хотите нет - я смог решить эту проблему: видимо, порядок свойств $id и $shop по какой-то причине важен! Я опубликовал ответ с окончательным решением.

lxg 22.03.2018 08:33

Я никогда не мог себе этого представить! Подумайте о создании проблемы в репозитории doctrine, самое меньшее, что они могут сделать, - это предоставить лучшую документацию по этому поводу.

Jannes Botis 22.03.2018 09:07
Ответ принят как подходящий

На основе Ответ Янниса я наконец смог найти решение для этого. Решение состоит из двух частей:

Первая часть немного странная: очевидно, что 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;

Другие вопросы по теме