Kohana 3.0. Шпаргалка по ORM

kohana Почти год назад я написал простенький мануал про ORM в Kohana, в котором затронул примитивные операции и совершенно не затронул работы со связями. И вот после целого года исканий я практически созрел и обрёл некий опыт? который спешу зафиксировать в виде этой шпаргалки.

Про инсталляцию и настройку ORM писать не буду. И так уже много раз писано.

СОГЛАШЕНИЯ ORM
===================================================
Я уже писал о соглашениях. На всякий случай повторюсь.

  • Названия таблиц должны быть во множественном числе по всем правилам английской грамматики. Например: users, posts, articles, cities.
  • Названия моделей должны быть в единственном числе. Например: user, post, article, city.
  • Таблица должна обязательно иметь автоинкрементное поле id

Немного холивара на эту тему
————————
На самом деле есть возможность не соблюдать соглашения по наименованиям, и есть способы переопределить названия и прочее. Но будет ли от этого легче потом разбирать код другому программисту, или даже вам самим?

Я отношусь к этому как к различным соглашениям по наименованию переменных. Вы можете их не придерживаться и называть переменные как вам взбредёт в голову. Но такая свобода может выйти боком.

Однако меня слегка понесло. Возвращаюсь к теме.

ТАБЛИЦА БЕЗ СВЯЗЕЙ
===================================================
В минимальном варианте достаточно создать модуль

<?php
Class Model_Article extends ORM {};
?>

Далее я описываю типичные операции с такими таблицами

Чтение записи
—————

$article = ORM::factory('article',$id);
echo $article->name;

//Или
echo ORM::factory('article',$id)->name;

Чтение нескольких записей
———————

$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;
}

Удаление записи
——————

$article = ORM::factory('article',$id);
$article->delete();
//или лучше 
ORM::factory('article')->delete($id);

Изменение записи
———————

$article = ORM::factory('article',$id);
$article->name="blablabla";
$article->save();

Создание записи
————————

$article = ORM::factory('article');  // Просто нигде не указываем ID
$article->name="blablabla";
$article->save();

Примечание. В обоих случаях для сохранения записи используется процедура save().
Люди говорят, что в Kohana3.1 за создание нового отвечает процедура create(), а за сохранение update(). А save() убирают совсем.

Kohana3 ORM. Связь много-к-одному
ТАБЛИЦА СО СВЯЗЬЮ МНОГО-К-ОДНОМУ
===================================================
Чаще всего такая связь реализуется с помощью создания внешнего ключа в одной из таблиц. Причём одна из таблиц имеет связь “много-к-одному”, другая “один-ко-многим”.

Для таблицы, которая имеет связь “много-к-одному” используют поле $_belongs_to и создают примерно такую модель:

<?php
Class Model_Article extends ORM {
protected $_belongs_to = array(
    'author'    => array(
       'model'         => 'aurhor',
       'foreign_key' => 'author_id',
    )
    );

};
?>

Далее я описываю типичные операции с такими таблицами

Чтение связи
———————————
Когда связь задана и прописана в базе, чтение становится банальным чтением поля.

$article = ORM::factory('article',$id);
echo $article->author->name;

Причём здесь $article->author — это такая же полноценная модель, как и собственно $article. Над ней тоже доступны все описанные в этом топике операции.

Изменение связи
———————————

$article = ORM::factory('article',$id1);
$article->author= ORM::factory('author',$id2);
$article->save();

Просто меняем поле. Не забываем сохранять.

Kohana3 ORM. Связь один-ко-многим
ТАБЛИЦА СО СВЯЗЬЮ ОДИН-КО-МНОГИМ
===================================================
Эта ситуация противоположна рассмотренной в предыдущей ситуации. Для таблицы, которая имеет связь “один-ко-многим” используют поле $_has_many и создают примерно такую модель:

<?php
Class Model_Author extends ORM {
   protected $_has_many = array(
      'articles'    => array(
                   'model'         => 'article',
                   'foreign_key' => 'author_id',
               )
    );
};
?>

Прочитать связь
———————-
У одного автора может быть много статей.

