Шаг 3 : Что такое MVC?
Рубрика: Первые шаги
21 Июл. 2009
Рамки MVC весьма размыты, поэтому моё понимание может не совпадать с полученными вами ранее знаниями. Хотел бы напомнить что все сказанное в уроке является моим личным опытом, и я не навязываю вам думать точно также. Такой материал весьма тяжело рассказывать, поэтому прошу отнестись с пониманием. И так, начнем урок...
Введение
Model-view-controller или просто MVC — это шаблон проектирования приложения. Он применятся как в web разработке, так и в обычном прикладном программировании. Выбор данной архитектуры подразумевает разделение приложения на три логические части (компоненты), каждая из которых выполняет определенный ряд своих функций.
Большая гибкость приложения на MVC и является самым главным плюсом к его использованию. Для себя я отметил следующие вещи:
- Вы можете изменять один компонент приложения при минимальном воздействии на другие.
- Все таблицы, html код, да и вообще вся верстка — теперь в отдельных файлах. Тонны php кода не будут мешаться с html (превращая приложения в кучу мусора).
- Исходя из пункта «два» - верстальщики начнут вас уважать и благодарить :) Также вы с лёгкостью сможете подключить к приложению шаблонизатор (Smarty или др).
- У вас получиться во много раз уменьшить повторение кода
Конечно если есть «плюсы», то и есть «минусы». В принципе «минусы» можно найти в чем угодно, поэтому обвинения что «MVC приложение работает медленнее чем приложение на чистом php» — я не рассматриваю. Понимаете, за счет небольшого (именно небольшого) убытка в скорости, вы получаете приложение которое и через десять лет будет легко расширять (что не всегда можно сказать про крупный сайт на чистом php).
Э..?
Давайте поговорим всё таки за счет чего достигаются все эти плюсы. Как я уже и говорил, приложение разбивается на три части. Рассмотрим подробнее что и для чего:
Контроллер (Controller)
В Yii контроллером называется класс наследуемый от CController или дочернего от него. Сам контроллер у нас занимается следующими действиями:
- Обрабатывает данные которые пришли от пользователя (GET, POST запросы)
- Содержит в себе общую логику приложения (проверки, анализ, редиректы). Проверяет имеет ли пользователь доступ к данному куску приложения и тп.
- Говорит где и что должно быть показано пользователю, но сам ничего не выводит! Те. просто вызывает в нужном месте нужные нам отображения
- Обращается к модели если надо получить какие то данные (например из базы)
Все данные которые пользователь указывает в адресной строке — попадают на обработку к контроллеру. Если вы не меняли маршруты-по-умолчанию у себя на сайте, то написав localhost/site/ - пользователь обратится к контроллеру Site.
Заметка : В приложении может быть как один контроллер, так и несколько.
Внутри класса контроллера должны находиться экшинсы которые отвечают за отдельные части приложения. Экшинсы (Actions) — это простые методы, которые имеют приставку «action» перед своим именем.
Вот так выглядит простой контроллер Site с двумя экшинсами:
<?php
// После имени контроллера - обязательно приставка Controller!
// т.е. вместо Site - пишем SiteController. Также наш контроллер
// обязательно должен быть наследован от класса CController
// или дочернего от него
class SiteController extends CController
{
// перед названием экшинса вы должны написать приставку action,
// после этого названиея (с большой буквы).
public function actionIndex()
{
}
public function actionAuthor()
{
}
}
Важно запомнить:
- все экшинсы должны начинаться с приставки «action»
- все контроллеры должны после имени иметь приставку «Controller»
- не забывайте наследовать свой контроллер от CController или дочернего от него класса.
Давайте теперь разберем для чего нам экшинсы.
Когда вы набрали в браузере : localhost/site/index/ - вы обратились к контроллеру Site и его экшинсу index (actionIndex). В данном случае весь код который будет внутри экшинса (к которому мы обращаемся) — будет выполнен.
Теперь давайте немного модернизируем наш код:
public function actionIndex()
{
die ("Привет! Это actionIndex()");
}
public function actionAuthor()
{
die ("Привет! Это actionAuthor()");
}
Когда мы обратимся к нашему приложению по адресу localhost/site/index/ мы получим на экране надпись:
Привет! Это actionIndex()
А если наберем localhost/site/author/ :
Привет! Это actionAuthor()
Заметка: Функция die() в php — аналогична функции echo. Единственное отличие в том что после вывода — приложение дальше не выполняется.
Как вы понимаете благодаря такой разбивке контроллеров вы можете легко планировать страницы внутри своего приложения.
Также хочу добавить, что написав localhost/site/author/?test=hello_world — вы также как и раньше обратитесь к экшинсу author, но на этот раз в нем будет доступна переменная $_GET['test'] со значением «hello_world».
Заметка: Для того что бы использовать красивые адреса вида localhost/site/author/2007/09/01/ и т.п. — надо использовать маршруты! Рассматривать данный материал мы будем отдельно, не в этом уроке!
Обычно все контроллеры приложения в Yii располагаются по адресу protected/controllers/. Имя контроллера также должно иметь приставку Controller. К примеру, если вы хотите что бы по адресу localhost/user/index/ выводился текст «Привет Вася!», вы должны выполнить следующие действия:
- В папке protected/controllers/ создать файл UserController.php
- В нем разместить следующий код:
<?php
class UserController extends CController
{
public function actionIndex()
{
die ("Привет Вася!");
}
} - Набрать в браузере localhost/user/index/ или localhost/user/
Заметка: Хочу отметить что за вывод страницы localhost/ также отвечает контроллер. (Да, он не указан в адресе после localhost, но он все равно есть). В Yii конфиге вы можете указать какой контроллер будет выполняться «по умолчанию» (обычно это SiteController)
Модель (Model)
Модель это класс для работы приложения с базой. Для каждой таблицы с которой предстоит работать — создается своя модель (в Yii это класс наследуемый от CModel или производного от него).
Модель в нашем приложении выполняет следующие действия:
- Облегчает работу с данными в базе (при наследовании от CActiveRecord или производного от него)
- Проверка данных перед сохранением/добавлением согласно правилам (rules)
- Содержит в себе пользовательские методы для более продвинутой работы с базой
Самая простая модель выглядит следующим образом:
<?php
// Наследуем нашу модель от CActiveRecord
class Users extends CActiveRecord
{
public static function model($className=__CLASS__)
{
return parent::model($className);
}
/**
* Специально что бы указать с какой таблицей
* в базе данных работает наша модель.
*
* @return unknown
*/
public function tableName()
{
// в нашем случае это таблица user
return 'user';
}
}
Заметка: Вы можете спокойно использовать данный каркас у себя в работе (изменяя лишь название класса и таблицу с которой работает модель).
Перед нами модель для работы с пользователями (с таблицей user). Благодаря тому что мы наследуем её от класса CActiveRecord — мы получаем дополнительный функционал (методы find, count, findAll и др.)
Для того что бы обратиться к модели из контроллера необходимо написать:
Название_модели::model()->название_метода();
Либо предварительно создав экземпляр модели:
$model = new Название_модели();
$model->название_метода();
Заметка: В отличии от контроллера после названия класса модели — приставок писать не надо. Мы можем называть наши модели как хотим. Файл модели обычно располагается в папке protected/models и совпадает с названием класса.
Посмотрите как просто выглядит запрос для получения пользователя с логином - «zolter»:
$user_info = User::model()->findByAttributes(array('login' => 'zolter'));
В данном примере я использовал встроенный метод findByAttributes в котором указал что необходимо найти запись где поле «login» равняется значению «zolter». Если такая запись в таблице users существует — в переменной $user_info будет сохранен результат (обьект).
Получить доступ к его полям вы можете следующим образом:
echo $user_info->login; // получу логин (zolter)
echo $user_info->id; // получу id
Заметка: Встроенных методов достаточно много. Более подробно они будут описаны в «Шаг 4: Модель». Самую полную информацию по всем методам вы можете получить в API (http://www.yiiframework.com/doc/api/CActiveRecord) [англ] или статье «Active Record» [рус.] (http://www.yiiframework.ru/doc/guide/ru/database.ar)
Если вам не хватает встроенных методов модели — вы можете легко расширить их. Для этого просто добавьте новый метод в свою модель и обращайтесь к нему аналогичным образом.
Заметка: Для каждой таблицы с которой работает приложение должна быть создана соответствующая модель.
Отображение (View)
Главной функцией отображения является вывод данных (которые возможно пришли из контроллера) на экран. Обычно файл отображения содержит в себе элементы пользовательского интерфейса. В нем также допускается использование php для реализации простой-логики. К примеру, выставления проверок: «если пользователь имеет статус админ — тогда выводим этот кусок файла, иначе — другой». Следуя требованиям MVC в отображении должен находиться минимум кода логики приложения (для этого специально существует контроллер).
Заметка: В некоторой документации View называют «отображением», в некоторой — «представлением». Мне больше нравиться первый вариант.
Вот пример простого отображения, которое служит для вывода меню на сайте:
<h1>меню</h1>
<a href='#'>пункт 1</a>
<a href='#'>пункт 2</a>
<?php if ($user['status'] == «admin»): ?>
<a href='#'>меню админа</a>
<?php endif; ?>
Как вы видите это простой html код. Ничего сложного! В нем также я влепил проверку "является ли пользователь админом" (если да — тогда выводим дополнительный пункт меню).
Переменная $user может быть объявлена в этом же отображении, а может быть передана с контроллера.
Заметка: для того чтобы вывести отображение из контроллера — используется метод render(«название_отображения»). По умолчанию будет выведено на экран отображение с переданным названием, из папки : views/название_контроллера/название_отображения/
Если в контроллере Test написать $this->render('my_view_file') — файл отображения будет подгружаться из адреса protected/views/test/my_view_file/. Поэтому обычно для каждого контроллера в папке views создается своя папка, со своими отображениями.
Давайте используя уже полученные ранее знания, решим следующую задачу:
Пускай у нас есть таблица users (с полями: id, login, passwd). Зайдя по адресу localhost/admin/index/ на экране должна быть показана информация о пользователе с логином "admin".
Создаем модель для работы с таблицей users. Назавём её User.php и поместим по адресу protected/models:
<?php
class User extends CActiveRecord
{
public static function model($className=__CLASS__)
{
return parent::model($className);
}
public function tableName()
{
// в нашем случае это таблица users
return 'users';
}
}
Теперь создадим контроллер который используя модель — получит данные пользователя «admin», и передаст их в отображение. Контроллер создаем в папке protected/controllers и называем AdminController.php :
<?php
class AdminController extends CController
{
public function actionIndex()
{
// Проверяем есть ли такой пользователь в базе.
// Если есть - сохраняем его данные в $user_info
if ($user_info = User::model()->findByAttributes(array('login' => 'zolter'))) {
$this->render('index', array(
// передаем параметнную $user_info в отображение
// в котором она будет доступна по имени - "$info"
'info' => $user_info,
));
} else {
die('нет такого юзера');
}
}
}
Ну и осталось дело за малым, создаем отображением. Для этого в папке views создаем папку admin (согласно названию нашего контроллера). Теперь в папке admin создаем файл index.php (согласно названию экшинса который его вызывает) :
<pre>
Id : <?php echo $info->id; ?>
Логин: <?php echo $info->login; ?>
Пароль: <?php echo $info->passwd; ?>
</pre>
При запуске localhost/admin/index вы должны увидеть на экране логин, пароль и id админа. (если не забыли предварительно создать его в таблице users).
Как это работает?
- Вы набрали localhost/admin/index тем самым обратились в контроллер Admin, и экшинс Index
- Далее начал работать код из actionIndex()
- Вы запросили из модели User данные, где логин равен значению «zolter». Т.к. Модель User связана с таблицей users — она сделала запрос в БД к этой таблице.
- После того как такие данные были найдены — методом render они были переданы в отображение index.php
- После этого на экран было показано отображением
Вывод..
Я не ставил перед собой цель описать весь функционал и возможности контроллера-модели-отображения. Надеюсь тем кто не понимал что такое MVC — стало немного понятнее.
Писал в спешке, надеюсь ничего не забыл. Если встретите ошибки в тексте — выделяйте их через Ctrl+Enter (буду очень благодарен).
Если хотите опубликовать этот материал у себя - пожалуйста, разместите ссылку на страницу откуда вы его взяли.
- Сегодня мы поговорим с вами о том как изменить генератор символов на капче. Часто мне стали приходить сообщения на почту ... "Как на Yii капче выводить цифры"
- Немного обсудив своё творение «Компонент Rss ленты v 1.0» на русском форуме я решил немного его переделать. Мною было ... "Компонент Rss ленты v2.0"
- Интеграция Zend/Pdf в Yii Framework...
next
Введение
Yii является одним из распостраненных PHP фреймворков. В этой статье мы рассмотрим пример интеграции библиотеки ... "Отображаем PDF на Yii при помощи Zend"

andy_s
Было сказано: Вторник, 21 Июль 2009
Читал в спешке, вроде всё понравилось, начинающим очень поможет понять хотя бы основные моменты. Отдельный плюс за описание MVC в контексте Yii :)
P.S. Не понимаю, почему вы пишите "экшинсы", а не "экшены". Экшинс - это уже множественное число (да и буква И спорная :))

