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

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

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

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

  • Писать SQL-запросы вручную используя метод query(). Это даёт большую гибкость при написании, но много рутины. Требуется ручная проверка вводимых данных на предмет вредоносных включений
  • Использовать Query Builder. Он позволяет строить запросы независимые от конкретной СУБД. Кроме того они автоматически проверяются. Работает аналогично Active Record в CodeIgniter
    $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

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

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

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

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

$articles = ORM::factory(‘article’); foreach ($articles->find_all() as $article) { echo $article->title; }

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

$article = ORM::factory(‘article’,1); echo $article->title;

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

$article = ORM::factory(‘article’,1); $article->title= «Новый заголовок»; $article->text= «Новый текст»; $article->save();

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

$article = ORM::factory(‘article’); //просто не указываем код $article->title= «Новый заголовок»; $article->text= «Новый текст»; $article->save();

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

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

articles=$articles; $view->render(true); } }

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

foreach ($articles->find_all() as $article):
echo url::base().»mycontroller/article/».$article->id;
monthly
0.8
endforeach

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

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

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

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

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:

echo»?>

foreach ($articles->find_all() as $article):
echo url::base().»mycontroller/article/».$article->id;
monthly
0.8
endforeach

Готово.

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

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

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

Штатная валидация в Kohana3

Недавно разобрался со стандарной библиотекой валидации форм фреймворка Kohana3. Это оказалось не совсем тривиально, и уж точно не так как в CodeIgniter. По Kohana2 имелась достаточно хорошая документация, но поскольку многое изменилось — изучать её новичку не имеет смысла. По Ko3 мануал тоже есть, но на данный момент явно не совсем полный и писанный на коленке. Или я не умею читать?

Вот что у меня в конце концов получилось (Проверялось на версии 3.0.4.1):

Краткий обзор объекта Validate
========================
Объект Validate содержит много всяких методов и свойств, но главные свойства, отвечающие за информацию о проверяемых полях следующие:

  • labels — человекопонятные названия полей. Очень приятно, когда пользователю выводится ошибка понятная простому человеку.
  • filters — предварительная обработка полей перед проверкой на правила. Можно подставлять обыкновеные PHP-функции. Обычно сюда ставят TRIM.
  • rules — собственно правила. Список возможных правил можно посмотреть в официальной документации
  • callbacks — массив, в который можно добавить свою обработку, если не хватает возможностей filters и rules. Эта процедура будет иметь полный доступ к свойствам объекта Validate и помимо хитрой обработки сама должна генерировать сообщения об ошибках. Подробно не изучал, врать не буду

Для добавления вышеописанных сущностей используются функции label(), filter(), rule(), callback(). Пример использования будет ниже.

Для собственно проверки используется метод check(), которая возвращает логический результат.

Функция errors() по замыслу возвращает массив строк с ошибками, произошедшими при проверке. Если указать в качестве аргумента имя файла со специфичными для нашей проверки сообщениями (должно находиться в папке application/messages) то библиотека попытается считать этот файл. Если не указывать имя файла — будет возвращён массив с правилами, в которых произошла ошибка.

Любопытно, но для того, чтобы получить стандартные сообщения об ошибках нужно вызвать функцию errors, указав имя несуществующего файла сообщений. По крайней мере я другого способа не нашёл.

Чтобы русифицировать штатные сообщения можно создать свой файл application/messages/validate.php примерно следующего содержания:

defined(‘SYSPATH’) or die(‘No direct script access.’);
return array( ‘not_empty’ => ‘Поле :field не должно быть пустым’, ‘matches’ => ‘Поле :field должно совпадать с полем :param1’, ‘regex’ => ‘Поле :field не соответствует необходимому формату’, ‘exact_length’ => ‘Поле :field должно иметь длину :param1 символов’, ‘min_length’ => ‘Поле :field должно иметь длину как минимум :param1 символов’, ‘max_length’ => ‘Поле :field должно быть в длину не более чем :param1 символов’, ‘in_array’ => ‘Поле :field должно быть одной из возможных опций’, ‘digit’ => ‘Поле :field должно цифрой’, ‘decimal’ => ‘Поле :field должно быть десятичным числом с :param1 знаками’, ‘range’ => ‘Поле :field должно быть в диапазоне от :param1 до :param2′, ’email’ => ‘Поле :field должно содержать реальный e-mail’, );

