Контент


ORM. Связи

И снова здравствуйте. В прошлый раз мы рассмотрели основы ORM — так сказать фундамент, без которого трудно понять его философию и полноценно использовать в своих проектах. Сегодня я расскажу как связать ORM-объекты между собой.

С чего начать?

Перед построением моделей обязательно понимание структуры таблиц в БД, с которой наши модели неразрывно связаны. И если отдельно взятую таблицу мы уже научились проецировать в модель ORM, то теперь надо их связать между собой. Давайте сперва рассмотрим теорию. Существует три вида связей между таблицами, описание которых мы сейчас и рассмотрим. А потом перейдем к использованию предоставляемых нам ORM‘ом возможностей.

Один-к-одному (1-1)

Это достаточно редкий вид связи, когда каждой записи в первой таблице соответствует только одна запись из второй, и наоборот. Теоретически эти таблицы могут быть объединены в одну (первичные ключи в них совпадают), но зачастую в целях оптимизации их разбивают на части. Например, если имеется таблица со списком статей и стоит выбор, где хранить информацию со статистикой просмотра этих статей, вполне логичным решением будет создание отдельной таблицы со связью один-к-одному. В этом случае дополнительная таблица выглядит как «пристройка» к основной. Если говорить в терминах фреймворка, то мы можем создавать вспомогательные таблицы вокруг таблицы users из состава модуля Auth (например, я ранее упоминал о таблице user_details с дополнительными анкетными данными пользователей). Опять же, в этом случае нам не придется вносить изменения как в модель, так и в структуру таблицы.

Предположим, у нас есть схема из этих двух таблиц:

Связь один-к-одному

Связь один-к-одному


Первичный ключ id из таблицы users перешел в таблицу user_details, только был переименован в user_id. В принципе, он мог бы называться также — id, но даже в случае со связью один-к-одному из двух таблиц можно выделить главную. Поэтому, называя ключ user_id, видно, какая из таблиц зависимая, и откуда в ней появился первичный ключ.

Создадим заготовки для моделей:

// models/User.php
class User_Model extends ORM {
 
}
// models/User_Detail.php
class User_Detail_Model extends ORM {
    // Relationships
    protected $has_one = array('user');
    protected $primary_key = 'user_id';

Скорее всего у вас подключен модуль Auth и модель User_Model уже существует. Поскольку мы его будем рассматривать как сторонний (точнее как часть фреймворка), вносить в него изменения нежелательно и даже бессмысленно. А если модели нет, создаете заготовку как в моем примере — модель будет совершенно работоспособна, но ничего конкретного мы в нее не вносим. Пусть вспомогательные модели под нее подстраиваются.

В модели User_Details всего две строчки. Первая описывает собственно связь. Все связи модели хранятся в свойствах в виде массивов. Каждому типу связи соответствует свой массив. В данном случае мы заполняем свойство $has_one. Таких связей может быть несколько, например так:

protected $has_one = array('user', 'anything_else');


Вторая строчка описывает имя первичного ключа. По умолчанию свойство $primary_key установлено в ‘id‘, как и рекомендует нам документация по ORM, однако мы можем обговорить имя ключа, чем и воспользуемся. При связи один-к-одному такое часто бывает. Кроме этих двух строк ничего не надо. Пишем где-нибудь в контроллере:

// получаем анкетные данные пользователя 1
$ud = ORM::factory('user_details', 1);
// зачем над данные без самого пользователя?
// обращаемся к нему как к обычному свойству объекта
$user = $ud->user;


Обратите внимание, что хотя связь между таблицами в принципе равнозначная, из модели User_Model мы так получить User_Details не сможем (ведь в ней не описана эта связь в свойстве $has_one). Еще один важный момент: как мы связь назвали при ее описании в свойстве $has_one, под таким именем она нам будет доступна. Теперь понятно, что было бы, если бы внешние ключи назывались не user или article, а user_id и article_id?

Еще одно примечание. В официальной документации в качестве примера приводится связь между моделью Blog_Post_Model и User_Model из соображений, что одному посту в блоге соответствует один пользователь (автор). Я с этим категорически не согласен, т.к. связь один-к-одному должна быть одинаковой на обоих концах, а у пользователя может быть несколько постов, так что там связь типа один-ко-многим. Такая вот оппозиция ;)