[adm] zolter
Было сказано: Вторник, 21 Июль 2009
согласен. просто привык так :)
потом наверно поправлю, что б правильнее было :)

[adm] zolter
Было сказано: Четверг, 23 Июль 2009
Пожалуйста)
К сожалению, что то случилось с расширением которое должно подсвечивать синтаксис php. Постараюсь завтра исправить, то читать код наверное не приятно

EverCelt
Было сказано: Четверг, 20 Август 2009
вопрос по поводу контроллеров. развернул тестовое приложение testdrive:
виджетот mainmenu создаю меню
array('label'=>'Home', 'url'=>array('/site/index')),
array('label'=>'Contact', 'url'=>array('/site/contact')),
array('label'=>'Test', 'url'=>array('/site/test1'))...
но доступ все равно происходт вот так
http://www.yii.test/testdrive/index.php?r=site/contact
,a не http://www.yii.test/testdrive/site/contact
p.s. извиняюсь, если глупый вопрос:) или по невнимательности:)

EverCelt
Было сказано: Четверг, 20 Август 2009
чтобы правильно выполнялось "localhost/site/index/ - вы обратились к контроллеру Site и его экшинсу index (actionIndex)"
необходимо изучить
http://www.yiiframework.ru/doc/guide/ru/topics.url
http://dbhelp.ru/how-to-remove-indexphp/page/