Пример валидации
========================
Например мы делаем форму восстановления пароля и отправки его на почту

public function action_passwordrecovery() { $post = Validate::factory($_POST) ->label(’email’,’E-mail’) // Название поля
->filter(email, ‘trim’) // Перед проверкой правил выполняем действие trim
->rule(’email’, ‘not_empty’) // Поле email не должно быть пустым ->rule(’email’, ’email’); // Поле email должно содержать валидный адрес
if ($post->check()) { // Проверка прошла успешно, выполняем нужное действие echo «Ваш email: «.$post->email; }else{ $data[‘errors’]=$post->errors(»); // Получаем массив строк с ошибками. // Обратите внимание на аргумент $this->template->content = new View(‘passwordrecovery’,$data); // Подключаем вьюс в котором разворачиваем и выводим массив $errors }

В догонку
========================
В примере выше показана проверка данных присылаемых формой. Но таким же образом можно организовать валидацию любого массива в любом месте. Многие делают валидацию в моделях ORM. Такой подход позволяет делать одну стандартную проверку на несколько различных действий с данными, такими как добавление, изменение и прочее, что только можно себе представить.

Ссылки
========================

  • http://v3.kohanaphp.com/guide/security.validation — официальная документация Ko3
  • http://docs.kohanaphp.com/libraries/validation — официальный мануал по Ko2. Имеет смысл прочитать для общего понимания принципа работы, не более

Как сбросить пароль root в MySQL

Начинаю серию шпаргалок по администрированию серверов LAMP. Надоело гуглить, и решил собрать всё в одну кучу.

Пароль рута MySQL не известен совсем? Такое может быть, если установили систему из какой-нибудь готовой сборки. Что делать?

Как войти без пароля?
==============================
Надо сначала запустить MySQL с опцией. Опцию можно указать либо в командной строке

$ mysqld_safe –skip-grant-tables

Либо в конфиг файле в секции описания сервера

[mysqld]
skip-grant-tables

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

Как поменять пароль?
===============================
После того как зашлиа дальше дело техники

use mysql; UPDATE user SET Password = PASSWORD(«password») WHERE User = «root»;

После этого незабудьте перезагрузить сервер в нормальном режиме.

Kohana для чайников. Файловая структура, или история одной ошибки.

Во всех нормальных мануалах Kohana3 (например тут), практически в самом начале, размещена красивая схема структуры файлов фреймворка.

Конечно же это баян, но тем не менее галопом пробегусь по содержимому.
В Kohana есть три главные папки:

  • application — папка конкретного приложения, в которой разработчик хозяйничает сам
  • modules — папка модулей, в которой хозяйничают разработчики сторонних модулей
  • kohana — ядро фреймворка, в котором хозяйничают разработчики ядра

Структура файлов этих трёх папок примерно одинакова. Есть небольшие различия, но не в них суть

Главная суть в том, что написанный вами класс можно разместить в любой их этих папок! Только нужно помнить о приоритетах поиска.

При обращении к классу сначала будет выполнен поиск класса в папке application, затем в modules, затем в kohana.

Зачем это сделано
================

Поскольку фреймворк является полностью открытым продуктом, его можно изучать и модифицировать полностью самостоятельно. Однако, при обновлении ядра все ваши изменения слетят. Логично складывать обновления в той области, где вы сами себе хозяин — в папке application например.

Например если вас не устраивает базовый класс фреймворка — не спешите его исправлять прямо на месте. Со следующим обновлением ваше исправление улетит в тартарары. Лучше скопируйте этот файл в аналогичное место, но в каталоге application. И поскольку application имеет больший приоритет при поиске класса — ваш файл будет использован, а родной класс фреймворка будет проигнорирован.

История одной ошибки
==============================
Ох ведь не зря этот пункт документации разместили прям в самом начале! Они знали что я об него споткнусь, не смотря на всю его простоту:

Я в своём приложении использую модуль Auth, он в свою очередь использует таблицу users и свою довольно навороченную модель modules/auth/classes/model/user . Всё у меня было хорошо, пока я не захотел собственноручно залезть в таблицу users и создал, как положено, свою простую ORM-модель application/classes/model/user.

Своей цели я может и достиг, но из-за приоритета папки application модуль Auth теперь начал использовать мою модель для работы с таблицей. А в ней не было кучи функций. Вот тут-то я и огрёб кучу ошибок, которую не мог разобрать недели две.

А ведь и свою модель можно было не создавать, а спокойно использовать модель модуля Auth!

Ещё раз убедился, что банальные вещи очень часто оказываются очень важными.

На этом заканчиваю баянистую тему по поводу очередной банальности =)

OpenX — движок для банерной сети

Если у вас есть посещаемый проект, значит вас уже посещало желание зарабатывать на показах банеров на нём. Но тут возникают технические нюансы. Например, вы верстаете макет сайта, конечно же оставляя место для банеров. Что вы туда подставите? Некий статичный HTML-код?

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

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

Инсталляция
=============================

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

  1. Сначала читаем условия соглашения и соглашаемся.
  2. Далее нужно зарегистрироваться на OpenX.org.Самый непонятный шаг. Я так понял, что это такая политика монетизации движка. На самом деле, регистрация на сервере не обязывает ни к чему. Просто это позволяет добавить ваши площадки и ваших рекламодателей к общей базе OpenX Market.Думаю большого толка от этой фишки нет, и поэтому её придётся просто терпеть.
  3. Далее указываем параметры MySQL. Кроме обычных логинов и паролей можно ввести довольно интересные настройки, например префикс таблицы.Чтобы увидеть эти настройки нужно нажать ссылку See more database fields
  4. Указываем логины-явки-пароли администратора.Самый трепетный момент — это выбор языка.Нет, русский язык тут есть. И вроде даже перевод не машинный. Но всё-таки почему-то перевод оставляет ощущение недоумения.А ведь в системе такое обилие хитрых настроек с тонкими нюансами, что малейшая шероховатость в переводе опций может только помешать.

    Короче мой выбор — английский, чего и вам советую

  5. Собственно финиш. Конец.

Кроме этого нужно проверить доступы папок, в которые будут закачиваться банеры
/www/images/ и /var

После этого система готова к работе

Интерфейс вебмастера
=============================

Для добавления в систему сайта в качестве банероносителя нужно зайти в Inventory -> Websites и выбрать там пункт Add new website.

После этого у этого сайта нужно так же добавить зону. Зона — это ни что иное, как место в макете для показа. При добавлении зоны нужно указать размер графического банера. А можно выбрать текстовый банер или видео. Выбор настолько большой, что я даже не всё понял

После добавления зоны нужно получить HTML-код для размещения на странице.
Всё.

Интерфейс рекламодателя
=================================

Интерфейс рекламодателя реализован аналогично с интерфейсом вебмастера. Inventory -> Advertisers добавляем рекламодателей, затем у рекламодателя добавляем кампанию, и у каждой кампании добавляем банер. Банеры, как мы уже говорили, могут не просто быть разных размеров, но и разных медиа.

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

Пользователи
=============================
Всё что мы проделали можно делать из-под администратора. И если стоит задача управления только своими сайтами и своими банерами, то этого достаточно. Но также можно завести отдельного пользователя для управления рекламными кампаниями конкретного рекламодателя.

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

Общие впечатления
=============================

  1. Админка тяжеловата, очень много элементов а-ля Jquery.
  2. На главной странице можно подобрать свой набор виджетов. Я например выключил все, связанные с OpenX.org.
  3. Статистика показов обновляется с задержкой. Величину задержки оценить не смог, к сожалению

Но это как в анекдоте — «Вам шашечки или ехать?» =)

