Как стать автором
Обновить

Комментарии 39

Добавляем к таблице
articles
поле
type
и задача решается в рамка БД, а не на уровне ORM. И вообще говоря, на php есть только одна ORM это Doctrine, все остальное так или иначе Mapper'ы
Первая строчка из документации Laravel:
The Eloquent ORM included with Laravel provides a beautiful, simple ActiveRecord implementation for working with your database.

И на вики есть такой линк.
Точно не понятно, как вы отделяете Doctrine от остальных реализаций? И кстати, а в доктрине есть что-то по полиморфным связям?
Исходя из определения ORM (object-relational mapping) можно провести следующий тест, является ли библиотека ORM:
  1. Создаем связную сущность:
    $a = new A();
    $a->b=new B();
    

  2. Сохраняем только $a
    orm.save($a);
    

  3. Затем извлекаем дважды одну и ту же сущность:
    $a1=orm.findById(1);
    $a2=orm.findById(1);
    


В итоге, следующее выражение для ORM должно выполнено успешно:
AssertTrue($a1 === $a2 && $a1->b===$a2->b)

Это тест на консистентность любой ORM и то как она разрешает связи. Дальнейшее обсуждение считаю бессмысленным.
Ну Propel2 под это определение подходит, вроде как.
Как бы да, но Propel2 еще в альфе. И я правильно понимаю что там будет уже не Active Record а data mapper?
Релиза не дождешься. Мы его на реальных проектах со второй альфы используем, особых багов и проблем с обновлением до 3 и 4 альфы не было.

>> И я правильно понимаю что там будет уже не Active Record а data mapper?

Последняя версия сейчас alpha-4 и это чистый Active Record. Помнится, я читал ветку на Гитхабе с грандиозным срачем на тему data mapper-а, и там предлагался некий гибридный вариант ORM. Честно говоря, не знаю, какой статус у того предложения сейчас, и ветку эту не могу найти сейчас.
Гибридный вариант мне совсем не нравится, но меня интригуют заявления о том что тамошняя реализация UoW работает в 4 раза быстрее чем вариант доктрины… правда я думаю что когда функционал закончат разницы почти не будет. А если так то лучше остаться на доктрине, в которой намного больше возможностей.
propelorm.org/blog/2015/06/27/propel2-current-state-and-future.html

Накопал больше информации. Исходя из этого поста, курс на data-mapper вроде как свершившийся факт. Там же написано, что Active Record остается опциональным вариантом, т.е. в схеме для какого-то entity можно включить ActiveRecord, и генератор сгенерирует persistance-методы внутри модели.
Стоит уточнить, что у полиморфных сущностей так же имеются свои минусы, так же как и у полиморфных связей. Все сильно зависит от ситуации.
Для примера мы можем захотеть комментировать нечто совершенно отличающееся от новостей и статей. Сущность «Товар» уж наверняка не стоит пихать в таблицу articles. Хотя технически ничто этого сделать не помешает.
И начнутся проблемы, когда к комментариям к новости нудно будет добавить парочку обязательных полей, которых не должно быть в комментах к статье.
А как бы вы реализовали схему, имея только условия из статьи и не зная изначально о доп. полях?
Скорее всего, так же бы и реализовал. Комментарий был не из разряда «полиморфные связи — отстой», а как обычное наблюдение из жизни.
НЛО прилетело и опубликовало эту надпись здесь
Для поддержки foreign_key вполне можно еще немного изменить структуру БД: к таблицам news и articles добавляется поле entity_id, которое автоматически ( триггером или в коде ) заполняется при создании записи и является внешним ключем с полем id в таблице entities, к которому уже и коннектятся все эти таблицы с комментариями, лайками, отметками избранного, тэгами, разделами и прочее.
А можно пойти еще дальше и сделать единую нумерацию айдишников для всех сущностей в базе.