Alexdamo
Было сказано: Пятница, 16 Октябрь 2009
Почему контроллер не может найти свой view.
Есть контроллер TestController extends CController.
В папке views есть папка test.
И вот при вызове render() он не находит в ней view, а если в render прописать так: render ('test/index'), то находит. В контроллерах, созданных автоматически (таких как SiteController), все ОК.

[adm] zolter
Было сказано: Пятница, 16 Октябрь 2009
Т.е. вы пишите:
$this->render('index') - не работает
А:
$this->render('test/index') - работает?

[adm] zolter
Было сказано: Пятница, 16 Октябрь 2009
п.с. при вызове рендера всегда надо указывать какой файл вы хотите отобразить. т.е. для экшинса index в скобках render-а надо писать 'index', для test - писать render('test') и т.д.

Alexdamo
Было сказано: Пятница, 16 Октябрь 2009
Все, разобрался. Дело было в том, что я создал в классе контроллера конструктор. Как только убрал конструктор, все заработало. Если используем конструктор в контроллере, то надо обязательно вызывать его родительский конструктор с указанием id контроллера. Тогда он находит свои views в нужной папке
Т.е. если контроллер TestingController, то конструктор:
{
parent::__construct('Testing');
}
Если конструктор нужен, конечно.
Вот еще один урок о том, что родительский конструктор надо не забывать вызывать.

