Решил записывать сюда всякие интересности и особенности поведения Kohana, с которыми периодически сталкиваюсь. В первую очередь для себя, но возможно и вам будет интересно. Попутно здесь же буду приводить полезные приемы.
Общая информация
- У вас есть базовый контроллер (Base_Controller к примеру), и он не должен быть доступен через URL (http://localhost/base/)? Сделайте его абстрактным (abstract class)!
- Иногда бывает необходимо создать public-метод в контроллере для внутренних целей (например, чтобы вызвать его из хука или хэлпера), однако он не должен быть виден пользователю, в частности чтобы в него нельзя было попасть через URL. Для этого перед названием метода просто добавляем знак подчеркивания.
Маршрутизация
- Чтобы избавиться от index.php в URL достаточно откорректировать параметр $config['index_page'] в файле config/config.php. Не забудьте подготовить файл .htaccess, иначе ничего не получится.
Вроде бы мелочь, но я сам из-за отсутствия пояснений в документации бросался переписывать системный хэлпер, да и на форуме сталкивался с такими же вопрошающими, так что в FAQ’е должно быть на 100%.
- Как выяснилось на форуме, если в URL присутствует имя фронт-контроллера (проще говоря index.php), то итоговый Router::$current_uri будет вычислен неверно. Например, http://localhost/any/directory/index.php/test будет обработан как http://localhost/index.php/test. Все это независимо от значения параметра $config['index_page'].
Database
- Экранирование (метод Database::escape()) требуется только для выполнения plain-запросов (через метод Database::query()), методы Query Builder‘а и ORM автоматически вызывают escape().
- Если необходимо вызвать какую-нибудь специфическую функцию СУБД в Query Builder‘е, используйте в методах параметр $quote, который определяет, заключать ли передаваемые имена полей в кавычки. Например:
$this->db->where('date_created >', 'NOW()', TRUE);
ORM
- Одна из наиболее распространенных ошибок, вызванных не совсем корректной официальной документацией — добавление связи «много-ко-многим» с объектом после сохранения исходного объекта. Приведу строчку, хорошо знакомую поклонникам модуля Auth:
if ($user->save() AND $user->add(ORM::factory('role', 'login'))) {
if ($user->add(ORM::factory('role', 'login')) AND $user->save()) {
- Если вы указываете при создании ORM-объекта значение ключа (например, ORM::factory(‘user’, 1)), помните, что объект создается немедленно. Поэтому, если далее необходимо еще проделывать с ним какие-либо манипуляции (в частности, объединение с другими таблицами посредством метода with()), то сперва создайте пустой ORM-объект, примените with() и только потом делайте выборку по ключу.
// НЕПРАВИЛЬНО! будет выполнено два запроса, условие article_id=1 потеряется $article = ORM::factory('article', 1)->with('user')->find(); // ПРАВИЛЬНО! всего один запрос $article = ORM::factory('article')->with('user')->find(1);
- Если в моделях используется наследование — помните, что связи и прочие свойства родительской модели могут быть случайно затерты данными новой модели. Пример:
class Parent_Model extends ORM { $protected $ignored_columns = array('ignored1', 'ignored2'); } class Child_Model extends Parent_Model { $protected $ignored_columns = array('ignored3'); } // теперь свойства Child_Model::$ignored1 и Child_Model::$ignored2 будут обрабатываться как существующие в базе поля
Validation
- Использование правила matches[] (обычно для подтверждения пароля или e-mail) позволяет не указывать правило required для этого поля, достаточно это правило указать для поля, с которым будем сравнивать.
- Если необходимо «собрать» поле из нескольких (дата, время или еще что-то), то лучше всего использовать add_callback(), в котором нужное поле и будет вычислено. Если это происходит в модели ORM, не забываем указать исходные компоненты поля как ignored_columns. Обсуждение на оф. форуме.
Views
- Наверняка у вас будут переменные, которые могут пригодиться в любом шаблоне приложения (к примеру, имя и id текущего пользователя). Поэтому я обычно в одном из базовых контроллеров устанавливаю глобальные переменные (метод set_global()) для представления $this->template — теперь они видны из всех включаемых в шаблон представлений.
Хуки
- Если вы создаете хуки как классы (другой вариант — написание отдельных функций и вызов Event::add()), не забудьте в конструкторе привязать методы к соответствующим событиям, а после описания класса создать экземпляр хука (new Some_Hook), т.к. система самостоятельно описанные классы не создает (происходит обычный include).
- Часто из метода хука требуется добраться до свойств или методов контроллера. Для этого можно использовать свойство Kohana::$instance, в котором и хранится текущий контроллер. Если контроллер еще не был создан, то можно его создать вручную (например, если хук отработал по 404й ошибке, то конструируем страницу Not Found).
Cache
- Если Вы в контроллере пытаетесь загрузить сохраненный в кэше ORM-объект, можете обнаружить (например, через Profiler), что запрос к БД все же производится. Чтобы этого не происходило, надо в модели добавить строчку:
protected $reload_on_wakeup = FALSE;
В этом случае при сериализации/десериализации объекта (что собственно и происходит при каждой загрузке ORM-объекта из кэша) не будет происходить запрос к БД. Впрочем, если объект загружается не из кэша (к примеру, из сессии), то таким образом можно пропустить произошедшие изменения в БД и объект будет устаревшим.
Auth
- В модуле Auth предоставлена модель User, унаследованная от Auth_User. Если вы захотите изменить в ней валидацию родительской модели (обычно меняют перечень полей и правила), то в конце метода validate() вызывайте не родительский метод (parent::validate()), а ORM::validate(), иначе получите ошибку.
- Если вы используете метод validate() с сохранением объекта (параметр $save установлен в TRUE), помните, что в объект автоматически подставляются только те поля, которые упомянуты в правилах валидации. Таким образом, при существовании непроверяемых полей необходимо их добавить вручную. С другой стороны, возможно это повод заполнять подобные поля в отдельной форме (что-то типа «анкеты»), а в таблице БД сделать их поддерживаемыми NULL. Также не забудьте про связи с другими объектами — их необходимо установить до вызова validate(), иначе они пропадут (ведь метод save() вы уже вызывать не будете).
- Вводите правильный пароль, но войти не можете? А не меняли ли Вы параметр ‘salt_pattern‘ в файле config/auth.php? Помните, что после его изменения войти под имеющимися учетными записями уже не получится. Подробнее можете почитать тут.
Конфигурация
- Иногда удобно группировать файлы в тематические директории. Так можно поступить и с конфигами, например так:
echo Kohana::config('somedir/somefile.paramname');
Router3
- Подключая модуль router3 к приложению на Kohana v2.3.x, будьте готовы к тому, что теперь знак подчеркивания в имени контроллера будет заменяться слэшем и в итоге контроллер будет ожидаться в поддиректории папки controllers. Это может быть как плюсом (реализация админки в отдельной папке с помощью префиксов), так и минусом (работавшие ранее контроллеры будут приводить к странице 404).
Просто полезные ссылки
- Очень интересная панель - Kohana Debug Toolbar, демо-страничку можно посмотреть тут.
Список будет расширяться, если есть какие-либо предложения - пишите
А как отключить обработку 404 ошибки? Чтобы ее мод реврайт обработал?
Вы не указали версию Kohana, поэтому вот примерное решение для Ko3:
По умолчанию все ошибки обрабатывает внутренний обработчик Kohana, поэтому надо "обернуть" вызовы в try ... catch. В результате header с кодом 404 (или любой другой, например 403) пройдет наружу.
Версия 2.3.1. Как для нее сделать?
Не помню насчет 2.3.1, а в 2.3.4 срабатывает событие ‘system.404′, по умолчанию оно обрабатывается системой, заголовки нормально генерируются.
Как отменить это умолчание? Чтобы кохана просто пропускала это событие. Если уж нельзя, то как тогда править то представление, которое выдается пользователю при этой ошибке?
Event::clear(‘system.404′);
Написал это в главном index.php, не работает.
Навешивание стандартного обработчика происходит внутри файла bootstrap.php, так что имеет смысл прописывать данную строчку в базовом контроллере или каком-нибудь хуке.