Kohana для чайников. Простейший ORM



kohanaВ прошлый раз я писал о том как настроить фреймворк Kohana для работы с базами данных. Сегодня я постараюсь немного осветить саму работу с БД.

Там же, в прошлом посте, в качестве теста был приведён простейший пример запроса к серверу БД прямо в контроллере. Такая работа есть моветон, хотя и возможна. Для читаемости, расширяемости кода следует стараться придерживаться архитектуры MVC, в которой принято всю работу с данными выносить из контроллера в модели.

В Kohana работу с СУБД можно реализовать несколькими способами

  • Писать SQL-запросы вручную используя метод query(). Это даёт большую гибкость при написании, но много рутины. Требуется ручная проверка вводимых данных на предмет вредоносных включений
  • Использовать Query Builder. Он позволяет строить запросы независимые от конкретной СУБД. Кроме того они автоматически проверяются. Работает аналогично Active Record в CodeIgniter
    1
    2
    3
    4
    5
    6
    7
    8
    
    $query = $this->db->select() 
                ->where('id', 3)
                ->from('products')
                ->get();
    foreach ($query as $row)
    {
        echo $row['name'];
    }
  • Использовать ORM. Чем собственно я и попытаюсь заняться в этом посте

Зачем использовать ORM
—————————————-
ORM (Object Relational Mapping) позволяет манипулировать данными обращаясь к полям таблиц как к свойствам объекта. Это позволяет порой радикально уменьшить количество кода, а соответственно уменьшается количество ошибок и улучшается читаемость/расширяемость.

Однако в некоторых случаях ORM оказывается слабым средством. Особенно, когда нужно составлять сложные аналитические запросы. В таких случаях стоит использовать другие средства.

Соглашения ORM
—————————————-

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

Там есть ещё и другие соглашения, но они касаются связей таблиц друг с другом. Мы пока не будем этим пользоваться.

Итак у нас есть таблица

1
2
3
4
5
6
CREATE TABLE `articles` (
  `id` int(11) NOT NULL auto_increment,
  `title` varchar(255) NOT NULL,
  `text` text NOT NULL,
  PRIMARY KEY  (`id`)
) ENGINE=MyISAM DEFAULT CHARSET=utf8;

Соответственно, модель должна называться article.

Простейший пример ORM. Kohana2
—————————————-

Создаём файл модели application/models/article.php

1
2
3
<?php
Class Article_Model extends ORM {};
?>

В принципе всё готово осталось только использовать. Например, можно вытащить список статей:

1
2
3
4
5
$articles = ORM::factory('article');
foreach ($articles->find_all() as $article)
{
    echo $article->title;
}

А можно в контроллере вытащить конкретную запись из таблицы

1
2
$article = ORM::factory('article',1);
echo $article->title;

Можно изменить запись

1
2
3
4
$article = ORM::factory('article',1);
$article->title= "Новый заголовок";
$article->text= "Новый текст";
$article->save();

Можно создать новую запись

1
2
3
4
$article = ORM::factory('article');  //просто не указываем код
$article->title= "Новый заголовок";
$article->text= "Новый текст";
$article->save();

Список возможностей этим не ограничивается, но мы остановимся. Мы попробуем создать контроллер для создания sitemap.

Создадим файл контроллера application/controllers/sitemap.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<?php
class Sitemap_Controller extends Controller{
 
    public function index(){
        header("Content-Type: text/xml;charset=utf8");
 
        $articles = ORM::factory('article');
 
        $view=new View('sitemap');
        $view->articles=$articles;
        $view->render(true);
    }
}
?>

Создадим вид application/view/sitemap.php:

1
2
3
4
5
6
7
8
9
10
11
12
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
 
<?php foreach ($articles->find_all() as $article): ?>
                <url>
                <loc><?php echo url::base()."mycontroller/article/".$article->id; ?></loc>
                <changefreq>monthly</changefreq>
                <priority>0.8</priority>
                </url>
<?php endforeach ?>
 
</urlset>

Вот впринципе и всё. Работает.

Простейший пример ORM. Kohana3
—————————————-
То же самое, но немного по другому.

Модель application/classes/model/article.php:

1
2
3
<?php
Class Model_Article extends ORM {};
?>