$author = ORM::factory('author',$id);
foreach ($author->articles->find_all() as $article){
    echo $article->title;
}

Удаление записи из таблицы.
————————————-
Если просто удалить запись, то в соответствующей таблице останутся записи с зависшими связями. Это собственно нарушение целостности базы данных. Это очень плохо. При удалении записи нужно определиться что делать с соотвествующими записями.

Например если удаляем автора то, что делаем со статьями? Либо присваивать им другого автора, либо их тоже удалять.

$author = ORM::factory('author',$id);
$author2 = ORM::factory('author',$id2);

foreach ($author->articles->find_all() as $article){
    $article->author=$author2;
    // Или удалять
    $article->delete();
}
$author->delete();

Нужно помнить, что у статьи тоже могут быть довольно сложные правила удаления, и общая схема действий может быть довольно сложной

Поскольку все эти телодвижения имеют гораздо большее отношение к базе данных нежели к бизнес-логике, то разумно поместить этот алгоритм как функцию прямо в модель.

Kohana3 ORM. Связь многие-ко-многим
ТАБЛИЦА СО СВЯЗЬЮ МНОГО-КО-МНОГИМ
===================================================
В отличие от предыдущего варианта тут нужна промежуточная таблица. И в отличие от предыдущего примера ситуация симметричная

Как и в предыдущем примере используется $_has_many, но уже не по внешнему ключу (foreign_key), а через таблицу (through)

Приведём пример одной модели. Другая модель будет похожей

<?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 так:

'tagged_images'  => array(
         'model'=> 'image', 
         'foreign_key' => 'user_id',
         'through'      => 'users_images', 
         'far_key'      => 'image_id',   
),

Создание связи
————————
Создание связи на низком уровне означает создание записи в промежуточной таблице, содержащей ссылки на обе связанные записи.

А на ORM это делается с помощью функции add

$tag =ORM::factory('tag',$id1);
$article =ORM::factory('article',$id2);
$article->add('tags',$tag);

Если эта связь также прописана в модели Model_Tag, а это советую обязательно сделать, то можно было бы написать и так

$tag =ORM::factory('tag',$id1);
$article =ORM::factory('article',$id2);
$tag->add('articles',$article);

В конце концов результат будет один и тот же — запись в промежуточной таблице.

Удаление связи
———————-
Удаление выполняется с помощью команды remove

$article =ORM::factory('article',$id2);
$tag =ORM::factory('tag',$id1);
// А можно и так
$tag =$articles->tags->where->('id','=',$id1)->find();

// А потом удалять
$article->remove('tags',$tag);

Доступ к связанным данным
—————————————
Всё как и в прошлом примере. Просмотр всех статей с заданным тегом:

$tag = ORM::factory('tag',$id);
foreach ($tag->articles->find_all() as $article){
    echo $article->title;
}

Удаление записи из таблицы
————————
При удалении записи опять таки нужно следить за целостностью данных. Тем более, что связи представляют собой отдельные записи в специальной таблице.

Эти связи можно также удалить,

$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()

Вот как-то так. Надеюсь дельная шпаргалка вышла.

