Авторские статьи Начальный практикум в Yii

Discussion in 'Статьи' started by Kuzya, 10 Jul 2009.

  1. Kuzya

    Kuzya Elder - Старейшина

    Joined:
    27 Apr 2008
    Messages:
    166
    Likes Received:
    106
    Reputations:
    30
    Официальный сайт фреймворка: 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' // Кодировка
    Измените их значения под свой сервер.
     
    #1 Kuzya, 10 Jul 2009
    Last edited: 10 Jul 2009
    3 people like this.
  2. Kuzya

    Kuzya Elder - Старейшина

    Joined:
    27 Apr 2008
    Messages:
    166
    Likes Received:
    106
    Reputations:
    30
    Регистрация
    Самое первое что мы должны сделать на нашем сайте - соорудить регистрацию и авторизацию. Для этого мы модифицируем уже существующий у нас контроллер
    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="&nbsp;Зарегистрироваться&nbsp;" />
    </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="&nbsp;&nbsp;Зарегистрироваться&nbsp;&nbsp;" />
    </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;amp;nbsp;&amp;amp;amp;amp;amp;amp;nbsp;<b><?=$error;?></b><br />
    <?endforeach;?>
    <?endif;?>
    <br />
    Теперь, если Вы всё сделали правильно, при отправке пустой формы покажутся соответствующие сообщения.
     
  3. Kuzya

    Kuzya Elder - Старейшина

    Joined:
    27 Apr 2008
    Messages:
    166
    Likes Received:
    106
    Reputations:
    30
    Авторизация
    Для авторизации мы будем использовать метод 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="&nbsp;&nbsp;Войти&nbsp;&nbsp;" />
    </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>
    Регистрируемся, входим и проверяем как изменяется главная страница.
     
  4. Kuzya

    Kuzya Elder - Старейшина

    Joined:
    27 Apr 2008
    Messages:
    166
    Likes Received:
    106
    Reputations:
    30
    Записи
    Теперь займёмся основной частью нашего сайта - ведением записей. Всю нужную работу будет выполнять контроллер с именем 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;amp;nbsp;|&amp;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;amp;nbsp;&amp;amp;amp;amp;amp;amp;nbsp;
    <?else:?>
    <a class="text" href="/records/list/user_id/<?=(int)$_GET['user_id']?>/page/<?=$page+1?>"><?=$label?></a>&nbsp;&nbsp;
    <?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;amp;gt;" => "Следующая &amp;amp;amp;amp;amp;gt;",
    "&amp;amp;amp;amp;amp;lt; Previous" => "&amp;amp;amp;amp;amp;lt; Предыдущая",
    "&amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;lt; First" => "&amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;lt; Первая",
    "Last &amp;amp;amp;amp;amp;gt;&amp;amp;amp;amp;amp;gt;" => "Последняя &amp;amp;amp;amp;amp;gt;&amp;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;amp;gt;');
    if(
    $this->prevPageLabel===null)
    $this->prevPageLabel=Yii::t('common','&amp;amp;amp;amp;amp;lt; Previous');
    if(
    $this->firstPageLabel===null)
    $this->firstPageLabel=Yii::t('common','&amp;amp;amp;amp;amp;lt;&amp;amp;amp;amp;amp;lt; First');
    if(
    $this->lastPageLabel===null)
    $this->lastPageLabel=Yii::t('common','Last &amp;amp;amp;amp;amp;gt;&amp;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));
    Вот и всё. Можно приступить к следующему этапу.
     
  5. Kuzya

    Kuzya Elder - Старейшина

    Joined:
    27 Apr 2008
    Messages:
    166
    Likes Received:
    106
    Reputations:
    30
    Комментарии.
    На нашем сайте пользователи (как авторизированные так и неизвестные) должны иметь возможность комментировать каждую запись. Для этого мы создадим
    контроллер 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. Удачи
    Вам в Ваших проектах!
     
    1 person likes this.
  6. SQLHACK

    SQLHACK Остались только слоны

    Joined:
    27 Sep 2006
    Messages:
    437
    Likes Received:
    372
    Reputations:
    407
    Кто нибудь осилил ?
     
    _________________________
    1 person likes this.
  7. corpix

    corpix New Member

    Joined:
    28 Apr 2009
    Messages:
    1
    Likes Received:
    0
    Reputations:
    0
    Полезная статья о хорошем фреймворке =)
    Кстати...к слову о 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')),
            );
        }

    }
    ...
     
  8. Kuzya

    Kuzya Elder - Старейшина

    Joined:
    27 Apr 2008
    Messages:
    166
    Likes Received:
    106
    Reputations:
    30
    Спасибо! Буду знать =)