[guest] Vic
Было сказано: Пятница, 05 Март 2010
Вроде бы всё не плохо, но только делать die() в контроллере -- верх неприличия.

[guest] Гость
Было сказано: Среда, 07 Июль 2010
Все почитал вроде все понятно =)) Немогу до конца вкурить как же все таки использовать функции мускула по типу mysql_fetch_array и др. если не трудно помогите буду оч благодарен =))

lavrenovnn
Было сказано: Среда, 11 Август 2010
Огромная благодарность автору!!! Читая доки, многое не понимал ... прочитав три ваши статьи ... особенно текущую - всё стало понятно!!! Теперь уии со мной!
Единственная заминка была с представлением (1.1.3), выдавал ошибку render - потому как нужен макет, заменив его на renderPartial - вывод без макета - работает отлично!

[guest] zolter
Было сказано: Среда, 11 Август 2010
Спасибо, очень рад что мои статьи все еще помогают :)
Надеюсь в скором времени разгребу с работой и продолжу писать

[guest] Darth_Ixis
Было сказано: Суббота, 28 Август 2010
Экшинсы...бррр)
А статья замечательная, лаконично написано :)

[guest] Гость
Было сказано: Понедельник, 13 Сентябрь 2010
в примере где идет проверка существования логина admin в коде ты ищешь логин zolter ;)

[guest] Azariil
Было сказано: Вторник, 23 Ноябрь 2010
Одного не пойму - почему если вся логика работы с БД должна быть в Модели, то почему мы запросы к БД через AR делаем в контроллере? По идее за это все должна отвечать модель? Или я чегото недопонимаю?

[guest] Breeze
Было сказано: Воскресенье, 30 Январь 2011
Вот кое-что интересное по MVC принципы на примерах.
Помогает лучше понять суть как по мне:
1)http://chtivo.webhost.ru/articles/mvc.php
2)http://chtivo.webhost.ru/articles/mvc_rate.php