Kohana 3.0. Шпаргалка по ORM: 24 комментария

  1. Дмитрий

    Лаконично. Даже красиво. Спасибо =)

  2. xproblem

    У вас ошибочка в начале. cities — множественное число, а city — единственное =)

  3. altesack Автор записи

    @ xproblem:
    Спасибо, исправил.

  4. Вертекс

    Пробую освоить 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 — не массив что ли ?

  5. altesack Автор записи

    Неа, не массив :)
    Но если хотите — можете сделать массивом $country-> as_array();

  6. kadabrik

    Чудесная шпаргалка. И сразу вопрос по связи много-ко-многим. В промежуточной таблице связей можно хранить еще дополнительные данные (судя по методу add в доке по API http://kohanaframework.org/guide/api/ORM#add).

    Так вот, каким образом можно обратиться к этим сохраненным данным?

  7. Вертекс

    Также вот в таком виде работает:

    $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);

  8. altesack Автор записи

    @ kadabrik:
    Опа… не знал. Может кто из гуру подскажет?

  9. Вертекс

    Что то я не до конца понял о связи «МНОГО-КО-МНОГИМ»

    У меня вот такие данные:
    Таблица «Фильмы» — 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);

  10. kadabrik

    altesack пишет:

    @ kadabrik:
    Опа… не знал. Может кто из гуру подскажет?

    Я кажется нашел ответ на свой вопрос. Вот тут такой же вопрос и ответ на него (инглиш): http://www.questionhub.com/StackOverflow/1946357

    @Вертекс исходя из моего вопроса и его решения — то прописывать модель для films_casts тебе нужно только если в промежуточной таблице используются дополнительные поля (до которых тебе надо добираться). Если нет — то модель не нужна, работу сделает сама Kohana.

  11. biakaveron

    Вместо ORM::factory(‘article’,$id)->delete() удобнее ORM::factory(‘article’)->delete($id) — так не выполняется ненужный SELECT.

  12. Вертекс

    Видимо на мой вопрос никто не знает ответа :(
    Придется по старинке юзать SQL код :(

  13. Jarek

    Здравствуйте! Хотел бы уточнить…
    Вот код ORM::factory(’country’)->find_all();

    Создает запрос вида SELECT * FROM country
    А что делать если мне не нужны все поля? Если мне нужно всего 2 поля, а в таблице их будет 8 .. Зачем мне тянуть остальные ?
    Возможно это как то решить?

    Спасибо.

  14. Altesack

    @ Jarek:
    А в чём проблема? Сложность запроса зависит не столько от количества полей, сколько от количества перебираемых записей.

    Или я не про то?

  15. Jarek

    Про то )

    Ну а возможно как-то тянуть только нужные мне поля ? зачем же мне все тянуть ?

  16. Jarek

    Ну вот я нашел метод select он дает возможность вбирать поля.
    Вот например:

    ORM::factory(‘article’)->select(‘id’,’title’,’annotation’)->find_all()

    Создает такой запрос :

    «SELECT `id`, `title`, `annotation`, `articles`.* FROM `articles` ORDER BY `articles`.`id` ASC»

    как мне убрать `articles`.* ?
    есть идеи ?)

  17. Altesack

    @ Jarek:
    Встречный вопрос. Чем могут помешать лишние поля?
    Что они могут ухудшить?

  18. Jarek

    Разве запрос не становится сложнее ?
    Если например я захочу выбрать 30-40 записей из базы, но при этом я использую только пару полей, зачем мне тянуть все остальное ? Запрос же будет тяжелее ?!
    Или я не прав ?

    Зы я не спец, если в чем то не прав поправьте меня…

  19. Igor

    неужели удаление можно совершить только через цикл?
    значит ли это что, если я хочу удалить 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();

  20. Максим

    biakaveron пишет:

    Вместо ORM::factory(’article’,$id)->delete() удобнее ORM::factory(’article’)->delete($id) – так не выполняется ненужный SELECT.

    Это в какой версии было, что удаление так можно делать, сейчас в 3.1 такое невозможно, в методе удаления стоит проверка на загрузку модели (селект по PrKey), а потом только удаление использую загруженный ПК.

  21. Alexey

    Короче никто так ничего и не ответил на счет select(’id’,’title’,’annotation’)
    Вот выбираю я для новостей заголовки и дату и передаю в шаблон, так зачем мне действительно выбирать и передавать еще гору полей. То ли будет 2 поля, то ли 10, которые не используются. Разве это не жрет памяти?

  22. altesack Автор записи

    @ Alexey:
    Некоторый проигрыш в потреблении памяти и правда есть. Но проигрыш не такой чтобы бросаться словами наподобие «жрёт память». В приложениях часто есть куча других узких мест, более критичных с точки зрения потребления памяти

    Если логика запроса такова,что обязательно требуется только два поля в выборке — то может попробовать Query Builder?

    Как раз написал пост про него http://blogocms.ru/2011/04/kohana-query-builder-pravila-xoroshego-tona/

Комментарии запрещены.