Kohana3.0 ORM. Связь «один-к-одному»

Не так давно я писал пространную шпаргалку по работе с ORM. В той шпаргалке я решил пропустить связь «один-к-одному», видимо считая, что она никогда в жизни никому не понадобится, а если понадобится — то там делать не фиг, всё и так само собой сделается. Как это часто бывает — я ошибся.

Дело в том, что две связанные таблицы с такой связью логически можно просто объединить в одну таблицу. А раз есть такое мощное средство как ORM, в котором есть обработка такой связи, я уже размечтался было работать с такой связью как с одной таблицей. Но нет. Дудки. Всё вручную.

Итак, детали в студию.

Пример БД
===========================

По организации базы данных связь «один-к-одному» реализуется так же как и в связи «один-ко-многим». Точно также заводится внешний ключ, только договариваются на каждую запись в главной таблице заводить не более одной записи в подчинённой.

В нашем примере это таблица пользователей и таблице информации о пользователях. В нашем случае объединение таблицы в одну было бы логично, но главная таблица используется модулем Auth, поэтому её лучше не трогать (хотя наверное лучше тронуть её..).

Описание связи
===========================
Модель создаётся довольно просто

defined(‘SYSPATH’) or die(‘No direct access allowed.’); class Model_User extends Model_Auth_User {
protected $_has_one = array( ‘userinfo’ => array( ‘model’ => ‘userinfo’, ‘foreign_key’ => ‘user_id’ ) ); } // End User Model