[guest] zolter
Было сказано: Понедельник, 31 Январь 2011
to Breeze
Действительно полезный материал и очень подробный. Спасибо!

[guest] Гость
Было сказано: Вторник, 15 Февраль 2011
Все сделал по примеру - выскакивает ошибка AdminController cannot find the requested view "index". Подскажите, что делать?

[guest] alex3d
Было сказано: Воскресенье, 20 Февраль 2011
Спасибо за статью! Блин... реклама девок в нижнем белье слева от статей явно не дает сконцентрироваться на материале.... :)))))))))))

[guest] Гость
Было сказано: Воскресенье, 15 Май 2011
Просто.... большое спасибо... в жопу всю эту официальную документацию, такое ощущение что они хотят тебя запутать, я думаю она будет полезна уже людям которые полностью в "теме". С удовольствием читаю дальше.... )))

[guest] Гость
Было сказано: Понедельник, 26 Сентябрь 2011
Хороший материал:) с официальной документации действительно тяжелова-то стартануть...

[guest] Гость
Было сказано: Четверг, 12 Январь 2012
Спасибо, друг! Просто без понтов объяснил суть, теперь смогу и сам что-то написать.

[guest] Гость
Было сказано: Четверг, 12 Январь 2012
неудобно читать зеленые замечания из-за того, что их нужно прокручивать

[guest] Гость
Было сказано: Четверг, 12 Январь 2012
$user_info = User::model()->findByAttributes(array('login' => 'zolter'));
Ну ладно - это очень простой запрос типа select * from user where login=zolter
А как быть со сложными запросами ?
Там где возвращается набор записей, где они должны быть как-то упорядочены, где накладываются условия из других таблиц...

[guest] zolter
Было сказано: Четверг, 12 Январь 2012
Да очень просто, тогда будет не findBy, а findAllBy. т.е.:
$user_info = User::model()->findAllByAttributes(array('status' => 1), array('order' => 'id DESC'))
т.е. это селект всех пользователей со статусом 1 и отскортированых по полю id

[guest] Гость
Было сказано: Воскресенье, 15 Январь 2012
ну хорошо, это
select * from user where status = 1 order by id desc
а как будет, например, слудующий запрос -
SELECT groups.id, groups.name, users.name
FROM groups
JOIN users ON users.id = groups.owner
WHERE archived = 1 AND groups.owner = $owner
ORDER BY groups.name, users.name ASC
? ;)

[guest] zolter
Было сказано: Воскресенье, 15 Январь 2012
$user = Yii::app()->db->createCommand()
->select('g.id, g.name, u.name')
->from('groups g')
->join('users u', 'u.id = g.owner')
->where('u.archived=1 AND g.owner=:owner', array(':owner'=>$owner))
->queryRow();
разве не круто?

[guest] Гость
Было сказано: Воскресенье, 15 Январь 2012
гм...
наверное это действительно круто,но я че-то синтаксиса не понял ;)
как это работает createcommand()->select()->from()->join()->where()->queryrow() ?!
А нельзя ли как-нибудь просто использовать чистый sql без всяких извращений ? ;)
чтобы не утомлять вас своими вопросами,может бытьподскажете, нет ли какой статейки для чайников, популярно описывающей как пользоваться этой расчудесной activerecord ?

[guest] Гость
Было сказано: Воскресенье, 15 Январь 2012
и все-таки задам еще один вопрос по поводу приведенной вами конструкции createcommand.
- где здесь модели для таблиц users и groups и как обращаться к полям результирующего набора данных ?
$user->g.id
$user->g.name
$user->u.name
?

[guest] zolter
Было сказано: Воскресенье, 15 Январь 2012
Запрос который я привел выше - не требует наличия моделей для этих таблиц. Я просто привел пример коснтруктора запросов который позволяет их удобно делать. Если хотите писать чистым SQL кодом - пожалуйста, такая возможность так же существует:
$sql="SELECT username, email FROM tbl_user";
$dataReader=$connection->createCommand($sql)->query();
По сути красота AR используется только для CRUD операций. Для чего то более сложного - либо createCommand либо через with и тп.
Хорошие статьи - в документации:
http://www.yiiframework.com/doc/guide/1.1/ru/database.dao
http://www.yiiframework.com/doc/guide/1.1/ru/database.query-builder
http://www.yiiframework.com/doc/guide/1.1/ru/database.arr

[guest] zolter
Было сказано: Воскресенье, 15 Январь 2012
Синтаксис ->select()->from()->join()->where()->queryrow() это число для удобства и читаемости. Как уже привел выше - можно и руками писать запросы :)