Один-ко-многим (1-N)

Данная связь означает, что каждой записи в одной таблице может соответствовать несколько записей из другой таблицы. Это самая распространенная связь между таблицами. На схеме ниже рассмотрено взаимодействие между темами на форуме и комментариями к ним:

Связь один-ко-многим

Связь один-ко-многим


На основании данной схемы описываем модели Post_Model и Comment_Model:

class Post_Model extends ORM {
	protected $db = 'forum';
	protected $has_many = array('comments');
}
 
class Comment_Model extends ORM {
	protected $db = 'forum';
	protected $belongs_to = array('post');
}


Думаю, здесь надо объяснить немного поподробнее. Сперва насчет переменной $db — в ней до вызова конструктора хранится имя профиля из файла(ов) config/database.php. Я уже писал о том, что в каждом отдельно взятом модуле хочу сделать свои префиксы для таблиц. Поэтому на схеме таблицы названы с префиксом ‘forum_‘. Конечно, можно было использовать свойство $table_name и указать имя таблицы, но тогда это пришлось бы делать для каждой модели. Кроме того, такой подход заставит нас переделывать все модели, если префикс поменяется (в идеале подобный выбор предоставляется пользователю во время инсталляции проекта). Другой вариант еще хуже — назвать модели в соответствии с именами таблиц, т.е. Forum_Post_Model и Forum_Comment_Model. Недостатки те же, да еще и имя класса увеличилось. Поэтому остановимся на префиксах. Если у вас их нет (или вдруг вы от них отказались), то просто не используйте опцию table_prefix в настройках профиля подключения к БД — и больше ничего менять не придется.

Далее — новые для нас свойства $has_many и $belongs_to. Их использование ничем не отличается от $has_one. Для таблицы, которая как бы является родительской (комментарии принадлежат статье, а не статьи комментарию) используем $has_many (дословно — «имеет много«), для «дочерней» таблицы — $belongs_toотносится к«). На уровне структуры таблиц это сопровождается добавлением первичного ключа родительской таблицы (в нашем случае это forum_posts.id) в дочернюю в виде внешнего ключа post_id. Типичный механизм — имя таблицы в единственном числе (post, т.к. префикс мы привычно отбрасываем) плюс суффикс ‘_id‘. Опять же, не всегда получается называть поля как мы хотим, поэтому ниже я покажу дополнительные свойства для жесткого указания имен внешних ключей.

Часто люди путаются при заполнении связей модели, где употреблять имя в единственном числе, а где во множественном. Следует рассуждать логически: статья имеет много комментариев, следовательно $has_many = array(‘comments’). Аналогично комментарий относится к статье, т.е. $belongs_to(‘post’). Связи один-к-одному всегда в единственном числе. Проще всего запомнить — если в названии связи есть слово many, то употребляем множественное число (т.е. связь неоднозначная, на противоположном ее конце несколько объектов).

Использование аналогично связи один-к-одному, только будьте готовы к тому, что на в результате можем получить массив записей:

// ищем пост №1
$post = ORM::factory('post', 1);
// выводим на экран текст топика
echo $post->text."<br />";
// получаем комментарии топика
// !! это не ORM-объект, а ORM_Iterator !!
$comments = $post->comments;
// далее в цикле пробегаемся по комментариям топика
foreach($comments as $comment)
    echo $comment->text."<br />";
// а теперь получаем топик, которому принадлежит комментарий №2
$comment = ORM::factory('comment', 2);
echo $comment->post->id;


Запомните, если связь неоднозначная, т.е. мы ждем несколько объектов, связанных с нашим, в результате получим не ORM-объект, а экземпляр класса ORM_Iterator. Для простоты можете рассматривать его как массив ORM-объектов нужного вам класса. Если же вас смущает использование непонятного класса, описание которого отсутствует (на данный момент) в официальной документации — можете просто превратить его в настоящий массив с помощью метода as_array().

