Итак, недавно мы разбирали стандартные поля Jelly, теперь рассмотрим все, что нужно для описания связей. Лично у меня постоянно возникает путаница в названиях и составе свойств для различных типов связей, поэтому будем систематизировать имеющуюся информацию.
Вступление
В качестве подопытного выступает модель Model_User
, которую мы будем постепенно связывать с другими моделями. Настройку будем вести от простого к сложному, в начале у нас все по умолчанию. По мере освоения материала будем усложнять задачу нестандартными именами полей и связей. Вся работа происходит в статическом методе initialize(Jelly_Meta $meta)
, в котором описываются все специфические свойства модели. Прочитайте предыдущую статью, если не помните, как инициализируются модели в Jelly. Поля связей принципиально ничего не отличаются, только свойства (параметры для конструктора) в них немного другие.
Связь типа «Один-ко-многим» (HasMany)
Пусть имеется модель Model_User
, с которой ассоциируются статьи (один пользователь может иметь несколько статей).
Короткая форма:
'articles' => new Field_HasMany |
В результате будет автоматически вычислено, что на другом конце связи находится модель Model_Article
, внешний ключ — user_id
.
Расширенная форма:
'articles' => new Field_HasMany(array( 'foreign' => 'article.user_id' )) |
Здесь мы уже описали имена модели и внешнего ключа, разделив их точкой. Можно укоротить запись:
'articles' => new Field_HasMany(array( 'foreign' => 'article' )) |
Опустив имя внешнего ключа, мы подразумеваем автоматическое вычисление его имени. В общем, это эквивалентно записи
'articles' => new Field_HasMany(array( 'foreign' => 'article.user:foreign_key' )) |
Jelly возьмет настройки модели Model_User
для извлечения имени внешнего ключа. По умолчанию это имя модели + ‘_id’ (как и в ORM).
Изменить имя внешнего ключа для модели можно с помощью метода
foreign_key()
, напримерforeign_key('user_key')
.
Связь типа «Один-к-одному» (HasOne)
По сути это частный случай связи HasMany, и синтаксис такой же.
Связь типа «Много-к-одному» (BelongsTo)
Главное отличие (в плане структуры таблицы) от HasMany в том, что внешний ключ расположен в данной модели. Для связи пользователь-статьи это означает, что внешний ключ расположен в модели Model_Article
, и участвует в определении связи. Соответственно короткая запись:
'user' => new Field_BelongsTo |
означает, что при обращении к $article->user
произойдет попытка загрузить модель Model_User
по значению ключа user_id
в модели Model_Article
.
Если предположить, что связь должна называться ‘author‘, а внешний ключ называется author_id
и ссылается на первичный ключ id
в модели Model_User
, то получится такое объявление:
'author' => new Field_BelongsTo(array( 'foreign' => 'user.id', 'column' => 'author_id', )) |
Свойство foreign
содержит все, что касается связанной модели (имя и внешний ключ), а column
— имя поля в данной модели. Думаю, всем понятно, почему column
отсутствует в связях HasMany и HasOne Если значение для column
не указано, оно генерируется на основании указанного имени модели с прибавкой суффикса ‘_id‘, т.е. user_id в данном случае. Обратите внимание, что используется не имя связи, а имя модели!
Конечно, для свойства
foreign
можно (и нужно — это более гибкая запись) использовать ‘user.:primary_key‘ или просто ‘user‘.
Связь «Много-ко-многим» (ManyToMany)
Этот тип связи характеризуется выносом внешних ключей обеих связанных моделей в промежуточную таблицу (как правило, эта таблица только из этих ключей и состоит). Пусть это будет извечное пользователи-роли с промежуточной таблицей roles_users
.
Короткая запись:
'role' => new Field_ManyToMany |
В результате связь будет основана на предположении, что на другом конце находится модель Model_Role
, а промежуточная таблица roles_users
содержит ключи user_id
и role_id
. Имя промежуточной модели формируется по такому же принципу, что в ORM.
Предположим, что роли находятся в таблице rolez
(а модель все та же — Model_Role
) с первичным ключом role_id
, промежуточная таблица users_roles
, а внешние ключи называются user_ke
y и role_key
. В таком случае надо использовать полную запись связи:
'rolez' => new Field_ManyToMany(array( 'foreign' => 'role.role_id', 'through' => array( 'model' => 'users_roles', 'columns' => array( 'user_key', 'role_key' ) ), )) |
Итак, если свойство foreign
нам уже знакомо (обозначаем настройки для связи с моделью Model_Role
, как в HasMany), то свойство through
в новинку. Это массив, который в наиболее полном варианте должен содержать ключи ‘model‘ (имя промежуточной модели или таблицы) и ‘columns‘ (имена внешних ключей для промежуточной таблицы). Порядок перечисления внешних ключей важен — сперва идет имя внешнего ключа от текущей модели (т.е. при объявлении связи в модели Model_Role
мы бы их поменяли местами).
Есть и упрощенный вариант — указать только имя промежуточной модели/таблицы:
'rolez' => new Field_ManyToMany(array( 'foreign' => 'role.role_id', 'through' => 'users_roles' )) |
В этом случае внешние ключи будут вычислены как user.:primary_key
и role.:primary_key
.
Заключение
Давайте представим себе, как будет выглядеть условная модель Model_User
со связями:
class Model_User extends Jelly_Model { public static function initialize(Jelly_Meta $meta) { $meta->name_key('username') ->sorting(array('username' => 'ASC')) ->fields(array( 'id' => new Field_Primary, 'username' => new Field_String(array( 'unique' => TRUE )), 'password' => new Field_Password, 'email' => new Field_Email(array( 'unique' => TRUE )), // user has many articles 'articles' => new Field_HasMany(array( 'foreign' => 'article.user_id' )), // user has one profile 'profile' => new Field_HasOne(array( 'foreign' => 'user_profile' )), // user has and belongs to many roles 'roles' => new Field_ManyToMany(array( 'foreign' => 'role', 'through' => array( 'model' => 'users_roles', 'columns' => array( 'user_id', 'role_id' ), ), )), 'group' => new Field_BelongsTo(array( 'foreign' => 'user_group', 'column' => 'group_id' )), ))->foreign_key('user_id'); } } |
Думаю, тут все понятно
Спасибо за материал. Полезно;)
Мне вот интересно, стоит ли потихоньку перелазить на Hive? Есть ли перспектива у нее?
Спасибо, хорошая статья, сейчас напрягает только то, что Jelly слабо обновляется, а в оф. документации не отражено слишком много важных моментов.
Добавлю к статье еще один нюанс, связи Jelly поддерживают Lazy Load, тобишь ленивую загрузку, это значит, что до обращения к статьям (Model_Article) не будет лишних запросов к связанным таблицам, пока не укажем точно, что хотим $article->author.
Но представим что мы хотим выбрать ряд статей по заданным критериям и вывести информацию о них, в то числе и автора. Тогда цикличное обращение к $aricle->author массива $articles приведет к массовым запросам в БД, чтобы этого избежать нужно при конструировании запроса вызвать метод with(‘model_name’), в нашем случае with(‘user’), например Jelly::select(‘article’)->with(‘user’)->where(‘id’,'>’,10)->execute();
это сократит все запросы до одного, правда он будет c Left Join, но это все равно намного лучше Данный фокус проходит с связью BelongsTo, но не проходит с связью HasOne.
Спасибо.
Интересна судьба Hive, обретет ли популярность или нет.