Официальный сайт фреймворка: http://yiiframework.com/ Русскоязычный форум: http://www.yiiframework.com/forum/index.php/board,19.0.html Версия фреймворка на момент написания статьи: 1.0.6 Автор: Кузьмин Антон (Kuzya) Сайт: http://kuzya.name К сожалению редактор форума не позволяет создавать структурированный текст, из-за чего большие части кода порой трудно читаются. В этом случае рекомендую Вам скачать PDF-вариант статьи: practical_intro_yii.pdf (17 страниц, шрифт Times New Roman, 12pt). Прикреплённые файлы: design.zip, ready.zip, database.zip Введение Здравствуйте. В этой статье мы будем практиковаться в изготовлении простейшего приложения с помощью фреймворка Yii. Нашей целью будет создание сайта на котором люди могут вести свои блоги и комментировать чужие. Скажу сразу что я являюсь ярым противником различных классов и хэлперов для работы с HTML. Поэтому, в отличие от демонстрационных приложений производителей, я не использую классы типа CHtml. Хотя возможно кто-то сочтёт это неправильным. Вообщем, все шаблоны в этой статье содержат только HTML и альтернативный PHP-синтаксис. Начните с создания "чистого" хоста для нашего сайта. Я назвал его "yii".В корневой директории создайте папку base. Скопируйте туда содержимое архива фреймворка (директории framework, requirements и т.д.). После этого, так же в корне, создайте новое приложение с помощью утилиты "yiic" и её команды webapp ( http://www.yiiframework.com/doc/guide/ru/quickstart.first-app ). Дизайн Возьмите архив с дизайном (приложен к статье). В нём есть директории fonts, images и файл style.css. Скопируйте их в корень сайта. Теперь пройдите в директорию /protected/views/layouts и откройте файл main.php. Здесь содержится глобальный внешний вид для всех без исключения страниц. Сначала просто скопируйте в этот шаблон HTML-содержимое нашего дизайна ( из архивного index.html ). После этого, при обращении к корню хоста, Вы должны увидеть совершенно другую картину. Главная переменная в layout`e это $content. Туда помещается содержимое текущей страницы. Давайте затрём приветствие HTML: <h2>Здравствуйте уважаемый гость!</h2> <p> Почему мы призываем вас завести блог именно у нас? Блог - это то место, где можно делиться мыслями, публиковать статьи, фотографии, общаться с друзьями или просто рассказывать о своей жизни. Создай личный дневник только для друзей или заведи блог на тему, которая тебя интересует. На нашем сайте Вы найдёте новых друзей, сможете бесконечно общаться с ними. </p> и за место него вставим отображение этой переменной PHP: <?=$content?> У нас в приложении главным контроллером является Site. В нём есть действие indexAction, которое отображает соответствующий шаблон. Откроем его ( /protected/views/site/index.php ) и за место содержимого поместим ранее вырезанную часть layout`a. Обновите страницу. Внешне никаких изменений произойти недолжно. Локализация Локализацией ( http://www.yiiframework.com/doc/guide/ru/topics.i18n ) мы займёмся именно сейчас. Почему, Вы поймёте по ходу чтения этого раздела. Для начала настроим исходный язык приложения (sourceLanguage) и пользовательский (просто language). Для этого откройте файл конфигураций /protected/config/main.php и после параметров "basePath" и "name" добавьте соответствующие ячейки: PHP: 'sourceLanguage' => 'en_US', 'language' => 'ru_RU', Так мы указываем приложению что в качестве исходного языка используется английский, а требуется русский. Обратите внимание на то что если эти языки будут совпадать то никакой перевод задействован не будет. Непосредственно преобразование английских фраз в русские мы будем осуществлять с помощью метода t, класса Yii, которому нужно передать категорию фразы, и саму фразу ( http://www.yiiframework.com/doc/api/YiiBase#t-detail ). Метод покопается в языковых файлах и вернёт текст перевода. Сами фразы мы будем хранить в виде массива, в php-файлах ( хотя возможны варианты, например хранение фраз в БД). Пройдите в директорию /protected/messages/ и создайте там папку ru_RU. В ней должны находиться файлы категорий с русскоязычными текстами. Мы будем пользоваться всего одной категорией ( естественно в настоящем приложении их несколько ) - common. Создайте для неё файл common.php со следующим содержимым: PHP: return Array( ); Поместим в него фразы из нашего приветствия с главной страницы. Отдельно фразу "Здравствуйте уважаемый гость!" и текст приветствия. PHP: <?php return Array( "hello_guest" => "Здравствуйте уважаемый гость!", "welcome" => "Почему мы призываем вас завести блог именно у нас? Блог - это то место, где можно делиться мыслями, публиковать статьи, фотографии, общаться с друзьями или просто рассказывать о своей жизни. Создай личный дневник только для друзей или заведи блог на тему, которая тебя интересует. На нашем сайте Вы найдёте новых друзей, сможете бесконечно общаться с ними. " ); Теперь снова обратимся к отображению действия indexAction, и фразу приветствия заменим на PHP: <?=Yii::t("common","hello_guest")?> а текст приветствия на PHP: <?=Yii::t("common","welcome")?> Если Вы всё сделали правильно, то после этих манипуляций главная страница опять должна остаться без изменений. Такую работу мы будем проводить со всеми англоязычными фразами. Во избежание лишней траты времени я не стал локализировать надписи в формах, меню и т.д. Пусть сразу будут русскими. .htaccess Сейчас мы сделаем так чтоб URL у нас были не такие "index.php?r=path/path", а такие - "/path/path". Для этого откройте конфигурационный файл и в ячейку components добавьте PHP: 'urlManager'=>array( 'urlFormat'=>'path', ), А в корень сайта положите файл .htaccess с содержимым Code: Options +FollowSymLinks IndexIgnore */* RewriteEngine on # if a directory or a file exists, use it directly RewriteCond %{REQUEST_FILENAME} !-f RewriteCond %{REQUEST_FILENAME} !-d # otherwise forward it to index.php RewriteRule . index.php Более подробную информацию по изменению URL Вы можете найти здесь - http://www.yiiframework.com/doc/guide/ru/topics.url База данных Займёмся базой данных. Её SQL-код лежит в архиве database.zip. Создайте базу yii и выполните этот код в ней. Теперь следует указать фреймворку с какой базой ему нужно работать и как с ней соединяться. Снова обратимся к файлу /protected/config/main.php и раскомментируем в нём строки PHP: /* 'db'=>array( 'connectionString'=>'Your DSN', ), */ Далее, за место "Your DSN" вставим строку соединения с БД. У меня это "mysql:host=localhost;dbname=yii". Добавим в этот массив ещё несколько строк: PHP: 'class' => 'CDbConnection', // Выбираем класс для работы с БД 'username' => 'root', // Имя пользователя 'password' => '', // Пароль 'charset' => 'utf8' // Кодировка Измените их значения под свой сервер.
Регистрация Самое первое что мы должны сделать на нашем сайте - соорудить регистрацию и авторизацию. Для этого мы модифицируем уже существующий у нас контроллер Site и создадим модель Users. Создание модели лучше всего производить с помощью утилиты yiic. Для этого запустите yiic с параметром shell и указанием индексного файла нашего приложения. После этого выполните команду "model Users". Создастся модель которую мы используем для работы с соответствующей таблицей. Сначала сделаем регистрацию. Объявите для этого метод actionRegister в контроллере Site. Впишем туда отображение формы регистрации. Она будет лежать в шаблоне /protected/views/site/register.php и иметь следующее содержимое. HTML: <h2>Регистрация</h2> <form action="/site/register/" method="POST"> <table width="100%"> <tr> <td>Имя пользователя</td> <td><input type="text" name="Users[login]" class="long_field" ></td> </tr> <tr> <td>E-mail</td> <td><input type="text" name="Users[email]" class="long_field" ></td> </tr> <tr> <td>Пароль</td> <td><input type="password" name="Users[password]" class="long_field"></td> </tr> <tr> <td colspan="2"> <input type="submit" value=" Зарегистрироваться " /> </td> </tr> </table> </form> Код обработки этого шаблона будет простой. PHP: $this->render("register"); Теперь пройдите по ссылке http://yii/site/register/. Вы должны увидеть форму регистрации из трёх полей. Как видно из шаблона, форма отсылает данные методом POST на этот же адрес. То есть при обращении к действию register контроллер должен как-то проверить - пришла форма, или это простое обращение. Такую проверку мы будем осуществлять проверяя существование массива $_POST['Users']. Если он есть - нам пришли данные из формы. В случае регистрации мы просто создадим новый объект модели Users, укажем необходимые поля и сохраним изменения. PHP: $users = new Users; // Заполняем поля $users->login = $_POST['Users']['login']; $users->password = md5($_POST['Users']['password']); $users->email = $_POST['Users']['email']; // Сохраняем данные $users->save(); После регистрации пользователю нужно сообщить об успешности этой операции. Мы воспользуемся Flash-сообщениями. Сначала внесём соответствующий текст в языковой файл, в ячейку "registered" PHP: "registered" => "Поздравляем! Вы зарегистрированы!" А затем объявим его в контроллере, вызывав метод setFlash ( http://www.yiiframework.com/doc/api/CWebUser#setFlash-detail ). PHP: Yii::app()->user->setFlash("registered",Yii::t("common","registered")); Здесь мы создали у текущего пользователя сообщение об успешной регистрации с идентификатором "registered". Осталось только шаблон формы изменить так чтоб она не отображалась если есть Flash-сообщение. В этом нам помогут методы hasFlash и getFlash этого же объекта. Первый определяет есть ли нужное нам сообщение, а второй это сообщение возвращает. HTML: <h2>Регистрация</h2> <?if(Yii::app()->user->hasFlash("registered")):?> <p> <?=Yii::app()->user->getFlash("registered")?> </p> <?else:?> <form action="/site/register/" method="POST"> <table width="100%"> <tr> <td>Имя пользователя</td> <td><input type="text" name="Users[login]" class="long_field" ></td> </tr> <tr> <td>E-mail</td> <td><input type="text" name="Users[email]" class="long_field" ></td> </tr> <tr> <td>Пароль</td> <td><input type="password" name="Users[password]" class="long_field"></td> </tr> <tr> <td>Подтверждение пароля</td> <td><input type="password" name="Users[password_confirm]" class="long_field"></td> </tr> <tr> <td colspan="2"> <input type="submit" value=" Зарегистрироваться " /> </td> </tr> </table> </form> <?endif;?> А вот код который должен у нас получится в действии actionRegister. PHP: if(isset($_POST['Users'])) { $users = new Users; // Сохраняем поля $users->login = $_POST['Users']['login']; $users->password = md5($_POST['Users']['password']); $users->email = $_POST['Users']['email']; // Сохраняем данные $users->save(); // Устанавливаем сообщение Yii::app()->user->setFlash("registered",Yii::t("common","registered")); } $this->render("register"); Следующим шагом мы добавим валидацию ( http://www.yiiframework.com/doc/guide/form.model#declaring-validation-rules ). Нам нужно проверить отсутсвие логина и указанного почтового адреса в базе. Для этого откроем файл /protected/models/User.php и посмотрим метод "rules". Там сейчас 3 правила - максимальная длинна логина 30 символов, пароля - 32, email`a - 50. Мы внесём сюда 5 правил - поля почтового адреса и логина не должны быть пустыми, содержимого этих полей в БД быть не должно, почтовый адрес должен иметь правильный формат. PHP: return array( // логин и почта должны быть введены array('login','required','on'=>'register','message'=>Yii::t('common','login_empty')), array('email','required','on'=>'register','message'=>Yii::t('common','email_empty')), // Они должны быть уникальными array('login','unique','on'=>'register','message'=>Yii::t('common','login_in_base')), array('email','unique','on'=>'register','message'=>Yii::t('common','email_in_base!')), // Почтовый адрес должен иметь соответствующий формат array('email','email','on'=>'register','message'=>Yii::t('common','email_no_right_format')) ); Внесём в языковой файл соответствующие переводы. PHP: "login_empty" => "Поле логина не должно быть пустым", "email_empty" => "Поле почтового адреса не должно быть пустым", "login_in_base" => "Введёный логин присутсвует среди зарегистрированных пользователей", "email_in_base" => "Введёный e-mail присутсвует среди зарегистрированных пользователей", "email_no_right_format" => "Вы ввели не правильный email" Теперь модифицируем контроллер. Во-первых, объявим массив куда будут складываться все ошибки валидации. Назовём его $errors. Во-вторых, перед сохранением данных мы вызовем метод "validate". И в третьих, мы осуществим обработку метода getErrors, модели, так чтоб в $errors поместились все имеющиеся ошибки. Затем мы передадим массив с ошибками в обработчик шаблона. PHP: $errors = Array(); if(isset($_POST['Users'])) { $users = new Users; // Сохраняем поля $users->login = $_POST['Users']['login']; $users->password = md5($_POST['Users']['password']); $users->email = $_POST['Users']['email']; // Проверяем данные $users->validate('register'); // Если ошибки есть if( $users->hasErrors() ) { // Помещаем их текст в массив $errors foreach($users->getErrors() as $field) $errors[] = $field[0]; } // Если массив ошибок пуст if(!count($errors)) { // Сохраняем данные $users->save(); // Устанавливаем сообщение Yii::app()->user->setFlash("registered",Yii::t("common","registered")); // Обновляем страницу $this->refresh(); // Если ошибки есть то показываем форму регистрации и передаём их в шаблон } } $this->render('register',Array('errors' => $errors)); В шаблон же мы добавим код который высветит все ошибки, если таковые есть. Его следует поместить прямо перед начальным тегом <form>. HTML: <?if(count($errors)):?> Ошибки: <br /> <?foreach($errors as $error):?> &amp;amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;amp;nbsp;<b><?=$error;?></b><br /> <?endforeach;?> <?endif;?> <br /> Теперь, если Вы всё сделали правильно, при отправке пустой формы покажутся соответствующие сообщения.
Авторизация Для авторизации мы будем использовать метод actionLogin, контроллера Site и шаблон /protected/views/site/login.php, содержимое которого мы поменяем на HTML: <div id="bodyPanel"> <h2>Войдите</h2> <form action="/site/login/" method="POST"> <table width="100%"> <tr> <td width="20%">Имя пользователя</td> <td><input type="text" name="Users[login]" class="long_field"></td> </tr> <tr> <td width="20%">Пароль</td> <td><input type="password" name="Users[password]" class="long_field"></td> </tr> <tr> <td colspan="2"> <input type="submit" value=" Войти " /> </td> </tr> </table> </form> </div> Так же придётся сменить содержимое метода "authenticate" компонента UserIdentity (/protected/components/UserIdentity.php). Этот метод содержит код благодаря которому пользователи могут входить на сайт. Работает он сейчас с двумя аккаунтами, информация о которых внесена непосредственно внутрь метода. Заменим его на код который будет брать информацию из базы данных. Сначала объявим два новых свойства: PHP: private $_id; private $_name; Затем поменяем полностью код метода "authenticate": PHP: // Получаем имя пользователя и пароль $username =$this->username; $password = md5($this->password); // Ищем их в БД $user = Users::model()->find("login=? AND password=?", Array($username,$password)); // Если пользователь не найден возвращаем false if($user === null) return false; // Если найден то заполняем свойства класса $this->_id = $user->id; $this->_name = $user->login; // и возвращаем true return true; И опишем ( а точнее перегрузим уже существующие ) два дополнительных общедоступных метода, возвращающих вышеуказанные свойства. PHP: public function getId() { return $this->_id; } public function getName() { return $this->_name; } Вернёмся к контроллеру. Затрите код его метода actionLogin и поместите туда следующее PHP: if(isset($_POST['Users'])) { // Авторизируем пользователя $identity = new UserIdentity($_POST['Users']['login'],$_POST['Users']['password']); // Если он не авторизирован if( $identity->authenticate() ) { // Если всё хорошо то заносим в данные // пользователя информацию об авторизации Yii::app()->user->login($identity); // И перемещаем его на главную страницу $this->redirect("/"); } // Если форма не отправлена то просто отображаем её } else $this->render('login'); Всё, теперь в "Yii::app()->user" ( http://www.yiiframework.com/doc/api/CWebUser ) буде содержаться информация о вошедшем пользователе. А его имя и номер мы получим с помощью "Yii::app()->user->getName()" и "Yii::app()->user->getId()" соответственно. Помните надпись "Здравствуйте гость!" на главной? Давайте сделаем так, чтобы она менялась в зависимости от того авторизирован пользователь или нет. Добавим в языковой файл соответствующую фразу. PHP: "hello_user" => "Здравствуйте {login}!", А индексную страницу изменим вот так. HTML: <h2> <?if(Yii::app()->user->isGuest):?> <?=Yii::t("common","hello_guest")?> <?else:?> <?=Yii::t("common","hello_user",Array("{login}" => Yii::app()->user->getName()))?> <?endif;?> </h2> <p> <?=Yii::t("common","welcome")?> </p> Здесь мы проверяем - если пользователь является гостем то выводим сообщения для гостей, а если нет - выводим фразу приветствия, указывая в качестве параметра "login" (который содержится в фразе) логин пользователя. И немного модифицируем главное меню. Пусть авторизированный пользователь видит ссылки на главную, на выход и на создание новой записи. Для этого, в layout`e, заменим код HTML: <ul> <li><a href="/site/login">Вход</a></li> <li><a href="/site/register/">Регистрация</a></li> <li><a href="/">Главная</a></li> </ul> на HTML: <ul> <?if(Yii::app()->user->isGuest):?> <li><a href="/site/login/">Вход</a></li> <li><a href="/site/register/">Регистрация</a></li> <?else:?> <li><a href="/site/logout/">Выход</a></li> <li><a href="/records/create/">Написать</a></li> <?endif;?> <li><a href="/">Главная</a></li> </ul> Регистрируемся, входим и проверяем как изменяется главная страница.
Записи Теперь займёмся основной частью нашего сайта - ведением записей. Всю нужную работу будет выполнять контроллер с именем Records. Создайте его с помощью утилиты yiic командой "crud Records" ( http://www.yiiframework.com/doc/guide/ru/quickstart.first-app и http://www.yiiframework.com/doc/guide/ru/topics.console ). Не забудьте предварительно создать и соответствующую модель, иначе контроллер просто не сможет быть создан и в командную строку выплеснется множество ошибок. Откроем файл /protected/controllers/RecordsController.php и обратим внимание на его методы. Первый из интересующих нас - "accessRules" (http://www.yiiframework.com/doc/guide/ru/topics.auth#access-control-filter). Здесь содержатся правила доступа к действиям контроллера. Сразу после создания они следующие: 1. Любым пользователям разрешено просматривать записи в виде списка и по отдельности (методы list и show) 2. Авторизированным пользователям разрешено добавлять записи и изменять их (методы create, update) 3. Администраторам разрешены функции администрирования и удаления (методы admin, delete) 4. Всё остальное всем запрещено В принципе, нам ничего тут менять не нужно. Только лишь передать права удаления авторизированным пользователям и стереть из прав действие admin. Его функционал будет заменён, поэтому можете удалить и само действие. Мы сделаем так, чтоб каждый смог управлять своими записями, в том числе и удалять их. Действие create. В самом начале происходит проверка наличия отправленных пользователем данных. Если таковые присутствуют то они вносятся в базу, в ином случае - обрабатывается шаблон "create". Он содержит в себе пару ссылок и вызов шаблона "_form". Отображению "_form", в свою очередь, передаётся параметр update. Почти такой-же код есть и в шаблоне обновления сообщения. Вообщем, здесь для двух действий используется одна и та же форма, только передаются ей, при обработке, разные значения параметра "update". Код кнопок можете спокойно затирать, оставьте лишь вызов формы. А код отображаемого шаблона _form заменим на следующий. HTML: <?if($update):?> <h2>Обновление записи</h2> <?else:?> <h2>Создание новой записи</h2> <?endif;?> <div class="actionBar"> </div> <?if($update):?> <form action="/records/update/" method="POST"> <?else:?> <form action="/records/create/" method="POST"> <?endif;?> <table width="100%"> <tr> <td width="20%"><span class="text">Название записи</span></td> <td> <input type="text" name="Records[title]" class="long_field" value="<?=$model->title?>"/> </td> </tr> <tr> <td colspan="2"><span class="text">Текст записи:</span></td> </tr> <tr> <td colspan="2"><textarea name="Records[text]" class="long_textarea" rows="20"><?=$model->text?></textarea></td> </tr> <tr> <td colspan="2"> <input type="submit" name="createRecord" value="Добавить запись"> </td> </tr> </table> </form> Здесь мы отправляем 2 параметра - название записи и её содержимое. Но в базе есть ещё 3 поля - время публикации, номер автора и количество просмотров. Добавим их в POST-массив перед помещением данных в модель, и уже всё вместе занесём в БД. Вот код который должен быть вызван в том случае если массив $_POST["Records"] обнаружен. PHP: $_POST['Records']['pub_date'] = time(); $_POST['Records']['author_id'] = Yii::app()->user->getId(); $_POST['Records']['views'] = 0; // Вносим все данные в модель $model->attributes=$_POST['Records']; // Сохраняем if($model->save()) $this->redirect(array('show','id'=>$model->id)); После сохранения пользователь будет перенесён на действие show, контроллера Records, передав в качестве параметра id номер новоиспечённой записи (ссылка типа /records/show/id/5/). Метод actionShow. Он отображает запрошенную запись, в зависимости от указанного id. Его код состоит из одной строки. В ней вызывается соответствующий шаблон, принимая результат работы метода loadRecords. В отображении этот результат обрабатывается, и полученные данные выводятся пользователю. Всё идеально, но есть небольшая загвоздка. В данных сообщения нет одного - имени автора. Есть только его идентификатор (поле author_id). Давайте настроим связь между моделями Records и Users так, чтоб при запросе одной или нескольких записей автоматически запрашивались имена их авторов ( http://www.yiiframework.com/doc/guide/ru/database.arr#declaring-relationship ). Для этого в первой модели найдите метод "relations" и в возвращаемый им массив внесите ячейку "author", куда будет помещена связь со второй моделью. PHP: 'author' => array(self::BELONGS_TO,'Users','author_id'), Теперь в каждой записи кроме основных полей будет добавлено поле author, содержащее результат работы запроса "SELECT * FROM users WHERE id=$author_id". В отображение все эти данные поступят вместе с остальными, так что нам нужно всего лишь напечатать свойство "$model->author->login". Вот полный код шаблона show. HTML: <?if($model === null):?> Запрашиваемой записи не существует. <?else:?> <h2><?php echo $model->title; ?>(<?=date("d-m-Y",$model->pub_date)?>/<?=$model->author->login;?>)</h2><br /> <p><?=$model->text;?></p> <!-- Если текущий пользователь является автором этого сообщения то выводим ссылки "Редактировать" и "Удалить" --> <?if($model->author_id == Yii::app()->user->getId()):?> <a href="/records/edit/id/<?=$model->id?>/">Редактировать</a> &amp;amp;amp;amp;amp;nbsp;|&amp;amp;amp;amp;amp;nbsp; <a href="/records/delete/id/<?=$model->id?>/">Удалить</a> <?endif;?> <?endif;?> Ничего сверхъестественного. Попробуйте добавить запись и просмотреть её. Метод actionList - просмотр списка записей. Разберём всё что в нём делается. Сначала создаётся объект класса CDbCriteria ( http://www.yiiframework.com/doc/api/CDbCriteria ). С помощью него, при выборке данных из базы, указывается сколько записей нужно выбрать ( количество указано в константе PAGE_SIZE нашего контроллера ). Затем эти данные извлекаются. Они передаются в отображение и выводятся на экран. В конце создаётся объект класса CPagination ( http://www.yiiframework.com/doc/api/CPagination ), отвечающий за разбиение материала на страницы. В самом действии почти ничего не нужно менять. Надо внести лишь 2 небольших изменения. Во-первых, значение константы PAGE_SIZE измените на 3. Во-вторых изменим код самого контроллера так, чтобы он показывал список не всех сообщений, а того пользователя, чей идентификатор передан в параметре "user_id" ( ссылка будет выглядеть вот так - http://yii/records/list/user_id/3 ). В самое начало кода добавим обработку поступившего номера пользователя. PHP: $user_id = (int) $_GET['user_id']; А сразу после создания объекта класса CDbCriteria заполним его свойство "condition", содержащее условия для выборки. PHP: $criteria->condition = "author_id={$user_id}"; Обратите внимание вот на что. В начале шаблона ( то есть до обработки полученных данных ) мы должны вывести надпись "Список сообщений пользователя...". Для того чтоб получить имя автора можно написать в модели соответствующую функцию, вызвать её, передав в качестве исходных данных номер пользователя в базе, и результат отдать в отображение. А можно взять его сразу из массива сообщений ещё до их обработки. Например, из нулевой записи PHP: $models[0]->author->login Возможно первый вариант в чём-то лучше, но мы будем использовать второй - он проще и быстрее. На очереди шаблон "list". Сотрите из него весь код и внесите туда вот что. HTML: <h2>Список сообщений пользователя <?=$models[0]->author->login;?></h2><br /> <?php foreach($models as $n=>$model): ?> <h2><?php echo $model->title; ?>(<?=date("d-m-Y",$model->pub_date)?>)</h2> <p><a class="text" href="/records/show/id/<?=$model->id;?>"><?=substr($model->text,0,200);?>...</a></p> <br /> <?endforeach;?> <!--шаблон страниц--> <?php $this->widget('CLinkPager',array('pages'=>$pages)); ?> Здесь мы сначала сообщаем чей список записей в данный момент просматривает пользователь, а затем выводим и сами сообщения, отображая в виде анонса 200 первых символов текста. Можете сейчас пройти по ссылке http://yii/records/list/user_id/5 и Вы увидите блог пользователя Николай. В правом нижнем углу страницы можно заметить перекорёженое изображение постраничной навигации. Оно выводится виджетом CLinkPager. Здесь мы напоролись на один из минусов Yii. По каким-то причинам отображений у CLinkPager нет. HTML-код, который Вы видите, находится прямо в PHP-коде виджета. Исправим это. Откройте файл ClinkPager.php, находящийся в директории /base/framework/web/widgets/pagers/. Нас интересует методы run и createPageButton. Первый собирает общий html-код и выводит его на экран. Второй - формирует код кнопок. Переделаем код под более-менее стандартный вид. Для этого мы создадим дерикторию views в папке с виджетом, и поместим туда 2 шаблона. Первый - pageButton.php. Он содержит код одной ссылки на страницу. HTML: <?if(!$hidden):?> <?if($selected):?> <b><?=$label?></b>&amp;amp;amp;amp;amp;nbsp;&amp;amp;amp;amp;amp;nbsp; <?else:?> <a class="text" href="/records/list/user_id/<?=(int)$_GET['user_id']?>/page/<?=$page+1?>"><?=$label?></a> <?endif;?> <?endif;?> Второй - pagesLayout, содержащий код отбражения страниц. HTML: <br /> <br /> <br /> <br /> <center> <p> <?foreach($buttons as $button):?> <?=$button?> <?endforeach;?> </p> </center><br /><br /> Теперь обратимся к методу createPageButton. Вначале, в зависимости от переменных $hidden и $selected формируется класс тега <li>, затем сам тег собирается и возвращается в виде результата. Заменим всё это одной строкой. Возвратим обработанный шаблон нашей кнопки, куда передадим все поступившие в функцию данные. В этом нам поможет метод renderPartitional ( http://www.yiiframework.com/doc/api/CController#renderPartial-detail ), класса CController - он не выводит обработанное отображение, а возвращает его. PHP: return Ccontroller::renderPartial("pageButton",Array('page'=>$page,'label'=>$label,'hidden'=>$hidden, 'selected'=>$selected), true); В методе run мы просто сотрём весь код начиная с объявления переменной $htmlOptions. За место него вызовем всё тот-же renderPartitional, передав ему код кнопок. PHP: echo CController::renderPartial("pagesLayout",Array("buttons"=>$buttons)); Всё, после этих манипуляций виджет работает с шаблонами и отображает нормальную по виду строку с выбором страниц. Осталось только осуществить перевод слов типа Next, Last и т.д. Для этого откроем наш языковой файл и внесём туда следующие ячейки. PHP: "Next &amp;amp;amp;amp;gt;" => "Следующая &amp;amp;amp;amp;gt;", "&amp;amp;amp;amp;lt; Previous" => "&amp;amp;amp;amp;lt; Предыдущая", "&amp;amp;amp;amp;lt;&amp;amp;amp;amp;lt; First" => "&amp;amp;amp;amp;lt;&amp;amp;amp;amp;lt; Первая", "Last &amp;amp;amp;amp;gt;&amp;amp;amp;amp;gt;" => "Последняя &amp;amp;amp;amp;gt;&amp;amp;amp;amp;gt;", "Go to page:" => "Страницы:", А в методе run, виджета, в самом начале, изменим категории в вызовах метода t, с "yii" на "common" PHP: if($this->nextPageLabel===null) $this->nextPageLabel=Yii::t('common','Next &amp;amp;amp;amp;gt;'); if($this->prevPageLabel===null) $this->prevPageLabel=Yii::t('common','&amp;amp;amp;amp;lt; Previous'); if($this->firstPageLabel===null) $this->firstPageLabel=Yii::t('common','&amp;amp;amp;amp;lt;&amp;amp;amp;amp;lt; First'); if($this->lastPageLabel===null) $this->lastPageLabel=Yii::t('common','Last &amp;amp;amp;amp;gt;&amp;amp;amp;amp;gt;'); if($this->header===null) $this->header=Yii::t('common','Go to page: '); Теперь постраничная навигация отображается по-русски. Изменение записей. Редактирование сообщений осуществляется через метод actionUpdate. Как Вы помните из описания метода actionCreate, он использует форму "_form". Единственное, что здесь требует редактирования, это шаблон update. Удалите там всё кроме вызова формы. PHP: <?php echo $this->renderPartial('_form', array('model'=>$model, 'update'=>true,)); ?> Для того чтобы пользователи не могли редактировать чужие сообщения можно сразу после объявления переменной $model, в методе actionUpdate, вставить одну строку. PHP: if($model->author_id != Yii::app()->user->getId()) $this->redirect("/"); Она сверит номер автора сообщения с номером текущего пользователя и в случае их несовпадения завершит работу, перебросив пользователя на главную страницу. Действие delete. В нём требуется только вставить проверку авторства пользователя и исправить редирект после удаления. Для этого мы в начало метода внесём следующий код. PHP: $record=$this->loadRecords(); if($record->author_id != Yii::app()->user->getId()) $this->redirect("/"); А код редиректа заменим на такой. PHP: $this->redirect(array('list','user_id'=>$record->author_id)); Вот и всё. Можно приступить к следующему этапу.
Комментарии. На нашем сайте пользователи (как авторизированные так и неизвестные) должны иметь возможность комментировать каждую запись. Для этого мы создадим контроллер Comments. По аналогии с контролером записей нужно воспользоваться утилитой yiic и командами model и crud. После создания контроллера откройте его и подредактируйте правила доступа следующим образом. В разрешённые всем действия добавьте create. Действия update, delete и admin удалите. Представим что всех их будет выполнять либо администратор ресурса, либо владелец блога. Затем откройте отображение show, контроллера Records. После кода отображения записи поместите код формы, в которую посетитель будет вносить текст комментария. HTML: <br /> <br /> <br /> <h3>Оставьте свой отзыв!</h3> <form action="/comments/create/" method="post"> <input type="hidden" name="Comments[post_id]" value="<?=$model->id?>" /> <table width="100%"> <tr> <td>Текст комментария:</td> </tr> <tr> <td><textarea name="Comments[text]" class="long_field"></textarea></td> </tr> <tr> <td><input type="submit" value="Сохранить" /></td> </tr> </table> </form> Далее нужно изменить сам контроллер. А именно - метод actionCreate. Изначально он выполняет сохранение тех данных которые приходят к нему из формы. Но в форме у нас всего два поля - комментарий и номер записи. А в таблице должны быть заполнены ещё столько же - дата публикации и идентификатор автора. Поэтому, прямо перед передачей POST-параметров в модель PHP: $model->attributes = $_POST['Comments']; мы создадим в POST-массиве две дополнительные ячейки. В одной будет номер автора (или 0 в случае неавторизированного гостя), а во второй - текущее время в формате UNIX TIMESTAMP. PHP: $_POST['Comments']['author_id'] = (Yii::app()->user->getId()) ? Yii::app()->user->getId() : 0; $_POST['Comments']['pub_date'] = time(); Как всегда, изменим и код редиректа. Сейчас он перенаправляет пользователя на метод show, дабы тот увидел то что он опубликовал. Но нам нужно показать посетителю не именно то что он оставил, а комментируемую запись и все сообщения комментаторов. Для этого мы перебросим его на метод show контроллера create, поместив в ссылку номер комментируемой записи. PHP: $this->redirect("/records/show/id/{$model->post_id}"); Следующим шагом нужно организовать отображение всех комментариев которые есть у записи. Сделать это можно двумя путями - в контроллере Records, в методе actionShow, получить список отзывов о записи (например, описать для этого соответствующую функцию в модели Comments). А можно настроить ещё одну связь в модели Records с моделью комментариев. Мы пойдём вторым путём и в массив, возвращаемый методом relations поместим вот такую ячейку. PHP: 'comments' => array(self::HAS_MANY,'Comments','post_id') Так мы укажем приложению что у каждой записи имеется ( возможно ) множество комментариев, которые должны отбираться в модели Comments, по полю post_id. Все они будут помещаться в итоговую модель, в соответствующую ячейку. Но в самих комментариях нам нужно отображать ещё и имя их автора. Для этого применим связь которую уже объявляли при работе с записями - свяжем модель комментариев и модель пользователей по полю author_id. PHP: 'author' => array(self::BELONGS_TO,'Users','author_id'), Осталось лишь отображение. Для этого в шаблон show поместите следующий код, прямо между формой отзыва и текстом записи. HTML: <br /><br /><br /> <h3>Комментарии:</h3> <?foreach($model->comments as $comment):?> <b> <?if(isset($comment->author->login)):?> <?=$comment->author->login?> <?else:?> Гость <?endif;?> (<?=date('d-m-Y',$comment->pub_date)?>) </b><br /> <?=$comment->text?><br /><br /> <?endforeach;?> Теперь у наших блогов есть ещё и комментарии. Кэширование. Ну и под конец коснёмся темы кэширования. Оно в Yii ( http://www.yiiframework.com/doc/guide/ru/caching.overview ) может быть реализовано с помощью пяти компонентов - memCache, ACP, XCache, EAcceleratior и CDbCache. Для всех них создан один уникальный класс-оболочка - CCache ( http://www.yiiframework.com/doc/api/CCache#get-detail ). Через него можно работать со всеми пятью компонентами через один и тот же интерфейс. Мы воспользуемся кэшированием с использованием базы данных ( CDbCache ). Для активизации компонента требуется открыть главный конфигурационный файл, и в массив ячейки "components", добавить ячейку "cache" со следующим содержимым PHP: 'cache' => Array('class' => 'system.caching.CDbCache'), Более ничего делать не нужно. Давайте применим кэширование к показу сообщений в блоге. Кэшировать мы будем данные получаемые из БД ( работа с данными - http://www.yiiframework.com/doc/guide/ru/caching.data ). Задействовать нам нужно всего 3 метода - get, set и delete. Они принадлежат объекту возвращаемому Yii::app()->cache. В качестве основного параметра им нужно передать специальный идентификатор ( обычное имя под которым кусок информации будет записан, удалён или получен из кэша ). Из названий этих методов понятно что первый проверяет наличие определённых данных, второй эти данные устанавливает, а третий - удаляет. Откроем контроллер ответственный за записи в блогах и обратимся к методу actionShow. В нём мы будем кэшировать данные поступающие из базы. Изменим код следующим образом. В начале мы получим номер запрашиваемой записи - он находится в параметре id. Далее, мы проверим - если кэш записи уже есть, то достанем его и передадим в отображение. Если нет - вызовем метод loadRecords, сохраним в кэше его результат и передадим в отображение. Ничего сложного. PHP: $id = (int) $_GET['id']; if(Yii::app()->cache->get("show_{$id}") === false) { $model = $this->loadRecords(); Yii::app()->cache->set("show_{$id}",$model); } else $model = Yii::app()->cache->get("show_{$id}"); $this->render('show',array('model'=>$model)); Но ведь содержимое сообщения может изменяться. При этом старый кэш нужно будет удалять. В таких случаях следует описать вызов метода delete. У нас они находятся в действиях редактирования и удаления. В "update" мы заменим код PHP: if($model->save()) $this->redirect(array('show','id'=>$model->id)); на PHP: if($model->save()) { Yii::app()->cache->delete("show_{$model->id}"); $this->redirect(array('show','id'=>$model->id)); } А в "delete", сразу перед редиректом мы вставим следующую строку: PHP: Yii::app()->cache->delete("show_{$record->id}"); Всё, эта часть закончена. Теперь рассмотрим работу с фрагментом страницы ( http://www.yiiframework.com/doc/guide/ru/caching.fragment ). Это ещё проще. Здесь задействуются методы beginCache и endCache класса CController (то есть можно будет к нему обращаться в отображении через $this). Мы должны вызвать их в начале и в конце того куска страницы который должен быть записан. При обращениях пользователей, если фрагмент уже находится в кэше он будет извлечён и показан. Если же нет, то он выполнится и запишется. Этим фрагментом будет код отвечающий за показ комментариев в отображении records/show. В качестве кэш-идентификатора у нас будет фраза "comments_page_номер_страницы". Прямо перед заголовком "Комментарии" поместите следующий код: PHP: <?if($this->beginCache("comments_page_{$model->id}")):?> А после цикла foreach, и трёх переносов строк, вызов окончания кэширования. PHP: <?$this->endCache("comments_page_{$model->id}");endif;?> Ну и удаление кэша мы вызовем в контроллере Comments, в действии create, сразу перед редиректом. PHP: Yii::app()->cache->delete("comments_page_{$_POST['Comments']['post_id']}"); Всё, теперь у нас кэшируются и записи, и комментарии. Как видите, всё очень легко. Заключение. На мой взгляд Yii, в отличие от многих других фреймворков, является инструментом с высоким порогом вхождения. Понять его основы, логику, принципы и зависимости в начале довольно трудно. Так же мне кажется что утверждение разработчиков о том, что Yii можно использовать для любых приложений, не верно. Всё-таки он больше подходит именно для веб-2.0-приложений ( как его позиционируют например на сайте журнала phpInside ). Если разобраться то оказывается что по функционалу фреймворк очень хорош, и является одновременно мощным и простым. Если в процессе чтения статьи у Вас что-то не получилось то Вы можете взять готовый код экспериментального приложения в архиве ready.zip. Удачи Вам в Ваших проектах!
Полезная статья о хорошем фреймворке =) Кстати...к слову о rules() в моделе... Если требуется проверить длинну и вывести соответствующее сообщение при не выполнении условия, то это делается так: PHP: <? class Feedback extends CActiveRecord { public static function model($className=__CLASS__) { return parent::model($className); } public function rules() { array('name', 'length', 'min' => 2, 'tooShort' => Yii::t('common','tooShort_name')), array('name', 'length', 'max' => 50, 'tooLong' => Yii::t('common','tooLong_name')), array('name', 'required', 'message' => Yii::t('common','empty_name')), ); } } ...