MVC такой MVC =)
Попытался объяснить, что такое MVC на примере, вот результат:
….для этого пишется контроллер, который по переданному id лезет через модель в базу и достает информацию о фльме, после чего передает ее в шаблон для отображения…
Сам бы, наверное, ничего не понял…
Yii контроллеры, экшены, фильтры и немного о производительности
Начнем с основ.
Я не ошибусь если предположу, что большинство современных фреймворков для разработки web-приложений основаны на паттерне MVC (модель-представление-контроллер). Не буду углубляться в суть этого подхода, интересующимся можно прочитать об этом тут. Yii не исключение. Для обработки запроса пользователя, необходимо создать контроллер, который может содержать несколько, так называемых «экшенов». Рассмотрим пример. Пусть, нам необходимо разработать функционал для работы с постами (допустим мы разрабатываем блог). Под «работой» я понимаю, то, что нам необходимо обеспечить возможность выполнения следующих операций — создание поста, изменение и удаление. Как правило конкретную работу выполняют как раз экшены, а контроллер служит своего рода объединяющим контейнером для них. В Yii контроллером является класс производный от CController, таким образом простейший контроллер может быть иметь следующий вид:
-
{
-
…
-
}
Yii накладывает несколько ограничений на объявление класса контроллера:
1. Наименование класса должно быть быть следующим <название_контроллера>Controller — окончание «Controller» — является обязательным. Пример: PostController.
2. Название файла, содержащего контроллер должно совпадать с наименованием класса. Пример: PostController.php.
Как говорилось выше, основные действия выполняют именно экшены, рассмотрим их реализацию в Yii.
Экшеном (action) может быть как метод контроллера, так и отдельный класс.
В простейшем и самом распространенном варианте, экшеном является метод контроллера. Здесь Yii так же накладывает свои правила:
1. Название метода должно содержать префикс «action».
Пример:
-
public function actionCreate()
-
{
-
….
-
}
И так мы имеем простейший контроллер, который имеет следующий вид:
-
{
-
public function actionCreate()
-
{
-
echo 'Вызван экшн create!';
-
}
-
}
Сохраним данный код в файле PostController.php, и поместим его в каталог Controllers приложения.
Теперь мы можем обратиться к нашему экшену, используя следующий url:
http://localhost/testDrive/index.php?r=post/create
(тут я предполагаю что Yii-приложение расположено в каталоге testDrive, в корне web-сервера)
Параметр r, который имеет значение post/create, в терминах Yii, называется роутом. Как несложно догадаться, первая часть роута (post) — обозначает идентификатор контроллера (PostController), вторая часть (create) — идентификатор экшена (в данном случае мы вызываем экшн actionCreate, в контроллере PostController).
Кроме стандартного способа создания экшенов (как методов контроллера), Yii позволяет создавать экшены в виде отдельных классов (В Codeigniter и Kohana такого делать нельзя, Symfony, кажется, умеет так).
Экшеном является класс, унаследованный от CAction.
Пример:
-
class ActionShow extends CAction
-
{
-
public function run()
-
{
-
//логика экшена располагается здесь
-
echo "Экшн в виде отдельного класса!"
-
…..
-
}
-
}
Сохраним данный код в файле ActionShow.php и поместим его в каталог
/testDrive/protected/controllers/post/ (его необходимо создать).
Так как мы «вынесли» экшн в отдельный класс, необходимо как-то сообщить нашему контроллеру где искать класс экшена. Для этого необходимо переопределить метод actions контроллера, который должен вернуть массив, следующего вида:
‘actionName’ => ‘path.to.action.class’
Пример:
-
'show' => 'application.controllers.post.ActionShow'
-
);
‘show’ — имя вызываемого экшена;
‘application.controllers.post.ActionShow’ — алиас пути, иными словами путь к файлу экшена в структуре приложения Yii (физически это путь к файлу /testDrive/protected/controllers/post/ActionShow.php)
Определив метод actions, наш контроллер будет иметь следующий вид:
-
{
-
public function actions()
-
{
-
return array(
-
'show' => 'application.controllers.post.ActionShow'
-
);
-
}
-
public function actionCreate()
-
{
-
echo 'Вызван экшн create!';
-
}
-
}
Теперь при обращении к url
http://localhost/testDrive/index.php?r=post/show
мы должны увидеть вызов метода run, класса ActionShow.
Возникает вопрос: «для чего выносить экшены в отдельные классы?»
Как отмечается в документации Yii, это позволяет организовать приложение по модульному принципу, таким образом один и тот же экшн, реализованный в виде отдельного класса, может быть использован несколькими контроллерами. Лично мне, тоже, гораздо удобнее работать с php файлом, в котором расположено минимум php-кода. Вынести экшн в отдельный класс это конечно же хорошо и удобно — но есть другая сторона вопроса — производительность. Как мне кажется (внутренности Yii я не смотрел), при рализации экшена как отдельного класса, на плечи Yii ложится несколько дополнительных задач, а именно:
1. Необходимо преобразовать alias пути в реальный путь до класса экшена.
2. Необходимо подключить файл экшена.
3. Необходимо создать экземпляр класса экшена.
4. Необходимо выполнить метод run().
Мне было интересно какой и способов реализации экшенов работает быстрее и насколько. Для этого я провел небольшое тестирование.
Был написан следующий контроллер:
-
* Description of TestActionController
-
*
-
* @author andrey
-
*/
-
class TestActionController extends CController
-
{
-
//put your code here
-
public function actions()
-
{
-
return array(
-
'outer' => 'application.controllers.outer.OuterAction'
-
);
-
}
-
public function actionInline()
-
{
-
print "inline action…";
-
}
-
}
Внешний экшн имеет вот такой вид:
-
* Description of OuterAction
-
*
-
* @author andrey
-
*/
-
class OuterAction extends CAction
-
{
-
//put your code here
-
public function run()
-
{
-
print "outer action…";
-
}
-
}
Для тестирования, утилита ab (apache bench) обращалась к следущим url:
http://localhost/yii/index.php/TestAction/outer — для тестирования экшена, выполненного в виде отдельного класса
http://localhost/yii/index.php/TestAction/inline — для тестирование экшена, выполнненого в виде метода контроллера
Тестирование проводилось на моей домашней машине: core2duo 2.2, 2GB — ОП, Ubuntu 8.10, Apache 2.2.9, php 5.2.6.
Команда для тестирования ab -t 10 -c 10 url.
И вот такие получились результаты.
экшн как метод контроллера ~ 92-95 rps
экшн как отдельный класс ~ 87-90 rps
Как видно из результатов, производительность отличается, примерно на 2-5 запросов в секунду, в пользу «встроенных» экшенов. Конечно, для очень больших и нагруженных проектов — даже эти 5 запросов очень важны, однако для меня же удобнее использовать «внешние» экшены, несмотря на «мизирный» проигрыш в скорости работы.
Хотелось бы сказать еще несколько слов о фильтрах. Согласно документации фильтр — это код, который может выполняться до и/или после выполнения любого экшена. Как и сами экшены — фильтры могут быть реализованы двумя способами:
- как метод контролера
- как отдельный класс
При реализации фильтра как метода контроллера, необходимо создать метод с префиксом «filter», пример:
-
public function filterInAction($filterChain)
-
{
-
// код фильтра
-
…….
-
$filterChain->run();
-
}
Особенности:
1. Префикс «filter» — обязателен.
2. Модификатор доступа для метода фильтра — public.
3. Строка $filterChain->run() — обязательна для выполнения цепочки фильтров и запрошенного экшена.
4. Встроенные фильтры могут выполняться только перед выполнением экшена (я не нашел способа выполнить встроенный фильтр после экшена)
Для того что-бы контроллер «знал» о необходимости выполнения фильтра — необходимо переопределить его метод «filters», пример:
-
{
-
return array(
-
'InAction' // указываем название метода фильтра БЕЗ префикса "filter"
-
);
-
}
Данный код говорит о том, что фильтр «InAction» — будет выполнен при обращеннии к любому экшену контроллера. Для того что бы исключить некоторые экшены, необходимо переписать метод вот так:
-
{
-
return array(
-
'InAction — inline' // указываем название метода фильтра БЕЗ префикса "filter"
-
);
-
}
фрагмент строки «- inline» — говорит о том, что экшн «inline» не будет обработан фильтром.
Второй способ реализации фильтров — в виде отдельного класса. Для этого необходимо создать класс, производный от CFilter и переопределить его методы.
Пример:
-
{
-
public function preFilter($filterChain)
-
{
-
// код выполняемый перед экшеном
-
…..
-
return true;
-
}
-
public function postFilter($filterChain)
-
{
-
// код выполняемый после экшена
-
…..
-
}
-
}
Особенности:
1. Оба метода должны принимать один параметр — $filterChain
2. Для того что бы продолжилось выполнение экшена или следующего в цепочке фильтра, метод preFilter должен вернуть true (если вернуть false — выполнение прекращается…)
Для использования внешнего фильтра, так как же и при использовании «встроенного» — необходимо переопределить метод filters контроллера.
Пример:
-
{
-
return array(
-
'InAction — inline',
-
array('application.filters.OuterFilter')
-
);
-
}
Обратите внимание на то, что при указании «внешнего» фильтра, элементом массива фильтров, является так же массив, а не строка, как при объявлении «внутренних» фильтров. Используя такой подход можно передавать дополнительные параметры в фильтр, например вот так:
-
{
-
return array(
-
'InAction — inline',
-
array(
-
'application.filters.OuterFilter',
-
'clean' => 'all'
-
)
-
);
-
}
В этом примере, мы передаем параметр «clean» со значением «all».
Так же как и при работе со «встроенными» фильтрами, можно указывать какие
экшены контроллера стоит обрабатывать, а какие нет.
-
public function filters()
-
{
-
return array(
-
'InAction — inline',
-
array(
-
'application.filters.OuterFilter — inline',
-
'clean' => 'all'
-
)
-
);
-
}
При использовании внешних фильтров, так же как и при использовании внешних экшенов, Yii должен проделать некоторую дополнительную работу.
1. Необходимо преобразовать alias пути в реальный путь до класса фильтра.
2. Необходимо подключить файл фильтра.
3. Необходимо создать экземпляр класса фильтра.
4. Необходимо выполнить методы preFilter() и postFiltrer().
На выполнение этих действий, затрачиваются дополнительные ресурсы и время. Мне было интересно провести испытания, аналогичные с испытаниями для экшенов. Получились следующие результаты.
Встроенный экшн + встроенный фильтр ~ 89 — 90 rps;
Встроенный экшн + внешний фильтр ~ 87 — 89 rps;
Внешний экшн + встроенный фильр ~ 84 — 88 rps;
Внешний экшн + внешний фильр ~ 87 — 90 rps;
Применение внешних фильтров дает следующие преимущества:
1. Код становится более модульным, упрощается повторное использование
2. Внешние фильтры, в отличии от встроенных, позволяют выполнять действия как до, так и после выполнения экшена.
Выводы.
Лично для себя я решил следующее:
Несмотря на то, что реализация экшенов и фильтров как отдельных классов, несколько снижает производтельность приложения, я все таки выбираю этот подход — мне так значительно проще и комфортнее ориентироваться в структуре проекта, несмотря на то, что количество файлов увеличивается.
Каждый выбирает свой способ реализации, надеюсь, эта статья поможет Вам сделать выбор!
Удачного yii-кодинга!
Читайте так же:
Yii, пишем фильтр для предотвращения XSS атак
Основной сайт Юпи! — http://yupe.ru
Исходный код — https://github.com/yupe/yupe
Присоединяйтесь!
Codeigniter vs Kohana
Раунд 1 MVC.
Kohana
При объявлении собственного конструктора в контроллере, необходимо вызвать конструктор базового класса, так как Kohana основан на ООП и PHP 5 синтаксис этого вызова следующий:
-
parent::__construct()!
в отличии от
-
parent::Controller()
в Codeigniter.
В отличии от Codeigniter, Kohana требует что бы в наименование класса контроллера присутствовал постфикс «_Controller», не указав который, при обращении к контроллеру получаем сообщение об ошибке «Fatal error: Cannot redeclare class ».
На мой взгляд, это не совсем удобно, так как разработчик может иметь собственные правила именования классов, которые ни как не пересекаются с правилами Kohana. Однако это позволяет избежать конфликтов имен, в тех случаях, когда класс контроллера и, например, модели, называется одинаково.
Для обозначения методов, доступных только внутри контроллера (не доступных с сайта), Codeigniter предлагает использовать префикс «_» в названии метода.
Пример:
-
public function _some_action() - метод недоступный для вызова через URL сайта.
Kohana предлагает два варианта решения этой проблемы (один из которых наиболее интуитивен, по моему мнению), а именно:
можно объявить метод как private — такой метод недоступен через URL сайта
Пример:
-
private function some_action() - приватный метод контроллера.
как и в Codeigniter можно объявить закрытый метод с префиксом «_»
Пример:
-
public function _some_action()
- так же приватный метод.
Можно комбинировать два этих способа и объявить метод следующим образом:
-
private function _some_action()
Контроллер в Codeigniter может содержать еще две функции, аналога которых пока нет в Kohana:
_remap($method) — данная функция предназначена для локального роутинга, т.е. перенаправления на другие контроллеры приложения. В качестве параметра ей передается запрошенный в URL метод.
_output($out) - функции передается обработанное отображение, предназначенное для отсылки браузеру. Данная функция может быть использована для конечной обработки вывода.
Отображения (VIEW)
В обоих фреймворках отображения (или предстваления, или view) — это просто PHP сценарии, содержащие в основном HTML-код. Однако способ работы с отображениями кардинально отличается в этих фреймворках.
Codeigniter
Для загрузки файла отображения из контроллера используется вызов метода:
-
$this->load->view(«view_file»);
view_file — имя файла отображения без расширения «php».
В Codeigniter одновременно с загрузкой происходит и вывод отображения в браузер пользователя.
Для передачи параметров в представление, используется следующий вызов:
-
$this->load->view(«view_file»,$some_data);
где $some_data — ассоциативный массив содержащий параметры для отображения.
Пример:
-
$some_data['user_fname'] = 'Андрей';
-
$some_data['user_lname'] = 'Опейкин';
Если файл отображения будет содержать следующий код:
<html>
<body>
<h1>Привет $user_fname $user_lname!</h1>
</body>
</html>
То «отрендеренное» отображение (страничка, которую увидит пользователь) будет выглядеть так:
<html>
<body>
<h1>Привет Андрей Опейкин!</h1>
</body>
</html>
Кроме ассоциативного массива в качестве второго параметра метода, можно передавать объект, свойства которого, заменяют соответствующие переменные в отображении.
На мой взгляд недостатком такого подхода является то, что при загрузке отображения, автоматически происходит его отправка в браузер пользователя. Для перехвата вывода необходимо использывать метод контроллера _output() - описанный ранее, так же получить (а не вывести в браузер) полностью готовое отображение можно передав третий параметр в метод загрузки представления:
-
$page = $this->load->view(«view_file»,$some_data,TRUE); // содержимое присваивается переменной $page
Kohana
Для загрузки файлов отображений существует два способа:
Использование конструктора класса View
Пример:
-
$view = new View(«view_file»);
Вызов статичного метода (метод фабрики)
Пример:
-
$view = View::factory(«view_file»);
В обоих случаях «view_file» — имя файла отображения.
Использование любого из этих способов, приводит лишь к созданию объекта отображения, вывод содержимого отображения в браузер пользователя НЕ происходит.
Воспользуемся примером отображения, описанного выше.
Для передачи параметров в отображение используется объектный подход.
-
$view->user_fname = 'Андрей';
-
$view->user_lname = 'Опейкин';
Так же можно передать параметры методом set();
-
$view->set('user_fname','Андрей');
-
$view->set('user_lname','Опейкин');
Установка параметров отображения так же как и создание объекта отображения НЕ приводит к пересылке информации пользователю.
Для отправки вывода пользователю необходимо вызвать метод render() отображения
-
$view->render(TRUE);
На мой взгляд подход Konaha удобнее и практичнее.
Модели (Models)
В обоих фреймворках использование моделей НЕ является обязательным. При желании можно обойтись контроллерами и представлениями.
Kohana
Как и в случае с контроллерами, требует, чтобы класс модели имел определенный постфикс — «_Model».
Пример:
-
class User_Model extends model
-
-
………
-
-
}
Для загрузки модели из контроллера, необходимо просто создать экземпляр класса модели:
-
$user = new User_model();
После этого можно обращаться ко всем методам и свойствам данного объекта.
Если в конструкторе модели вызывается родительский конструктор, т.е. конструктор имеет вид:
-
public function __construct(){
-
parent::_construct();
-
………………………….
-
}
Автоматически загружается класс для работы с базой данных, доступный как
-
$this->db
Codeigniter
Для загрузки класса модели необходимо использовать следующий код:
-
$this->load->model(«model_name») ;
После этого к методам модели можно обращаться следующим образом:
-
$this->model_name->some_method();
Объект для работы с базой данных НЕ создается автоматически при загрузке модели, для того что бы объект все таки создался, необходимо передать значение TRUE в качестве третьего аргумента:
-
$this->load->model(«model_name»,'',TRUE) ;
Второй аргумент определяет псевдоним модели, по которым она будет доступна в контроллере.
Вся информация о фреймворке Kohana взята из соответствующей документации. Могут быть некоторые неточности, так как документация еще находится в процессе разработки и описывает далеко не все возможности и API этого фреймворка.