Контроллер application/classes/controllers/sitemap.php:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<?php
class Controller_Sitemap extends Controller {
 
    public function action_index(){
        $this->request->headers['content-type'] = 'text/xml;charset=utf8';
 
        $articles = ORM::factory('article');
        $view=new View('sitemap');
        $view->articles=$articles;
 
        echo $view;
    }
 
}
?>

Вид application/views/sitemap.php:

1
2
3
4
5
6
7
8
9
10
11
12
<?php echo'<?xml version="1.0" encoding="UTF-8"?>'?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
 
<?php foreach ($articles->find_all() as $article): ?>
                <url>
                <loc><?php echo url::base()."mycontroller/article/".$article->id; ?></loc>
                <changefreq>monthly</changefreq>
                <priority>0.8</priority>
                </url>
<?php endforeach ?>
 
</urlset>

Готово.

Послесловие
——————–
Рассмотренный нами случай ORM – простейший за счёт отсутствия связей между таблицами. Обычно модель выглядит сложнее.

PS: Чорд! Забыл сказать, чтобы включить модуль ORM в Kohana3 нужно в файле application/bootstrap.php раскомментировать соответствуюющую строчку:

1
2
3
4
5
6
7
8
9
10
11
12
/**
 * Enable modules. Modules are referenced by a relative or absolute path.
 */
Kohana::modules(array(
	// 'auth'       => MODPATH.'auth',       // Basic authentication
	// 'codebench'  => MODPATH.'codebench',  // Benchmarking tool
	'database'   => MODPATH.'database',   // Database access
	// 'image'      => MODPATH.'image',      // Image manipulation
	'orm'        => MODPATH.'orm',        // Object Relationship Mapping
	// 'pagination' => MODPATH.'pagination', // Paging of results
	// 'userguide'  => MODPATH.'userguide',  // User guide and API documentation
	));

Посты по теме:

  1. Kohana для чайников. Простейший роутинг
  2. Kohana для чайников. Настраиваем базу данных
  3. Kohana для чайников. Работа с шаблонами.
  4. Kohana для чайников. Инсталляция.
  5. Kohana для чайников. Hello world.

Категории Kohana |
автор: altesack / Понедельник, Январь 18, 2010 / 30 комментов »