Много-ко-многим (M-N)

Этот тип связи появляется, когда между связанными таблицами нет однозначности в обе стороны, т.е. каждой строке из одной таблицы может соответствовать несколько записей из другой, и наоборот. Типичный пример — у пользователя может быть несколько ролей (так было в модуле Auth, идущем в составе Kohana 2.3.x), каждая роль может быть присвоена нескольким пользователям. Поэтому простым перекидыванием первичного ключа из одной таблицы в другую связь не наладить — нет однозначности. Решение — создание промежуточной таблицы (pivot table) между ними, в которую добавляются первичные ключи обеих таблиц. Обычно кроме этих полей в ней ничего нет.

Связь много-ко-многим

Связь много-ко-многим

Имя для промежуточной таблицы составляется из расставленных в алфавитном порядке названий связанных таблиц, объединенных знаком подчеркивания. Первичный ключ состоит из внешних ключей таблиц users и forum_roles (user_id и role_id соответственно). По сути таблица roles_users служит только для хранения пар пользователь-роль, чтобы из одной таблицы можно было «перескочить» в другую. Давайте посмотрим, как это выглядит в терминах Kohana:

class User_Model extends ORM {
	protected $has_and_belongs_to_many = array('roles');
 
class Role_Model extends ORM {
	protected $db = 'forum';
	protected $has_and_belongs_to_many = array('users');
}

Как видно, для указания связи много-ко-многим используется свойство $has_and_belongs_to_many (часто на форумах используется термин HABTM, аббревиатура от данного названия). Обратите внимание, что промежуточная таблица roles_users вообще не участвует в процессе создания моделей ORM, главное — чтобы ее имя и названия полей соответствовали требования ORM (либо были указаны явно). В результате получим всех администраторов системы (id=1):

$role = ORM::factory('role', 1);
foreach ($role->users as $user)
   echo $user->username."<br />";

И все-таки, насколько неудобно использовать id при создании объектов! Насколько более понятны и близки нам «говорящие» поля, такие как username или name. Естественно, разработчики предусмотрели способ передавать при создании экземпляра модели не идентификатор, а произвольную строку. Для этого надо переопределить метод unique_key($id). Можете просто поковырять модель User_Model в модуле Auth, там это уже сделано.

Но что же делать, если в промежуточной таблице необходимо хранить не только внешние ключи? Например, в рассматриваемом нами случае с ролями могут присутствовать поля created (время добавления роли) и expired (время окончания действия роли). В таком случае вместо одной связи много-ко-многим между двумя таблицами мы получаем две связи один-ко-многим между тремя:

Во что превратилась связь много-ко-многим

Во что превратилась связь много-ко-многим


Соответственно код моделей будет такой:

class User_Model extends ORM {
	protected $has_many = array('assigned_roles');
 
class Role_Model extends ORM {
	protected $db = 'forum';
	protected $has_many = array('assigned_roles');
}
 
class Assigned_Role_Model extends ORM {
	protected $db = 'forum';
	protected $belongs_to = array('users', 'roles');
}


В этом случае мы рассматриваем такую связь как две: наличие нескольких назначенных ролей пользователю и возможность назначения роли несколько раз (предполагается, что различным пользователям). Кстати, никто не мешает комбинировать оба варианта (только класс и таблица должны называться forum_roles_users, иначе запрос не сформируется).

«Сквозное» один-ко-многим

Рассмотренный выше вариант преобразования HABTM в два has_many доступен в более компактном виде. Посмотрите на такую запись:

class User_Model extends ORM {
	protected $has_many = array('roles' => 'assigned_roles');
 
class Role_Model extends ORM {
	protected $db = 'forum';
	protected $has_many = array('users' => 'assigned_roles');
}
 
class Assigned_Role_Model extends ORM {
	protected $db = 'forum';
	protected $belongs_to = array('users', 'roles');
}

Теперь в $has_many мы записываем как промежуточную модель (`assigned_roles`), так и «конечную» (`roles` и `users`) как бы с использованием псевдонимов (aliasing). Теперь можно обращаться как к $user->roles, так и к $user->assigned_roles.

Это конечно удобно, но взамен мы теряем возможность использовать псевдонимы для «настоящих» связей один-ко-многим. Даже не знаю, стоило ли оно того. Впрочем, в версии 2.4 для «сквозных» связей будет предусмотрено отдельное свойство $has_many_through.

Работа со связями

Как заполнять таблицы мы знаем. А как создавать связи между конкретными записями? Как добавить пользователю X роль Y, добавить комментарий №4 именно в топик №13? Все довольно просто. Сейчас мы рассмотрим доступные нам возможности.

  • В случае связи один-к-одному все достаточно просто: в дочернюю таблицу необходимо подставить ключ из родительской. Например, при сохранении анкетных данных в поле $user_details->user_id надо подставить что-то вроде $user->id, где $userORM-модель сохраняемого пользователя. Аналогичные действия надо предпринять при сохранении объекта, связанного с другим через $belongs_to.
  • Несколько сложнее добавление связи в случае много-ко-многим. Для этого в ORM добавили специальный метод add(). Использовать надо так:

    // создаем объект пользователя с id=$id
    $user = ORM::factory('user', $id);
    // добавляем ему роль с id=$roleid
    $user->add(ORM::factory('role', $roleid));


    В качестве параметра передается экземпляр ORM-модели, с которой надо связать объект. Для проверки существования связи с данной моделью (проверка на дублирование) доступен метод has(), а для удаления связи — remove(). Оба они также принимают в качестве параметра проверяемый/удаляемый объект.

    Возвращаясь к удобству переопределения метода unique_value($id): насколько ORM::factory(‘role’, ‘admin’) читабельнее, чем ORM::factory(‘role’, 1)!

  • Специальный метод для удаления связи существует только для много-ко-многим. Если вы удалите топик, комментарии к нему Kohana удалять не будет. Либо об этом позаботитесь вы сами (программно), либо (что проще) об этом позаботится СУБД, если вы при создании таблиц будете использовать конструкции типа ON DELETE CASCADE.

Custom’изация ORM-моделей

Итак, если какие-то параметры (названия таблиц, ключей и т.д.) у нас отличаются от ожидаемых по правилам ORM, есть несколько способов вручную указать, что и куда подставлять. Часть свойств я упоминал в предыдущей статье, сейчас расскажу про оставшиеся, отвечающие за взаимодействие с другими моделями.

$load_with. Позволяет указать, какие модели загружать вместе с данной автоматически. Представляет из себя массив связей. Например, чтобы при создании модели User_Detail загружались еще и данные этого пользователя, просто указываем $load_with = array(‘user’). Совместная загрузка будет работать только с однозначными связями, т.е. если указываемая модель была занесена нами в $has_one или $belongs_to. Поэтому в модели Comment_Model мы можем указать загрузку модели Post_Model, но не наоборот. По сути мы сразу вызываем join-запрос, не дожидаясь «ленивой» загрузки связанной модели в дальнейшем. Рекомендую не увлекаться этим свойством, т.к. не все загружаемые вместе с текущим объектом модели вам пригодятся.

$foreign_key. Массив указанных вручную внешних ключей для модели. Используется только для связей один-ко-многим (в других случаях внешних ключей по сути нет). Если в дочерней модели имя поля, являющегося внешним ключом, не соответствует концепии ORM (т.е. не имя_родительской_модели + суффикс ‘_id‘), то необходимо указать в данном свойстве истинное имя поля. Предположим, что в рассмотренной нами выше схеме внешний ключ таблицы forum_comments называется new_post_id:

Схема комментарии - топики с измененным внешним ключом

Схема комментарии - топики с измененным внешним ключом


Очевидно, что при попытке доступа к соединенной таблице в ORM произойдет ошибка, т.к. в join будет вставлено не new_post_id, а post_id. Поэтому будем использовать свойство $foreign_key:

class Post_Model extends ORM {
	protected $db = 'forum';
	protected $has_many = array('comments');
	protected $foreign_key = array('comments'=>'new_post_id');
}
 
class Comment_Model extends ORM {
	protected $db = 'forum';
	protected $belongs_to = array('post');
	protected $foreign_key = array('post'=>'new_post_id');
}


Обратите внимание, что имя внешнего ключа надо прописывать в обеих моделях! Если в модели Post_Model вы это не сделаете, то при попытке получить все комментарии топика (что-то вроде $post->comments) произойдет ошибка SQL-запроса. Возможно, не совсем логичное решение разработчиков, т.к. при изменении внутренней структуры дочерней модели изменения придется внести и в родительскую. Также запомните, что ключи в массиве $foreign_key должны совпадать с указанными в связях, т.е. ‘post‘ для Comment_Model и ‘comments‘ для Post_Model.

Свойство появилось только в версии 2.3.2!

Ну, и напоследок — псевдонимы (aliasing).
Иногда имя таблицы, с которой связывается текущая таблица, не выражает ту роль, которую она играет. Типичный пример — таблица users. Пользователь может быть автором, модератором, редактором, голосующим и т.д. Кроме того, в рамках одной таблицы может быть несколько внешних ключей к одной и той же таблице. Как быть в этом случае? Помогут псевдонимы:

class Article_Model extends ORM {
	// у статьи может быть автор и редактор, причем это разные люди
	protected $belongs_to = array('author'=>'user', 'redactor'=>'user');
}


Как видите, все достаточно просто. Сначала пишем имя связи (т.е. роль, которую играет модель), а далее — само имя модели (причем в зависимости от типа связи оно может быть в единственном или множественном числе, как я уже упоминал). Используя эти псевдонимы, в контроллере мы можем вызывать «магические» свойства $article->author и $article->redactor.

Предположим, в модели также описана связь с категориями, для которой не нужны псевдонимы. Не стоит писать

protected $belongs_to = array('author'=>'user', 'redactor'=>'user', 'categories'=>'categories');

Можно смешить псевдонимы с обычными ключами:

protected $belongs_to = array('author'=>'user', 'redactor'=>'user', 'categories');

Проблемы, с которыми можно столкнуться

Использование одной таблицы в различных ролях. Опять же, центральная в моей схеме БД таблица — users. Мы можем создать одну модель и нафаршировать ее связями со всеми таблицами, использующими ее данные (если вспомнить схему, приведенную мной в статье о модуле «форум«, становится понятно, о чем это я. И это только один модуль!). А можем выделить роли этой таблицы и создать для каждой из них свою модель. Для указания имени таблицы, напомню, есть свойство $table_name. Например, роль «Автор» (Author) просто напрашивается для модуля «форум«, т.к. присутствует и в комментариях, и в топиках. Можно поиграться и с наследованием от базового класса, но это не всегда предсказуемо. К примеру, свойства модели при переопределении не дополняются базовыми, т.е. связи предка будут потеряны.

Применение префиксов таблиц. Если между собой связываются таблицы с различными префиксами (к примеру, users и forum_roles из схемы про много-ко-многим), то могут возникнуть проблемы с автоматическим вычислением системой имен таблиц. В первую очередь это связано именно со связями много-ко-многим. Имя промежуточной таблицы вычислится в разных моделях по-разному, и в одну из сторон связь работать не будет. В этом случае вместо forum_таблица1_таблица2 придется использовать таблица1_forum_таблица2. Это логично с точки зрения ORM, но не очень удобно, если мы хотим, чтобы все таблицы модуля начинались одинаково.

Что дальше?

Думаю, с теорией теперь более-менее ясно. В качестве закрепления попробуйте реализовать большую и сложную схему с кучей связанных между собой таблиц. В следующей статье я покажу это на примере описанной мной ранее схемы модуля «форум«.

PS. Надеюсь, я ничего не забыл, если что — традиционно тыкайте меня в ошибки в комментариях.

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

Опубликовано в cправочник.

Теги: , , , .


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

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

  1. cwer пишет:

    Спасибо большое. Очень полезно :)

  2. cwer пишет:

    Возник вопрос :)
    в файле ORM.php есть строки (около 338)

    // empty manymany relationship
    return $this->related[$column] = $model->where($model->table_name.’.’.$model->primary_key, NULL)->find_all();

    Зачем? почему бы просто не вернуть NULL? это лишний запрос с WHERE .. IS NULL, не знаете зачем так сделано?)

  3. BIakaVeron пишет:

    Хороший вопрос. По сути получается выборка записей с пустыми значениями первичного ключа…

  4. vgray пишет:

    а как описать след ситуацию в терминах ORM
    есть тaблица авторов (id,author_name) есть таблица связей
    (id,author_id,cooperate_with_author). Те один автор может иметь связи со многими другим авторами ( например они вместе писали книгу). Получается что нужна свзязь one-to-many от таблицы авторов к таблицы связей ( ключ id author_id ) и нужна связь one-to-one от таблицы связей к таблице авторов ( ключ cooperate_with_author id).

  5. BIakaVeron пишет:

    А ведь соавторов может быть много. Мне представляется, что тут надо предусмотреть таблицу «Соавторство», которая будет связана с таблицей authors связью много-ко-многим (в соавторах может быть несколько авторов и каждый автор может быть соавтором в нескольких случаях).

  6. vlad пишет:

    Если HABTM определены для users и forum_roles нужно ли определять соответствующие внешние ключи для forum_roles_users? Использование InnoDB обязательно?
    И как избежать генерирования выборок при M-N типа
    SELECT `user_id` AS `id` FROM `forum_roles_users` WHERE `forum_roles_users`.`user_id` = 3
    // возвращает 5
    SELECT `users`.* FROM `users` WHERE `users`.`id` IN (5) ORDER BY `users`.`id` ASC

  7. BIakaVeron пишет:

    Использование внешних ключей на уровне СУБД необходимо для автоматического поддержания целостности данных. Т.е. Вы можете вообще про минимуму описать структуру таблицы, а все проверки (NULL, UNIQUE, внешние ссылки…) совершать собственноручно, через приложение. Но зачем, если все инструменты есть в СУБД? В частности, очень удобны конструкции вида ON DELETE CASCADE, позволяющие при удалении статьи автоматически очищать все относящиеся к ней комментарии.

    Про второй случай опишите подробнее. Не очень понимаю, как выборка user_id по user_id=3 может вернуть user_id=5? :) Может, напутали чего?

  8. VKS пишет:

    Столкнулся с проблемой при связи manymany
    Ошибка при использовании метода has_{model}, версия 2.3.4, any suggestions?

  9. BIakaVeron пишет:

    А откуда Вы взяли такой метод в ОРМ2? Если разве что в старых туториалах… Используйте has($model).

  10. VKS пишет:

    Да уже разобрался, да это действительно наследие старого времени.

  11. cybexx пишет:

    Можно ли с помощью ORM выбрать рубрики и количество записей в рубриках одним запросом. У меня получается только выбор рубрик, потом через foreach поиск количества записей в каждой рубрике, т.е. запросов = кол-во рубрик +1.
    Заранее спасибо.

  12. cybexx пишет:

    это пояснение к предыдущему моему камменту:
    я все таки решил изучать сначала 2.3, т.к. документации для этой ветки все таки больше. Поэтому желательно ответ давать применительно к ORM в Kohana 2.3.

  13. BIakaVeron пишет:

    Не надо делать его в foreach, тогда уж лучше вторым запросом сделать что-то вроде ‘SELECT category_id, count(*) as cnt_records FROM records GROUP BY category_id’, используя методы QBuilder’а.
    А вообще, я стараюсь в «родительские» таблицы добавлять статистические поля, в данном случае в таблицу рубрик удобно добавить поле records, которое увеличивать после создания новой записи.

  14. cybexx пишет:

    чтобы уточнить…
    Т.е. исключительно средствами ORM такой запрос сгенерить нельзя ?

    SELECT t1.*, COUNT( t2.id ) AS count
    FROM cats t1
    LEFT JOIN records t2 ON t1.id = t2.cat_id
    GROUP BY t1.id

    Интересуюсь не с целью очернить фрейморк, а только для того чтобы знать на что способен ORM

  15. BIakaVeron пишет:

    Сам по себе ORM для этого непредназначен. Дело в том, что возвращаемый набор записей не может быть представлен виде имеющихся моделей (что является основной идеей ORM). Другое дело, в что любая ORM-модель может реализовывать метод с использованием QBuilder’а внутри.

  16. souznik пишет:

    День добрый!
    Если можно, то у меня вопрос.
    Ситуация такая, есть промежуточная таблица, в которой кроме ключей других таблицах хранится еще и другая информация. Т.е. второй вариант из «Много-ко-многим». Сделал по вашему описанию, теперь не могу понять, как добавить мне дополнительное значение в промежуточную таблицу.

  17. BIakaVeron пишет:

    Примерно так:

    // создается промежуточный объект
    $pivot = ORM::factory('model1_model2');
    // задаем связи с родительскими таблицами
    $pivot->model1_id = 1;
    $pivot->model2_id = 2;
    // задаем собственно внутренние значения объекта
    $pivot->value = 'qwerty';
    $pivot->save();

  18. souznik пишет:

    BIakaVeron, но ведь мне надо добавить например юзера, которому дали роль админа с какой то даты по какую то… По вашему коду мне придется добавить юзера, потом добавить к ему роль и дату?

  19. BIakaVeron пишет:

    Да:
    1. Создаете пользователя (роль скорее всего уже есть).
    2. Создаете промежуточную запись (связываете ее с юзером и ролью, добавляете дату).

    PS. Если говорить про Ko3, там в методе add() добавлен дополнительный третий параметр $data, который позволяет указать значения остальных полей промежуточной модели.

  20. Андрей пишет:

    Для таких моделей:
    class User_Model extends ORM {
    protected $has_and_belongs_to_many = array(‘roles’);

    class Role_Model extends ORM {
    protected $db = ‘forum’;
    protected $has_and_belongs_to_many = array(‘users’);

    Выборка всех пользователей для роли замечательно работает.
    $role = ORM::factory(‘role’, 1);
    foreach ($role->users as $user)
    echo $user->username.»";

    Но если пытаюсь выбрать в обратную сторону: все роли для пользователя:
    $user = ORM::factory(‘user’, 1);
    foreach ($user->roles as $role)
    echo $role->name.»";

    Выдает ошибку, что не существует таблицы users_roles.
    Я понимаю, что здесь все из-за нестандартных названий таблиц, но как раз и сложилась ситуация когда хочется работать именно с такими названиями.

  21. BIakaVeron пишет:

    1. Как объявляли модели? (текст можно на pastebin выложить)
    2. Имя промежуточной таблицы вычисляется одинаково для обеих моделей (должно быть roles_users), Вы что-то меняли?

  22. Андрей пишет:

    п.1. Объявил в точности как в Вашем примере в разделе Много-ко-многим (M-N) самый первый пример.
    п.2. Имя таблицы действительно должно было быть roles_users, если бы у нас были таблицы users и roles. Но в том то и фишка, что вторая таблица forum_roles.
    поэтому пример :
    $role = ORM::factory(‘role’, 1);
    foreach ($role->users as $user)
    echo $user->username.»";
    работает с таблицей forum_roles_users (так нарисовано на вашей схеме). если ее перименовать в roles_user, тогда выдается ошибка «Ошибка SQL: Table ‘test.forum_roles_users’ doesn’t exist»

  23. Андрей пишет:

    а обратная выборка
    $user = ORM::factory(‘user’, 1);
    foreach ($user->roles as $role)
    echo $role->name.»";

    требует таблицы roles_users
    Ошибка SQL: Table ‘test.roles_users’ doesn’t exist

  24. BIakaVeron пишет:

    Ага, я об этом писал в одном из последних абзацев данной статьи :) Плюс, ругался на реализацию ORM в 2.3.4 тут.

    На данный момент сделал вывод, что надо использовать однотипные имена классов и таблиц (т.е. если модели для форума, то все начинаются с forum_, то же самое с именами таблиц). На самом деле это даже логичнее, если планируется несколько различных модулей на базе единой авторизации. Например:

    ядро
    user
    role

    модуль Forum
    forum_post
    forum_comment
    forum_user
    forum_role

    модуль Blog
    blog_article
    blog_comment
    blog_user
    blog_role

    Так все специфические для отдельных модулей значения (статистика пользователя к примеру) не будут храниться в общей таблице users, а распределятся по соответствующим таблицам своих модулей.

  25. Oleg пишет:

    Буду рад если подскажете.
    Из сегмента урл мы получаем значение и выбираем из таблицы строку попутно проверяя ещё несколько условий, так вот как перед выводом данных определить вернула нам бд какой-то результата или нет?

  26. biakaveron пишет:

    В смысле? Если идет выборка одного значения (через find()), то просто проверяем if ($result->loaded()), а если это несколько значений (итератор, через find_all()), то обычно через count($result).

  27. Oleg пишет:

    Не знал про $result->loaded() спасибо помогло.

  28. Oleg пишет:

    Буду рад если поможете в разъяснении ещё одного вопроса.
    Есть три таблицы
    Таблицы nodes, сategories и связывающая их nodetocat
    Я извлекаю запись

      $node = ORM::factory('node')
    	->where('node_id', 'LIKE', $idNode)
    	->where('node_state', 'LIKE', 'public')
    	->find();


    Как мне проверить соответствует ли она определённому полю в таблице сategories?

  29. biakaveron пишет:

    1. Почему у Вас там LIKE? Наверное должно быть равенство.
    2. Объявляем связь ManyToMany:

    protected $_has_many = array(
         'categories' => array('through' => 'nodetocat')
    );


    3. Для проверки, имеет ли $node категорию $category, используем метод has():

    if ($node->has('categories', $category) {...}

  30. Oleg пишет:

    1. Да, спасибо должно быть равенство.

    При проверке через has выкидывает ошибку.
    Call to a member function pk() on a non-object
    И ссылается на файл MODPATH\orm\classes\kohana\orm.php [ 1041 ]
    В моделях делаю так

    Class Model_Node extends ORM 
    {
    protected $_primary_key = 'node_id';
    protected $_has_many = array (
    	 'categories' => array(
    		 'model' => 'category',
    		 'through' => 'nodetocat',
    		 'foreign_key' => 'node_id',
    		 'far_key' => 'category_id'
    	)
     
    );
    };
     
    Class Model_Categorie extends ORM 
    {
              protected $_primary_key = 'category_id';
    		  protected $_has_many = array(
    		                     'node' => array (
                                                 'model' => 'node',
                                                 'through' => 'nodetocat',
                                                 'foreign_key' => 'category_id',
                                                 'far_key' => 'node_id'
    							 )						 
    		  );
    };


    А в контроллере соответственно

    $flat = ORM::factory('node')
                                            ->where('node_id', '=', $nameNode)
                                            ->where('node_state', '=', 'public')
                                            ->find();
                            if ($flat->has('catergories', $nameCat)) 
    						{ echo $nameCat;}

  31. biakaveron пишет:

    $nameCat должна быть моделью ORM. В данном случае получится что-то вроде:

    $cat = ORM::factory('categorie')->where('name', '=', $nameCat)->find();
    if ($flat->has('categories', $cat))
    { ... }

  32. Oleg пишет:

    Действительно, заработало а если мы делаем выборку не одной записи а несколких при помощи find_all(); Как тут быть?

  33. Oleg пишет:

    Пробовал сделать так:

     foreach ($node as $n)
    			 {
    			 if($n-&gt;has('actions',  $actions))	
    				echo ($n);
    			}


    Работает но я в цикле разобрал объект, проверил отобрал нужное а как собрать обратно? Или не нужно?

  34. biakaveron пишет:

    А зачем? $node так и остается списком моделей. Или Вы хотите удалить лишние модели? Тогда проще собирать отдельный массив с подходящими объектами.

  35. Oleg пишет:

    Хочу удалить лишние, думал про массив но в голову взбрело что лучше засунуть в объект…



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

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