Также обязательно нужно создать модель связанной таблицы.

defined(‘SYSPATH’) or die(‘No direct access allowed.’); class Model_Userinfo extends Orm {
protected $_belongs_to = array( ‘user’ => array( ‘model’ => ‘user’, ‘foreign_key’ => ‘user_id’ ) ); } // End User Model

Обратите внимание, что связь объявлена как $_belongs_to. Кстати, если не собираетесь из подчинённой таблицы лазить в главную можно эту связь тут не описывать. Достаточно пустой модели.

Чтение свойств
===========================
Чтение свойств делается средствами ORM просто замечательно.

$a=$user->userinfo->info1;

Пожалуй чтение — это единственное, что в нашем случае делается замечательно средствами ORM. Дальше всё сложнее.

Удаление записи
===========================
Удаление собственно записи из таблицы users само по себе не влечёт удаления записи из таблицы userinfos. Если конечно у вас не настроено удаление средствами СУБД по внешнему ключу.

В противном случае нужно удалять обе записи вручную. Иначе останется мусор.

$user ->userinfo->delete(); $user ->delete();

Редактирование записи
===========================
Сохранять нужно тоже обе записи отдельно.

$user=ORM::Factory(‘user’,$id);
$user->username = $username;
$user->userinfo->info1 = $a; $user->userinfo->info2 = $b; $user->userinfo->info3 = $c;
$user->save(); $user->userinfo->save();

Создание записи
===========================
И создавать нужно отдельно

$user=ORM::Factory(‘user’); $user->username = $username; $user->save();
$userinfo=ORM::Factory(‘userinfo’); $userinfo->user_id = $user->id; // После сохранения $user значение $user->id определено $userinfo->info1 = $a; $userinfo->info2 = $b; $userinfo->info3 = $c; $userinfo->save();

Вместо резюме
=========================
Вот такие пироги с котятами. Существенное усложнение кода, ухудшение его читаемости ..

А что делать — любая связь в БД требует определённых телодвижений. И порой задумаешься: может лучше избавиться от связи и свести всё в одну таблицу?

MySQL: Забавные находки UPDATE+ORDER

Вот уж не думал что в операторе UPDATE можно использовать ключевое слово ORDER. И никогда не думал, что такое мне пригодится!

Вот пример. Мне нужно сделать такой запрос:

UPDATE t SET id = id + 1;

Читать далееMySQL: Забавные находки UPDATE+ORDER

Использование iBrowser для TinyMCE опасно!

На всякий случай для тех кто не в теме, TinyMCE — это популярный wysiwyg-редактор для вебсайтов, а ibrowser — это плагин для него, позволяющий загружать изображения (да и просто файлы) на сайт, удалять уже загруженные и т.д. Короче файл-менеджер, причём бесплатный.

Может я зря порю горячку, может это старая и давно уже решённая проблема, ибо iBrowser совсем не молод и довольно вяло поддерживается, да и TinyMCE тоже довольно бородатая разработка. Но факт остаётся фактом.

Читать далееИспользование iBrowser для TinyMCE опасно!

FirstVDS Улёт. Работаем

В прошлый раз я писал о том, как попробовал хостинг FirstVDS. Прошло некоторое время, и время более-менее расставило всё по своим местам. В общем я решил пока не рвать с этими ребятами, так что, как-только подойдёт время, этот блог будет опять переезжать.

Читать далееFirstVDS Улёт. Работаем