30 комментов

    Подсказка:
    public function action_index(){
    $this->request->headers['content-type'] = ‘text/xml;charset=utf8′;

    Автор, расскажите пожалуйста подробнее про связи между таблицами. Очень интересно почитать.
    Спасибо за сайт!!!

    @ Аркатов Дмирий:
    Да!!!! Это то что нужно!!
    @ Максим:
    Планирую обязательно написать, когда сам достигну достаточного просветления в этом вопросе.

    @altesack Огромное Спасибо за материал!!!

    Тренируясь, обнаружил в ОRM метод load, пример:

    $articles = ORM::factory(’article’);
    $articles->load ()

    Подскажите, пожалуйста, для каких он целей?

    Архитектурно правильнее было бы наверно выбрать статьи из БД в контроллере, а не в представлении, а в представление передавать уже результат. Типа так: $view->articles=ORM::factory(’article’)->find_all()

    Я правильно мыслю?

    @ Nayjest:
    Кстати нужно попробовать. Да, так выглядит правильнее.
    @ Atures:
    Я пока не могу ответить на этот вопрос, потому что пока немного плаваю в Kohana, а ещё больше в ORM.

    А она Вам так сильно нужна? :)

    @altesack
    Не сильно, просто интересно ))) Возможно кто-то из комментирующих пользователей подскажет.

    Модель application/classes/models/article.php:
    Здесь application/classes/model/article.php

    Вид application/view/sitemap.php:
    Здесь application/views/sitemap.php:

    Я про kohana 3

    @ KotDev:
    Точно! Опечатка. Исправляюсь. Спасибо :)

    Доброго времени суток, в коде создания таблицы, после `text` text NOT NULL не стоит запятая.
    С уважением Евгений

    Подскажите пожалуйста, в кохане 3 у меня выдает ошибку на строчке:
    $this->request->headers['content-type']=‘text/xml;charset=utf8′;
    1.{PHP internal call} » Kohana_Core::shutdown_handler(arguments)
    На что он ругается?

    @ Евгений:
    Спасибо поправил.
    Насчёт ошибки – может перед вызовом инструкции на вывод уже было что-то отправлено?

    1. все равно выдает ошибку на 5 строчке, $this->request->headers['content-type']=‘text/xml;charset=utf8′;
    говорит что ErrorException [ Parse Error ]: syntax error, unexpected ‘=’
    2. я так и не понял пути какие в третьей кохане писать view или views, также model или models?

    p.s. Эта сфера для меня нова :)

    @ Евгений:
    Похоже, что у вас в ‘text/xml;charset=utf8′ неправильные кавычки

    @ altesack:
    Да, закрывающая кавычка в строчке: ‘text/xml;charset=utf8′ неправильная. Как только перебил ее вручную, сразу все заработало. Просьба изменить ее в коде тоже.
    Теперь появилась другая ошибка, только уже в строчке:

    пишет что syntax error, unexpected T_STRING. Опять видно очепятки в синтаксисе. Что это может быть?

    извеняюсь, вставил код вместо текста, строчка на которую ругается кохана вот: …xml version=”1.0″ encoding=”UTF-8″… в файле APPPATH/views/sitemap.php

    @ Евгений:
    Опечатку справил.
    Новую ошибку тоже. Заменил первую строчку.

    @ altesack:
    Спасибо за оперативность. После всех мыканий заработало, но выдало такую фразу:
    Ошибка синтаксического анализа XML: объявление XML или текста не в начале сущности
    Адрес: http://mysite.lan/sitemap
    Строка 4, символ 1:
    ^
    Это тот результат, что должен получится?

    похоже что в этом файле application/views/sitemap.php ругается на синтаксис. Еще я заметил что в коде путь написан “mycontroller/article/” а не “controller/article/” Ведь папку вроде не изменяли…

    @ Евгений:
    mycontroller – имеется в виду название Вашего контроллера, который должен быть в ссылке на сайтмапе.

    Class Controller_Welcome extends Controller_Template
    {
    public $template = ‘welcome’;

    function action_index()
    {
    $this->request->headers['content-type'] = ‘text/xml;charset=utf8′;

    $articles = ORM::factory(’article’);
    $this->template->articles = $articles->find_all();
    }
    }

    <?php echo'’?>

    id; ?>
    monthly
    0.8

    блин, как же трубно переходить с CakeHP на кохану или кодожжотер. Всё как-то недо… приходится писать то,что кейк за меня делал.

    @ Alex:
    А кейк ещё жив?

    еще как жив!

    Alex пишет:

    еще как жив!

    интересно чем же вас не устраивает кейк

    интересно чем же вас не устраивает кейк

    Всмысле “не устраивает”?? Почему на нём не пишу? Потому, что не знаю =)
    Когда-то, когда принимал решение, за какой фреймворк садиться, кейк по сравнительным отзывам показался мне более трудным и старым (устаревшим).
    Это было сугубо субъективное ощущение.

    Тоесть получается что создав модель наследуя класс ORM мы все действия по записи данных и выдергиванию переносим в контроллер? Тоесть например добавление статьи в базу происходит так?
    class Sitemap_Controller extends Controller{

    public function action_add(){
    header(”Content-Type: text/xml;charset=utf8″);

    $articles = ORM::factory(’article’);
    $articles->title = “dsdsd”;
    $articles->text = “dsdsd”;
    $articles->save();
    ……………………..
    }
    }
    Так?

    Ну примерно так, да. Кстати можно и не выносить в контроллер. Можно тот же метод создать в модели. Я в последнее время так стараюсь делать.

    Еще вопрос у тойже таблицы articles есть связь с таблицей tags многим ко многим. В представляение я передаю данные так:
    $posts = ORM::factory(’post’)->find_all(10);
    $this->template->content = View::factory(’pages/posts’, array(’posts’=>$posts));
    А в самом представлении обрабатываю так:
    foreach($posts as $post){
    echo $post->title;
    ….
    foreach($post->tags->find_all() as $tag){
    echo $tag->value.’ “;
    }
    }
    Появляется соответсвенно вопрос а правильно ли что в представлении идет работа с данными? тоесть $post->tags->find_all() ??
    А еще можно Джаббер или еще какойни будь видь связи с тем кто разобрался.. Просто возникают мелкие недопонимания и хочется знать как сделать правильнее!

Ответить