При создании чего угодно сначала создается entity с полями id и type, после чего id переносится в поле id нужной сущности. И все связи идут по общему айдишнику — становится не важен тип записи, которая линкуется, поскольку id однозначно определяет тип.
В разных таблицах? А constraint?
Constraint — на единую таблицу entities.
Без дополнительного запроса к этой таблице тип автоматически известен не станет, здесь никакой магии.
Магия появляется с адресацией — нет необходимости указывать тип ресурса в дополнение к id.
И у данной схемы также есть свои минусы: таблица entities станет узким местом в системе, с одной стороны требуя для своего поддержания дополнительные индексы по большому количеству записей, с другой — обслуживание этой таблицы (в системах требующих такого обслуживания) межет стать проблемой. Так же как мы захотели сделать единую систему комментариев для статей, следом за статьями нам рано или поздно захочется сделать сущностями и сами комментарии, для какой-нибудь системы оценок и рассчёта рейтинга. Хотя этот вариант мне кажется лучшим для небольших систем, но к нему стоит также подходить осторожно.
Именно так. В максимальном своем развитии эта идея ведет к БД на графах со всеми их плюсами и минусами. Из плюсов — легкость реализации сложной логики взаимодействия. Из минусов — отсутствие возможности масштабирования. С другой стороны не так много систем существует, которым реально надо больше одного сервера.
Неужели связь через pivot table будет работать дольше полиморфной связи?
В Doctrine Extesions есть поведение для переводов сущностей (Translatable), использующее подобный подход. Но там можно переключить конкретные сущности на персональные таблицы для переводов.
А для тегов в symfony я использовал бандл FPNTagBundle, использующий подход, описанный в этой статье, как раз Many-to-Many. Правда, в сонате они не поддерживаются, и там в классе админки нужно их вручную считывать из базы. Я писал об использовании тегов в Symfony и Sonata в своей статье.
Поскольку конкретная РСУБД тут не фигурирует, то рекомендую для приведенной задачи просто используйте Postgresql с его наследованием таблиц.
Более того, для ряда задач отпадает необходимость вообще в задействовании связей — Postgres предлагает ряд приятных «дополнительных» типов вроде Array, HStore, JSON(B).
Ну так уж можно залезть в то, что на Postgres можно намутить NoSQL
Ну jsonb в 9.5 прекрасно этому способствует
в 9.4
Это я только к тому, что тема данной статьи все же немного другая.
hstore тоже для этого годится — я еще лет 10 назад NoSQL на постгресе с ним мутил. Очень приятные впечатления.
У «Полиморфных связей» есть еще одна очевидная проблема — разрастание таблицы и сложность горизонтального масштабирования.
В django тоже есть, называется Content Types
За пределами Django в Python очень хорошо с ними ещё умеет работать SqlAlchemy, рекомендую! Используем давно, постоянно и радуемся. :)
Тогда уж к ряду следовало бы упомянуть про «SQL Antipatterns: Avoiding the Pitfalls of Database Programming» от Bill Karwin.
Там есть раздел, посвященный полиморфным ассоциациям.
Кстати на stackoverflow он часто отвечал на вопросы по этому поводу, разбирая всё на примерах.
Вот даже презентация есть: www.slideshare.net/billkarwin/sql-antipatterns-strike-back

Самый главный минус полиморфных связей, как уже сказали — отсутствие контроля целостности данных. Его придется реализовывать программно.
Всё-таки этот тип связей не совсем вписывается в концепцию реляционных баз данных.
Ну и когда будете проектировать БД, нужно будет подумать над тем, захотите ли Вы в дальнейшем делать общую выборку по таблице с полиморфными связями.
И если вдруг захотите, а связанных таблиц у Вас будет больше, например, 5-ти, подумайте, как будет выглядеть сам запрос и сколько JOIN'ов нужно будет сделать, а самое главное — как это скажется на производительности.
В разрабатываемом мною движке есть одна любопытная таблица. Назовём её условно fields. Ниже SQL-код её создания:

CREATE TABLE IF NOT EXISTS `[+prefix+]fields` (
`id` INT NOT NULL AUTO_INCREMENT,
`module` TINYINT NOT NULL DEFAULT '0' COMMENT 'See below',
`type` INT NOT NULL DEFAULT '0' COMMENT 'See below',
`handlers` INT NOT NULL DEFAULT '0' COMMENT 'See below',
`input` INT NOT NULL DEFAULT '0',
`alias` VARCHAR(64),
`caption` TINYTEXT NULL,
`description` TINYTEXT NULL,
`category` INT NULL,
`elements` TEXT NULL,
`default` TEXT NULL,
`flags` INT NOT NULL DEFAULT '0',
`rank` INT NOT NULL DEFAULT '0',
PRIMARY KEY (`id`),

UNIQUE KEY `alias`(`alias`),

FOREIGN KEY (`category`) REFERENCES `[+prefix+]categories`(`id`) ON UPDATE SET NULL ON DELETE SET NULL
) ENGINE=InnoDB COMMENT='Data fields' INSERT_METHOD=FIRST;

Это таблица полей дополнительных данных. Поля могут быть полями т.н. филдсетов для ресурсов, настройками самого движка, отдельных модулей, дополнительными полями пользователей или даже доп.полями данных модулей. В любом случае таблицы со значениями всегда ссылаются на поле id этой таблицы. Поле module означает тип дополнительного поля (поле филдсета, настройка и т.п.). Собственно, поле module по большому счёту обрабатывается уже самим PHP. Это, так сказать, в качестве хорошего примера.

Теперь к сабжу. Почему бы не реализовать функционал проще? Например, таблицы articles и news — это на самом деле одна таблица, а комментарии в любом случае будут ссылаться на эту таблицу. Как говорится, зачем плодить лишние сущности? В принципе у описанного мною подхода есть, конечно, существенный недостаток. Скорость чтения записей из одной таблицы будет несколько ниже, особенно на большом количестве данных. Однако, во-первых, будем реалистами. Каковы могут быть объёмы данных в среднестатистических проектах. Во-вторых, архитектура источника данных на множестве таблиц усложняет расширение проекта в дальнейшем. И опять-таки же, если уж так хочется разных таблиц, почему бы не сделать таблицу комментариев к каждому типу отдельно?
Уже высказались по целостности БД. Свои 5 копеек — в 2002 изобрел такой же велосипед в постгресе для интернет-магазина (хранение картинок для категорий, товаров и общесистемных), написал триггеры, которые проверяли и выполняли ограничения (всякие там каскадные удаления etc). За долгие годы эксплуатации движка пожалел об этом неоднократно. Минусы сильно перевешивают плюсы. Даже сквозная нумерация записей в разных таблицах не спасает (опять же, в постгресе это делается на раз). Наследование тогда использовать побоялся (были мысли о кроссплатформенности по БД) — как чисто постгресную фишку.
Сейчас — однозначно только PostgreSQL и только наследование таблиц для полиморфизма сущностей.
А я когда-то изобрел это, только не знал, что название сие полимерная связь :)
Полиморфная)
К примеру: если нужно сделать связь комментариев (comments) с товарами (products), новостями (news), статьями (articles), то лучше бы просто делать связи в отдельных промежуточных таблицах — products_comments (product_id, comment_id), news_comments (news_id, comment_id) и articles_comments (article_id, comment_id).
Тогда можно навесить ограничения (constraints) и намного проще для понимания.
Я так же за промежуточные таблицы, но по другим соображениям. А что если мне нужно переименовать тип(например с news на articles)?
На мой взгляд в промежуточных таблицах нужно хранить id типа. Да, мы получим дополнительную таблицу types с id и name, но все же
Зарегистрируйтесь на Хабре , чтобы оставить комментарий

Публикации

Истории