Yii. Связи таблиц в стиле Active Record
версия для печатиОкружение: фреймворк Yii 1.x + два головастика над одним сайтом. Третий день в темпе допиливаем суперский проект, который уже давно пора было сдать. И тут я застрял на ровном месте, прописал связь двух таблиц, а она не работает.
Данный опус написан для таких же новичков Yii, как я, но еще не наступивших на грабли описания relations() между AR-классами моделей таблиц. Гуру, что уже сенбернара съели на этом фреймворке, могут даже не смотреть под кат ;)
Почти сразу к сути. В классической реляционной СУБД есть три типа связей таблиц: один-к-одному, один-ко-многим и многие-ко-многим. Эти же связи определяют подчиненность таблиц. Yii использует Active Record (AR), поддерживая четыре своих типа связей + еще одну агрегативную. И это не предел, в Ruby их вообще шесть :)
Ниже цитата из русского манула по Yii.
Внимание! Это описание может и вас завести в тот же тупик, где мы побывали.
BELONGS_TO: если связь между А и В один-ко-многим, значит В принадлежит А (например, Post принадлежит User);
!!Это ошибка. Правильно: "А принадлежит B, связь между B и А один-ко-многим".
HAS_MANY: если связь между таблицами А и В один-ко-многим, значит у А есть много В (например, у User есть много Post);
HAS_ONE: это частный случай HAS_MANY, где А может иметь максимум одно В (например, у User есть только один Profile);
!!Тоже не совсем точно. Это как раз "один-к-одному", т.е. и B может иметь максимум одно А.
MANY_MANY: эта связь соответствует типу связи многие-ко-многим в БД. Поскольку многие СУБД не поддерживают непосредственно этот тип связи, требуется ассоциативная таблица для преобразования связи многие-ко-многим в связи один-ко-многим. В терминологии AR связь MANY_MANY можно описать как комбинацию BELONGS_TO и HAS_MANY. Например, Post принадлежит многим Category, а у Category есть много Post.
Аналогичный текст изложен на оф.сайте:
There are four types of relations that may exist between two active record objects:
BELONGS_TO: e.g. a member belongs to a team;
HAS_ONE: e.g. a member has at most one profile;
HAS_MANY: e.g. a team has many members;
MANY_MANY: e.g. a member has many skills and a skill belongs to a member.
Прочитав их оба по нескольку раз, я не видел истины.
Напоминаю синтаксис описания связи:
'VarName' => array('RelationType', 'ClassName', 'ForeignKey', …доп.параметры)
VarName — имя связи, RelationType - один из четырёх типов связей, ClassName - имя AR-класса, представляющего связываемую таблицу, ForeignKey - внешние ключи, используемые для установления связи.
Теперь покажу на примерах правильное понимание связей, правильность прочтения и вообще все то, как это действительно реализовано в Yii/AR. Куски кода взяты из метода relations(), перекрытого в наследниках класса CActiveRecord.
Модель shop.php: // Магазин принадлежит одному юзеру. Shop - подчиненная таблица в этой связи 'owner' => array(self::BELONGS_TO, 'User', 'userId') Из другой модели, product.php: //Продукт принадлежит одному магазину. Product - подчиненная таблица в связи. 'inShop' => array(self::BELONGS_TO, 'Shop', 'shopId') //Но! Продукт имеет много картинок. Product - главная таблица в связи. 'pics' => array(self::HAS_MANY, 'Image', 'productId') И мои "грабли" на примере модели User.php: //У юзера есть только один профиль. У каждого профиля - только один юзер. 'moreInfo' => array(self::HAS_ONE, 'Profile', 'userId')Имена связей умышленно подобраны отличными от подключаемых таблиц, с целью акцентировать внимание в правильных местах
Последняя связь устанавливается через запрос типа:
SELECT * FROM `profile` WHERE `profile`.`userId`=:idгде параметр :id читается из модели User. Ни каких JOIN, максимальная оптимизация запроса: в справочной таблице `Profile` ищем запись, соответствующую id-нику юзера. Поэтому поле берем из справочной таблицы (Profile), а его значение - из основной (User). Нафига я это разжевываю? Да потому, что во всех других отношениях в параметре [ForeignKey] указываются поля основной таблицы! Вот это была запарка..
P.S.: в моем случае нужно было описать связь "у каждого магазина есть только один владелец". Я думал, ошибочно понимая документацию, что нужный тип связи - HAS_ONE. Но с другой стороны, у владельца может быть несколько магазинов, и правильным выбором оказалась BELONGS_TO, т.к. HAS_ONE описывает только связь "одна-к-одной". Мы потратили часа полтора, пока мне не пришла мысль ради теста поменять тип связи, и "О, чудо!" - все заработало :)
Здесь еще статья по связям. Правда она про Ruby, но это не мешает понимаю сути.
[UPD] В Yii описание связи MANY_MANY имеет свои грабли. Порядок указания полей расшивочной таблицы зависит от класса, в котором описано отношение. Таблицы тоже разные, комментарий, первоисточник (en)
//User class:
public function relations()
{
return array(
'projects' => array(
self::MANY_MANY,
'Project',
'project_user_assignment(user_id, project_id)'
),);
}
//Project class:
public function relations()
{
return array(
'users' => array(
self::MANY_MANY,
'User',
'project_user_assignment(project_id, user_id)'
),);
}
[1oo%, EoF]Понравилась статья? Расскажите о ней друзьям: