Почти год назад я написал простенький мануал про ORM в Kohana, в котором затронул примитивные операции и совершенно не затронул работы со связями. И вот после целого года исканий я практически созрел и обрёл некий опыт? который спешу зафиксировать в виде этой шпаргалки.
Про инсталляцию и настройку ORM писать не буду. И так уже много раз писано.
СОГЛАШЕНИЯ ORM
===================================================
Я уже писал о соглашениях. На всякий случай повторюсь.
- Названия таблиц должны быть во множественном числе по всем правилам английской грамматики. Например: users, posts, articles, cities.
- Названия моделей должны быть в единственном числе. Например: user, post, article, city.
- Таблица должна обязательно иметь автоинкрементное поле id
Немного холивара на эту тему
————————
На самом деле есть возможность не соблюдать соглашения по наименованиям, и есть способы переопределить названия и прочее. Но будет ли от этого легче потом разбирать код другому программисту, или даже вам самим?
Я отношусь к этому как к различным соглашениям по наименованию переменных. Вы можете их не придерживаться и называть переменные как вам взбредёт в голову. Но такая свобода может выйти боком.
Однако меня слегка понесло. Возвращаюсь к теме.
ТАБЛИЦА БЕЗ СВЯЗЕЙ
===================================================
В минимальном варианте достаточно создать модуль
1 2 3 | <?php Class Model_Article extends ORM {}; ?> |
Далее я описываю типичные операции с такими таблицами
Чтение записи
—————
1 2 3 4 5 | $article = ORM::factory('article',$id); echo $article->name; //Или echo ORM::factory('article',$id)->name; |
Чтение нескольких записей
———————
1 2 3 4 5 6 7 8 9 | $articles = ORM::factory('article')->find_all(); //или $articles = ORM::factory('article')->limit(10)->offset(20)->find_all(); //или $articles = ORM::factory('article')->where("a",">",$b)->find_all(); //а потом foreach($articles as $article){ echo $article->name; } |
Удаление записи
—————–
1 2 3 4 | $article = ORM::factory('article',$id); $article->delete(); //или лучше ORM::factory('article')->delete($id); |
Изменение записи
———————
1 2 3 | $article = ORM::factory('article',$id); $article->name="blablabla"; $article->save(); |
Создание записи
———————–
1 2 3 | $article = ORM::factory('article'); // Просто нигде не указываем ID $article->name="blablabla"; $article->save(); |
Примечание. В обоих случаях для сохранения записи используется процедура save().
Люди говорят, что в Kohana3.1 за создание нового отвечает процедура create(), а за сохранение update(). А save() убирают совсем.

ТАБЛИЦА СО СВЯЗЬЮ МНОГО-К-ОДНОМУ
===================================================
Чаще всего такая связь реализуется с помощью создания внешнего ключа в одной из таблиц. Причём одна из таблиц имеет связь “много-к-одному”, другая “один-ко-многим”.
Для таблицы, которая имеет связь “много-к-одному” используют поле $_belongs_to и создают примерно такую модель:
1 2 3 4 5 6 7 8 9 10 11 | <?php Class Model_Article extends ORM { protected $_belongs_to = array( 'author' => array( 'model' => 'aurhor', 'foreign_key' => 'author_id', ) ); }; ?> |
Далее я описываю типичные операции с такими таблицами
Чтение связи
———————————
Когда связь задана и прописана в базе, чтение становится банальным чтением поля.
1 2 | $article = ORM::factory('article',$id); echo $article->author->name; |
Причём здесь $article->author – это такая же полноценная модель, как и собственно $article. Над ней тоже доступны все описанные в этом топике операции.
Изменение связи
——————————–
1 2 3 | $article = ORM::factory('article',$id1); $article->author= ORM::factory('author',$id2); $article->save(); |
Просто меняем поле. Не забываем сохранять.

ТАБЛИЦА СО СВЯЗЬЮ ОДИН-КО-МНОГИМ
===================================================
Эта ситуация противоположна рассмотренной в предыдущей ситуации. Для таблицы, которая имеет связь “один-ко-многим” используют поле $_has_many и создают примерно такую модель:
1 2 3 4 5 6 7 8 9 10 | <?php Class Model_Author extends ORM { protected $_has_many = array( 'articles' => array( 'model' => 'article', 'foreign_key' => 'author_id', ) ); }; ?> |
Прочитать связь
———————-
У одного автора может быть много статей.
1 2 3 4 | $author = ORM::factory('author',$id); foreach ($author->articles->find_all() as $article){ echo $article->title; } |
Удаление записи из таблицы.
————————————-
Если просто удалить запись, то в соответствующей таблице останутся записи с зависшими связями. Это собственно нарушение целостности базы данных. Это очень плохо. При удалении записи нужно определиться что делать с соотвествующими записями.
Например если удаляем автора то, что делаем со статьями? Либо присваивать им другого автора, либо их тоже удалять.
1 2 3 4 5 6 7 8 9 | $author = ORM::factory('author',$id); $author2 = ORM::factory('author',$id2); foreach ($author->articles->find_all() as $article){ $article->author=$author2; // Или удалять $article->delete(); } $author->delete(); |
Нужно помнить, что у статьи тоже могут быть довольно сложные правила удаления, и общая схема действий может быть довольно сложной
Поскольку все эти телодвижения имеют гораздо большее отношение к базе данных нежели к бизнес-логике, то разумно поместить этот алгоритм как функцию прямо в модель.

ТАБЛИЦА СО СВЯЗЬЮ МНОГО-КО-МНОГИМ
===================================================
В отличие от предыдущего варианта тут нужна промежуточная таблица. И в отличие от предыдущего примера ситуация симметричная
Как и в предыдущем примере используется $_has_many, но уже не по внешнему ключу (foreign_key), а через таблицу (through)
Приведём пример одной модели. Другая модель будет похожей
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | <?php Class Model_Article extends ORM { protected $_has_many = array( 'tags' => array( 'model' => 'tag', 'through' => 'tags_articles' ) ); // А это связь с предыдущего примера protected $_belongs_to = array( 'author' => array( 'model' => 'aurhor', 'foreign_key' => 'author_id', ) ); }; ?> |
UPD:
Если угораздило создать таблицы не с теми названиями, что вы хотите привязать по внешнему ключу – внешние ключи можно переопределить помощью слов foreign_key и far_key так:
1 2 3 4 5 6 | 'tagged_images' => array( 'model'=> 'image', 'foreign_key' => 'user_id', 'through' => 'users_images', 'far_key' => 'image_id', ), |
Создание связи
———————–
Создание связи на низком уровне означает создание записи в промежуточной таблице, содержащей ссылки на обе связанные записи.
А на ORM это делается с помощью функции add
1 2 3 | $tag =ORM::factory('tag',$id1); $article =ORM::factory('article',$id2); $article->add('tags',$tag); |
Если эта связь также прописана в модели Model_Tag, а это советую обязательно сделать, то можно было бы написать и так
1 2 3 | $tag =ORM::factory('tag',$id1); $article =ORM::factory('article',$id2); $tag->add('articles',$article); |
В конце концов результат будет один и тот же – запись в промежуточной таблице.
Удаление связи
———————-
Удаление выполняется с помощью команды remove
1 2 3 4 5 6 7 | $article =ORM::factory('article',$id2); $tag =ORM::factory('tag',$id1); // А можно и так $tag =$articles->tags->where->('id','=',$id1)->find(); // А потом удалять $article->remove('tags',$tag); |
Доступ к связанным данным
—————————————
Всё как и в прошлом примере. Просмотр всех статей с заданным тегом:
1 2 3 4 | $tag = ORM::factory('tag',$id); foreach ($tag->articles->find_all() as $article){ echo $article->title; } |
Удаление записи из таблицы
———————–
При удалении записи опять таки нужно следить за целостностью данных. Тем более, что связи представляют собой отдельные записи в специальной таблице.
Эти связи можно также удалить,
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | $tag = ORM::factory('tag',$id); foreach ($tag->articles->find_all() as $article){ $tag->remove('articles',$article); } $tag ->delete() или перекинуть на другой тег. $tag1 = ORM::factory('tag',$id1); $tag2 = ORM::factory('tag',$id2); foreach ($tag1->articles->find_all() as $article){ $tag1->remove('articles',$article); $article->add('tags',$tag2); } $tag1 ->delete() |
Вот как-то так. Надеюсь дельная шпаргалка вышла.
Посты по теме:
RSS-подписка
Лаконично. Даже красиво. Спасибо =)
У вас ошибочка в начале. cities – множественное число, а city – единственное =)
@ xproblem:
Спасибо, исправил.
Пробую освоить ORM.
Вытаскиваю данные из таблицы Страны.
class Model_Country extends ORM {
protected $_table_name = ‘countries’;
}
$country = ORM::factory(’country’)->find_all();
$this->template->content = View::factory(’films/country’, $country);
Так не работает, а так работает:
$country = ORM::factory(’country’)->find_all();
$this->template->content = View::factory(’films/country’, array(’country’ => $country));
$country – не массив что ли ?
Неа, не массив
Но если хотите – можете сделать массивом $country-> as_array();
Чудесная шпаргалка. И сразу вопрос по связи много-ко-многим. В промежуточной таблице связей можно хранить еще дополнительные данные (судя по методу add в доке по API http://kohanaframework.org/guide/api/ORM#add).
Так вот, каким образом можно обратиться к этим сохраненным данным?
Также вот в таком виде работает:
$country = ORM::factory(’country’)->find_all();
$genre = ORM::factory(’genre’)->find_all();
$this->template->content = View::factory(’films/reference’)
-> set(’country’, $country)
-> set(’genre’, $genre);
@ kadabrik:
Опа… не знал. Может кто из гуру подскажет?
Что то я не до конца понял о связи “МНОГО-КО-МНОГИМ”
У меня вот такие данные:
Таблица “Фильмы” – films
Таблица “Актеры” – casts
В каждом фильме играет несколько актеров, а каждый актер, соответственно, может сняться в нескольких фильмах.
Создал промежуточную таблицу со столбиками films_id и cast_id
Приведу пример:
Фильм “Матрица”, где снимались Нео, Матрица, Морфеус
В свою очередь каждый из них снимался в “Матрица”, “Матрица 2″ и “Матрица 3″
В моделях нужно писать:
protected $_has_many = array(’films’=> array(’model’ => ‘films’, ‘through’ => ‘films_casts’));
protected $_has_many = array(’cast’=> array(’model’ => ‘cast’, ‘through’ => ‘films_casts’));
А для таблицы films_casts тоже нужна модель? Если да, то что там прописать?
В контроллере видимо вот так будут описываться связи:
$film = ORM::factory(’film’);
$cast = ORM::factory(’cast’);
$film -> add(’cast’, $cast);
altesack пишет:
Я кажется нашел ответ на свой вопрос. Вот тут такой же вопрос и ответ на него (инглиш): http://www.questionhub.com/StackOverflow/1946357
@Вертекс исходя из моего вопроса и его решения – то прописывать модель для films_casts тебе нужно только если в промежуточной таблице используются дополнительные поля (до которых тебе надо добираться). Если нет – то модель не нужна, работу сделает сама Kohana.
Вместо ORM::factory(’article’,$id)->delete() удобнее ORM::factory(’article’)->delete($id) – так не выполняется ненужный SELECT.
Спасибо. Пофиксил.
Видимо на мой вопрос никто не знает ответа
Придется по старинке юзать SQL код
Здравствуйте! Хотел бы уточнить…
Вот код ORM::factory(’country’)->find_all();
Создает запрос вида SELECT * FROM country
А что делать если мне не нужны все поля? Если мне нужно всего 2 поля, а в таблице их будет 8 .. Зачем мне тянуть остальные ?
Возможно это как то решить?
Спасибо.
@ Jarek:
А в чём проблема? Сложность запроса зависит не столько от количества полей, сколько от количества перебираемых записей.
Или я не про то?
Про то )
Ну а возможно как-то тянуть только нужные мне поля ? зачем же мне все тянуть ?
Ну вот я нашел метод select он дает возможность вбирать поля.
Вот например:
ORM::factory(’article’)->select(’id’,'title’,'annotation’)->find_all()
Создает такой запрос :
“SELECT `id`, `title`, `annotation`, `articles`.* FROM `articles` ORDER BY `articles`.`id` ASC”
как мне убрать `articles`.* ?
есть идеи ?)
@ Jarek:
Встречный вопрос. Чем могут помешать лишние поля?
Что они могут ухудшить?
Разве запрос не становится сложнее ?
Если например я захочу выбрать 30-40 записей из базы, но при этом я использую только пару полей, зачем мне тянуть все остальное ? Запрос же будет тяжелее ?!
Или я не прав ?
Зы я не спец, если в чем то не прав поправьте меня…
неужели удаление можно совершить только через цикл?
значит ли это что, если я хочу удалить 2000 статей для $author – То будет совершено 2000 обращений к БД?
или можно каким-то образом указать что нужно удалить из таблицы Articles все записи у которых author_id равен определенному значению
$author = ORM::factory(’author’,$id);
$author2 = ORM::factory(’author’,$id2);
foreach ($author->articles->find_all() as $article){
$article->author=$author2;
// Или удалять
$article->delete();
}
$author->delete();
@ Igor:
в принципе порылся и нашел решение проблемы. Черта какого-то метод delete_all() из 3.1 удалили, тут вот есть решение, как я понял, подтянули метод из предыдущей версии фреймворка
http://forum.kohanaframework.org/discussion/8447/kohana-3.1-orm-i-save_all/p1
biakaveron пишет:
Это в какой версии было, что удаление так можно делать, сейчас в 3.1 такое невозможно, в методе удаления стоит проверка на загрузку модели (селект по PrKey), а потом только удаление использую загруженный ПК.
Короче никто так ничего и не ответил на счет select(’id’,’title’,’annotation’)
Вот выбираю я для новостей заголовки и дату и передаю в шаблон, так зачем мне действительно выбирать и передавать еще гору полей. То ли будет 2 поля, то ли 10, которые не используются. Разве это не жрет памяти?
@ Alexey:
Некоторый проигрыш в потреблении памяти и правда есть. Но проигрыш не такой чтобы бросаться словами наподобие “жрёт память”. В приложениях часто есть куча других узких мест, более критичных с точки зрения потребления памяти
Если логика запроса такова,что обязательно требуется только два поля в выборке – то может попробовать Query Builder?
Как раз написал пост про него http://blogocms.ru/2011/04/kohana-query-builder-pravila-xoroshego-tona/