Контент


Объявление связей в Jelly

Итак, недавно мы разбирали стандартные поля 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_key и 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');
    } 
}


Думаю, тут все понятно :)

Google Bookmarks Digg Reddit del.icio.us Ma.gnolia Technorati Slashdot Yahoo My Web News2.ru БобрДобр.ru RUmarkz Ваау! Memori.ru rucity.com МоёМесто.ru Mister Wong

Опубликовано в Kohana3.

Теги: , , , .


Комментарии (3)

Будьте в курсе обсуждения, подпишитесь на RSS ленту комментариев к этой записи.

  1. maxmara пишет:

    Спасибо за материал. Полезно;)

    Мне вот интересно, стоит ли потихоньку перелазить на Hive? Есть ли перспектива у нее?

  2. taggi пишет:

    Спасибо, хорошая статья, сейчас напрягает только то, что 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.

    Спасибо.

  3. bagir пишет:

    Интересна судьба Hive, обретет ли популярность или нет.



Можно включить подсветку кода: <code><pre lang="">...</pre></code>
Разрешены некоторые HTML теги

или используйте trackback.