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]

Понравилась статья? Расскажите о ней друзьям:


Комментарии

Показать/скрыть правила
Имя
[i] [b] [u] [s] [url]
:-) ;-) :D *lol* 8-) :-* :-| :-( *cry* :o :-? *unsure* *oops* :-x *shocked* *zzz* :P *evil*

Осталось 1000 символов.
Код защиты от спама Обновить код
Каждый комментарий проходит ручную модерацию. 100% фильтрация спама.
Для работы модуля комментариев включите javaScript

Продвижение
Время
Метки