Статьи Начальный практикум в Kohana

Discussion in 'Статьи' started by Kuzya, 31 May 2009.

  1. Kuzya

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

    Joined:
    27 Apr 2008
    Messages:
    166
    Likes Received:
    106
    Reputations:
    30
    Начальный практикум в Kohana
    Официальный сайт фреймворка: http://kohanaphp.com/
    Русскоязычный сайт фреймворка: http://kohanaphp.ru/ (на данный момент крайне плохо проработан, лучше посещать официальный ресурс)
    Версия фреймворка на момент написания статьи: 2.3.2

    К сожалению редактор не позволяет создавать структурированный текст, из-за чего большие части кода порой трудно читаются. В этом случае рекомендую Вам скачать PDF-вариант статьи: http://kuzya.name/files/kohana/practical_intro_kohana.pdf (22 страниц, шрифт Times New Roman, 12pt).

    Здравствуйте. Данная статья, как и все остальные из этой серии, ориентирована на людей которые умеют пользоваться документацией и хотя бы в малой степени с ней ознакомились. Англоязычное описание фреймворка построено довольно хорошо и понятно. По моему можно даже не знать английского языка, а руководствоваться примерами которые приводят авторы. Многие моменты в статье описываться не будут так как уже описаны в документации. В тексте статьи я постарался выставить ссылки на определённые разделы руководства везде где могут возникнуть вопросы. Вообщем, проблем быть не должно. А если Вы ранее работали с Code Igniter, то постигнуть Kohana не составит никакого труда. Ведь это прямой потомок CI, модернизированный и лишённый многих недостатков.
    Думаю стоит отметить что в статье я немного отступаю от стандартов разработки кода в Kohana. В каждый файл разработчики рекомендуют встраивать следующий код.

    PHP:
    defined('SYSPATH') OR die('No direct access allowed.');
    Я его не встраиваю. Просто из привычки. К тому же мне кажется что ничего плохого от этого нет. Вы же можете поступать как захотите.
    Ниже я опишу использование базовых инструментов Kohana на примере создания сайта компании торгующей своим программным продуктом (в качестве подопытного будет программа Bred 3). Сайт будет крайне простой и незамысловатый. Состоять он будет из следующих частей:
    1. Главная страница с новостями
    2. Страница скриншотов программы
    3. Раздел оформления заказов
    4. Гостевая книга
    5. Раздел загрузки программы
    В процессе написания ресурса мы постараемся задействовать как можно больше полезных компонентов фреймворка.

    1. Подготовка
    Первым делом скачайте свежую версию фреймворка. Что интересно, при скачивании Вы можете указать какие модули, библиотеки и языки включать в будущий архив. Дистрибутив, который я использовал при написании этой статьи, не включал в себя никаких дополнительных модулей кроме Archive. Из библиотек я выбрал только SwiftMailer. Ну и в варианте языков кроме английского был выбран русский. После скачивания распакуйте архив на "чистый" хост (у меня он называется kohana, у Вас он может иметь другое имя, учитывайте это в будущем) и обратитесь к нему через браузер. Обращаться следует именно в корень хоста, а не к файлу install.php. Никаких проблем возникнуть не должно и в низу страницы появится надпись "Your environment passed all requirements. Remove or rename the install.php file now." гласящая о том что всё хорошо. Удалите файл install.php и откройте example.htaccess для редактирования. В нём строку

    RewriteBase /kohana/

    измените на

    RewriteBase /

    После чего переименуйте файл в .htaccess. Этим мы включили использование mod_rewrite для красивого вида URL. Теперь пройдите по ссылке http://kohana/welcome/index. Браузер должен отобразить страницу приветствия. Если бы мы не производили вышеописанные манипуляции с .htaccess то тоже самое обращение выглядело вот так http://kohana/index.php/welcome/index. Полную информацию по установке фреймворка Вы найдёте тут: http://docs.kohanaphp.com/installation и тут http://docs.kohanaphp.com/installation/troubleshooting (на случай если возникнут какие-то проблемы).
    Так же нам нужно немного подчистить конфигурацию от значений по умолчанию. Откройте файл /application/config/config.php. Нас интересуют параметры site_domain и index_page. Из комментариев понятно что первый параметр содержит директорию в которой установлена Kohana. Его значение можно либо вообще удалить, либо заменить на одиночный слэш (корень сайта). Второй параметр обозначает индексный файл фреймворка. Так как мы с помощью .htaccess избегаем использования index.php при обращении к сайту, значение этой ячейки тоже нужно очистить. Если этого не сделать то при автоматической генерации ссылок (например при разбитии материала на страницы) это всплывёт и будет серьёзно мешать. Вообщем, придётся изменять в любом случае.
    Теперь займёмся базой данных. Создайте у себя на сервере базу с любым именем. Затем выполните в ней код из файла database.sql. Он создать 2 таблицы - kh_news и kh_gb. Они содержат информацию о новостях и отзывах в гостевой книге соответственно. Для того что бы фреймворк мог нормально к ней подключаться нужно указать необходимые настройки в файле /system/config/database.php. Изначально он имеет следующее содержимое:

    PHP:
    $config['default'] = array
    (
    'benchmark'     => TRUE,
    'persistent'    => FALSE,
    'connection'    => array
    (
    'type'     => 'mysql',
    'user'     => 'dbuser',
    'pass'     => 'p@ssw0rd',
    'host'     => 'localhost',
    'port'     => FALSE,
    'socket'   => FALSE,
    'database' => 'kohana'
    ),
    'character_set' => 'utf8',
    'table_prefix'  => '',
    'object'        => TRUE,
    'cache'         => FALSE,
    'escape'        => TRUE
    );
    Изменим содержимое массива "Connection", его ячейки user, pass, host и database. Подставьте туда свои данные. Вы могли заметить что таблицы у нас имеют префикс "kh_". Укажите его в ячейке "prefix" основного массива. Больше ничего менять не нужно. Более подробная информация по конфигурации БД располагается здесь: http://docs.kohanaphp.com/libraries/database/configuration.
    Возьмёмся за локализацию. Если при скачивании дистрибутива Вы выбирали поддержку русского языка то в дирекотрии /system/i18n должна находится папка ru_RU с языковыми файлами. Зайдите в /system/config и откройте файл locale.php. В нём есть всего одна строка отвечающая за используемый язык. По умолчанию она настроена на работу с английским языком.

    PHP:
    $config['language'] = array('en_US''English_United States');
    Замените её на следующую

    PHP:
    $config['language'] = array('ru_RU''Russian_Russian Federation');
    Теперь фреймворк стал русскоязычным. Полная информация по интернационализации фреймворка - http://docs.kohanaphp.com/general/i18n
    2. Дизайн
    Все необходимые элементы дизайна находятся в соответствующем архиве. В корень хоста, туда где находятся папки application и system, скопируйте директорию images и файл стилей. Находящийся там же шаблон index.php скопируйте в папку application/views, откройте и разбейте на следующие части. Код находящийся с начала шаблона и до открывающего тега "body" поместите в файл header.php.

    Code:
    <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
    <html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="content-type" content="text/html; charset=utf-8" />
    <title>Начальный практикум в Kohana - http://kuzya.name</title>
    <meta name="keywords" content="" />
    <meta name="description" content="" />
    <link href="/styles.css" rel="stylesheet" type="text/css" />
    </head>
    За место него в основной шаблон поставьте код отображения переменной $header.

    PHP:
    <?=$header?>
    Далее поместите в файл menu.php код начинающийся сразу после комментария "start header" и до комментария "start page"

    Code:
    <!-- start header -->
    <div id="header">
    <div id="logo">
    <h1><a href="#">Metamorph_global</a></h1>
    <h2><a href="http://www.metamorphozis.com/" id="metamorph">Design by Metamorphosis Design</a></h2>
    </div>
    <div id="menu">
    <ul>
    <li><a href="/">Главная</a></li>
    <li><a href="/screenshots/">Скриншоты</a></li>
    <li><a href="/buy/">Купить</a></li>
    <li><a href="/gb/">Гостевая</a></li>
    <li><a href="/download/">Скачать</a></li>
    </ul>
    </div>
    </div>
    <!-- end header -->
    <hr />
    На его место вставьте код отображения переменной $menu

    PHP:
    <?=$menu?>
    Ну и всё начиная с комментария "start footer" и до конца файла перенесите в файл footer.php, поместив за место него соответствующий код.

    PHP:
    <?=$footer?>
     
    #1 Kuzya, 31 May 2009
    Last edited: 31 May 2009
  2. gibson

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

    Joined:
    24 Feb 2006
    Messages:
    391
    Likes Received:
    247
    Reputations:
    88
    Тема сисек не раскрыта, пересмотри материал, не нужно "тупо" копипастить со своего ресурса.

    Опиши все по подробней или дай ссылки где про то или иное можно прочитать.

    не добавишь, придеться удалить из за недостатка материала.
     
  3. Kuzya

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

    Joined:
    27 Apr 2008
    Messages:
    166
    Likes Received:
    106
    Reputations:
    30
    3. Программирование
    3.1 Главная страница, новости.

    Новости должны располагаться в виде ленты на главной странице. Для начала сделаем обычный вывод тех шаблонов, которые мы только что сформировали. Создадим контроллер новостей и как действие по умолчанию впишем это отображение (Информация по контроллерам - http://docs.kohanaphp.com/general/controllers и работе с отображениями - http://docs.kohanaphp.com/general/views).

    PHP:
    class News_Controller extends Controller {
    public function 
    index() {
    // Обрабатываем все шаблоны
    $view           = new View('index');
    // Переменный header, menu и footer
    $view->header   = new View('header');
    $view->menu     = new View('menu');
    $view->footer    = new View('footer');

    // Показываем
    $view->render(true);
    }
    }
    Сохраните контроллер в соответствующей папке и обратитесь по ссылке http://kohana/news/. Вы должны увидеть пустой шаблон со словом "text" вместо основного контента.
    [​IMG]

    Из за того что на главной странице всегда должна отображаться лента новостей мы сделаем её контроллер контроллером по умолчанию. Для этого откройте файл /system/config/routes.php и единственную настройку установите в значение "news"

    PHP:
    $config['_default'] = 'news';
    После этого обращения к корню сайта будут автоматически вызывать контроллер новостей.
    Вернёмся к коду. Сейчас нам нужно вывести все имеющиеся новости на экран. Они находятся в базе и надо их оттуда достать. Для этого создадим новую модель (http://docs.kohanaphp.com/general/models) News_Model в файле /application/models/news.php. В неё поместим конструктор создающий объект БД для этой модели и пустой метод который будет получать список новостей. Назовём его "newsList"

    PHP:
    class News_model extends Model {
    public function 
    __construct() {
    parent::__construct();
    // Создаём новый объект БД
    $this->db = new Database();
    }
    public function 
    newsList() {

    }
    }
    Этот метод должен будет возвратить массив новостей. Для их получения из базы мы воспользуемся конструктором запросов (http://docs.kohanaphp.com/libraries/database/builder). На данный момент нам пригодятся лишь 3 метода - from (указывает из какой таблицы взять данные), orderby (указание сортировки данных) и get (получение данных). Выполним их по очереди, а результат работы метода get вернём в качестве результата функции.

    PHP:
    $this->db->from("news");
    $this->db->orderby("id","DESC");
    return 
    $this->db->get();
    Это эквивалентно запросу "SELECT * FROM `news` ORDER BY `id` DESC". В контроллере нам нужно вызвать вышеописанный метод и передать то что он вернёт в отображение. Там с помощью foreach-цикла мы обойдём массив новостей и отобразим всё одной лентой. Назовём параметр шаблонизатора, содержащий список новостей, newsList, по аналогии с методом модели. Сразу после обработки шаблона "index" добавьте вызов метода модели.

    PHP:
    $view->newsList $this->news->newsList();
    Параллельно с этим нам нужно создать конструктор у контроллера новостей, в котором модель будет автоматически подгружаться что бы в дальнейшем быть доступной из методов контроллера.

    PHP:
    public function __construct() {
    parent::__construct();
    $this->news = new News_Model;
    }
    Не забывайте в конструкторах прописывать вызовы конструктора класса-предка, иначе за место сайта получите горы ошибок. Откройте шаблон "index.php" и за место слова text добавьте следующий код.

    Code:
     <?foreach ($newsList as $news):?>
    <h1 class="title"><a href="/news/view/<?=$news->id?>"><?=$news->title?></a></h1>
    <p class="meta"><small><?=date('d-m-Y',$news->pub_date)?></small></p>
    <div class="entry">
    <!-- Показываем анонс - 200 символов от всего текста -->
    <p><?=substr($news->text,0,200)?>...</p>
    </div>
    <?endforeach;?>
    Теперь на главной странице отображаются все 4 новости которые находятся в базе.
    Отойдём от основной темы и вернёмся к модели новостей. Формируя запрос в методе newsList мы написали 3 строки кода. Но конструктор запросов к БД в Kohana имеет одну интересную особенность. Вызываемые методы конструктора возвращают в качестве результата свой же объект (в нашем случае $this->db). То есть мы из вызова одного метода можем вызывать другие методы, выстраивая так называемую "цепочку". Вот пример такого вызова. Этот код можно поставить за место тех трёх строк.

    PHP:
    return $this->db->from("news")->orderby("id","DESC")->get();
    Результат работы от этого не изменится, но кода стало значительно меньше. Такой подход сильно облегчает жизнь при разработке больших проектов.
    В начале статьи я писал про разбиение контента на страницы. У нас 4 новости, давайте разобьём их на 2 страницы, отображая 3 новости за раз. Воспользуемся классом "Pagination" (http://docs.kohanaphp.com/libraries/pagination). Перед тем как его задействовать нужно определится с настройками. При разбиении контента они передаются классу Pagination двумя способами - через специальный конфигурационный файл (/system/config/pagination.php) или же массивом прямо при создании объекта. Мне кажется что первый вариант лучше не использовать так как он является "центральным", а разбиение может понадобится не только в одном контроллере или методе. Пойдём по пути массива с настройками. Нам понадобится всего 4 опции
    1. base_url - ссылка куда будет подставляться номер страницы. В нашем случае эта опция примет значение "/news/index/" потому что третьей частью адреса будет именно номер страницы.
    2. total_items - общее количество объектов. В модель мы добавим метод который вернёт число новостей.
    3. items_per_page - сколько объектов отображать на страницу. Мы отображаем по 3 новости.
    4. style - стиль отображения ссылок. Здесь укажем "digg". На мой взгляд он выглядит приличнее остальных.
    Метод возвращающий количество новостей в базе будет называться "newsCount" и содержать всего одну строку:

    PHP:
    public function newsCount() {
    return 
    $this->db->count_records("news");
    }
    Поместите его в модель и вернёмся к контроллеру. Массив с настройками и их значениями можно объявить прямо в начале метода index:

    PHP:
    // Настройки разбиения на страницы
    $pagination_config = Array('base_url'              => '/news/index/',
    'total_items'        => $this->news->newsCount(),
    'items_per_page' => 3,
    'style'                   => 'digg'
    );
    И сразу можно создать общедоступный объект класса Pagination, передав ему в конструктор массив с настройками.

    PHP:
    $this->pagination = new Pagination($pagination_config);
    Обратите внимание на то что объект имеет вид не локальной переменной ($pagination). Сделано это для того чтобы мы могли обращаться к нему из отображения без лишних проблем. Теперь в шаблоне "index.php" разместим код который вызовет отображение ссылок на страницы.

    Code:
    <center><?=$this->pagination->render()?></center>
    Он должен идти сразу после foreach-цикла. Сохраните все изменения и обновите главную страницу. В низу покажется список страниц со ссылками на предыдущую и следующую страницы.
    На очереди ограничение вывода новостей. Ссылка на вторую страницу имеет вид http://kohana/news/index/2, то есть номер страницы передаётся методу index как единственный параметр. Обозначим его, изменив объявление метода.

    PHP:
    public function index($page 0) {  
    Так как изначально параметр не передаётся (например, при обращении к главной странице) то мы указали его значение по умолчанию. Теперь значение переменной $page нужно передать в модель, в метод выборки новостей. Заменим код его вызова с

    PHP:
    $view->newsList $this->news->newsList();
    на

    PHP:
    $view->newsList $this->news->newsList($page);
    Ну и соответственно добавим методу newsList один параметр.

    PHP:
    public function newsList($page) {
    Вместе с этим вставим в формирование запроса вызов метода "limit", где укажем ограничение на выборку новостей

    PHP:
    limit(3,$page*3)
    Здесь есть один важный момент. Так как отсчёт страниц у нас начинается ни с нуля (фактически нулевой страницы нет, как и ссылки на неё) то нам нужно постоянно уменьшать значение страницы на единицу. Если Вы сейчас попробуйте посмотреть новости то на главной странице у Вас покажутся 3 последние, а на второй новостей не будет вообще. Потому что при просмотре второй страницы, формируя лимит новостей, значение с которого нужно начать отбирать будет равно 6 ($page*3), а не 3. Поэтому в начало метода модели следует добавить строчку декремента параметра $page

    PHP:
    $page--;
    А что бы при нулевом значении в первую границу limit не попадало число -3 (-1*3) мы изменим эту строку на

    PHP:
    if($page 0$page--; else $page 0;
    Таким образом, после наших многочисленных манипуляций, полный код метода index, контроллера новостей становится следующий.

    PHP:
    public function index($page 0) {
    // Настройки разбиения на страницы
    $pagination_config = Array('base_url'       => '/news/index/',
    'total_items'    => $this->news->newsCount(),
    'items_per_page' => 3,
    'style'          => 'digg'
    );
    $this->pagination = new Pagination($pagination_config);
    // Обрабатываем все шаблоны
    $view           = new View('index');
    $view->header   = new View('header');
    $view->menu     = new View('menu');
    $view->footer   = new View('footer');
    $view->newsList $this->news->newsList($page);

    // Показываем
    $view->render(true);
    }
    А два метода модели выглядят вот так.

    PHP:
    public function newsList($page) {
    if(
    $page 0$page--; else $page 0;

    return 
    $this->db->from("news")->limit(3,$page*3)->orderby("id","DESC")->get();
    }
    PHP:
    public function newsCount() {
    return 
    $this->db->count_records("news");
    }
    Единственное что осталось - написать метод просмотра полной новости. Как Вы могли заметить из кода главного шаблона, его имя "view".
    Действие просмотра новости крайне простое. Нужно лишь взять информацию из базы и вывести её в шаблон. Ссылка показа новости имеет следующий вид: http://kohana/news/view/4. То есть в качестве идентификатора новости у нас передаётся её id. Он же является единственным параметром для нашего метода. Сначала создадим в модели функцию которая будет получать данные о новости по её id.

    PHP:
    public function newsData($id) {
    $result $this->db->from("news")->where("id",(int) $id)->get();
    return 
    $result[0];
    }
    Теперь опишем метод view в контроллере.

    PHP:
    public function view($id) {
    $view  = new View('./news/view');
    $view->header = new View('header');
    $view->menu   = new View('menu');
    $view->footer = new View('footer');
    $view->news   $this->news->newsData($id);

    $view->render(true);
    }
    Из кода видно что данные новости передаются в шаблон через переменную news. Сам код шаблона точно такой же как на главной странице, но с тремя отличиями. В нём нет цикла (мы же выводим одну новость), строки разбиения на страницы, и полный текст новости показывается целиком, а не первые 200 символов.

    Code:
    <?=$header?>
    <body>
    <?=$menu?>
    <!-- start page -->
    <div id="page">
    <!-- start content -->
    <div id="content">
    <div class="post">
    <h1 class="title"><a href="/news/view/<?=$news->id?>"><?=$news->title?></a></h1>
    <p class="meta"><small><?=date('d-m-Y',$news->pub_date)?></small></p>
    <div class="entry">
    <p><?=$news->text?></p>
    </div>
    </div>
    </div>
    <!-- end content -->
    <div style="clear: both;">&nbsp;</div>
    </div>
    <!-- end page -->
    <hr />
    <?=$footer?>
    Вот и всё. Новостная часть полностью готова. Идём дальше.
     
  4. Kuzya

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

    Joined:
    27 Apr 2008
    Messages:
    166
    Likes Received:
    106
    Reputations:
    30
    3.2 Скриншоты.
    Отображать скриншоты программы мы будем на отдельной странице. Они будут считываться из директории /images/screenshots (скопируйте туда содержимое соответствующей папки из архива с дополнительными файлами) и выводится в уменьшенном размере на страницу. Директория здесь используется для того что бы не заморачиваться с различными панелями загрузки скриншотов. Закинул картинку в папку - она отобразилась на сайте. Всё просто.
    В самом начале мы получим список уже имеющихся в директории изображений. Это действие я решил поместить в модель. Возможно помещение его в контроллере тоже было бы правильным, но мне кажется что раз происходит операция с данными то находится она должна в модели. Создайте пустую модель Screenshots_model и поместите туда единственный метод под именем imagesList.

    PHP:
    class Screenshots_Model extends Model {
    public function 
    imagesList() {

    }
    }
    С помощью функции scandir мы обработаем папку скриншотов и вернём список существующих картинок.

    PHP:
    // Получаем список файлов
    $images scandir("./images/screenshots");

    // Убираем "." и ".."
    for($i 0$i count($images); $i++) {
    if(
    $images[$i] == "." OR $images[$i] == "..")
    unset(
    $images[$i]);
    }

    return 
    $images;
    Теперь передадим эти данные в отображение и выведем пользователю. Конечно, надо написать функцию изменения размера изображений, ведь скриншот может быть большим и весить несколько мегабайт. Мы её опишем в контроллере Screenshots применяя библиотеку для работы с изображениями (http://docs.kohanaphp.com/libraries/image). Назовём эту функцию resize. У неё будет 2 входящих параметра - имя картинки и требуемая ширина.

    PHP:
    // Нам нужно только имя файла, а не всякие ../file
    $image basename($image);
    // Создаём новый объект Image
    $image = new Image("./images/screenshots/{$image}");
    // Изменяем размер изображения основываясь его на ширине
    $image->resize($width200Image::WIDTH);
    // Выводим изображение
    $image->render();
    Обратите внимание на то что методу resize, объекта image, третьим параметром мы передаём значение константы Image::WIDTH. Это нужно для того что бы размер изображения изменялся с сохранением пропорции основываясь на ширине. То есть цифра 200 указывающая высоту в нашем случае ничего не значит.

    Так как мы собрались передавать массив изображений в шаблон, то нам ничего не стоит вывести код показа изменённого изображения. В самом контроллере нам нужно лишь поработать с отображениями - вывести их и передать результат работы "imagesList"

    PHP:
    public function index() {
    $view  = new View('screenshots/index');

    $view->header        = new View('header');
    $view->menu          = new View('menu');
    $view->footer          = new View('footer');
    $view->screenshots $this->screenshots->imagesList();

    $view->render(true);
    }
    В шаблоне мы обойдём массив с помощью цикла foreach и отобразим картинки вот с такими источниками:

    /screenshots/resize/имя_изображения/300

    Так же мы организуем в отображении счётчик и после вывода каждых двух картинок будем создавать новую строку.

    PHP:
    <?=$header?>
    <body>
    <?=$menu?>
    <!-- start page -->
    <div id="page">
    <!-- start content -->
    <div id="content">
    <div class="post">
    <h1 class="title"><a href="/screenshots/">Скриншоты нашей программы</a></h1>
    <div class="entry">
    <p>
    <table width="100%" cellspacing="10" cellpadding="5">
    <tr>
    <?$i = 0;?>
    <?foreach($screenshots as $screenshot):?>
    <!--Если остаток от деления на 2 - 0 (число кратно 2) то начинаем новую строку-->
    <?if(!fmod($i,2)):?></tr><tr><?endif;?>
    <td>
    <img src="/screenshots/resize/<?=$screenshot?>/300" onClick="window.open('/screenshots/resize/<?=$screenshot?>/800');"/>
    </td>
    <?$i++;?>
    <?endforeach;?>
    </tr>
    </table>
    </p>
    </div>
    </div>
    </div>
    <!-- end content -->
    <div style="clear: both;">&nbsp;</div>
    </div>
    <!-- end page -->
    <?=$footer?> 
    Сохраните этот шаблон под именем index.php в папке /application/view/screenshots/
    В итоге мы должны получить следующее.

    [​IMG]
    И сколько бы мы не клали изображений в директорию скриншотов - страница будет вытягиваться только вниз. Вы могли заметить что в коде шаблона, при отображении картинки, указывается событие onClick. В нём прописан код открытия скриншота шириной 800 пикселей.
     
  5. Kuzya

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

    Joined:
    27 Apr 2008
    Messages:
    166
    Likes Received:
    106
    Reputations:
    30
    3.3 Оформление покупки.
    В отдельном разделе нашего сайта посетителям должна предоставляться возможность оформить заказ на программу. Они будут делать это с помощью формы, в которой нужно указать все необходимые регистрационные данные. После чего запрос уйдёт на E-mail менеджеру.
    Создайте контроллер buy с методом index. Разместим в нём код показа формы, которую клиент должен будет заполнить. Вот её шаблон. Сохраните его в директории отображений, в папке buy, под именем index.php.

    Code:
    <?=$header?>
    <body>
    <?=$menu?>
    <!-- start page -->
    <div id="page">
    <!-- start content -->
    <div id="content">
    <div class="post">
    <h1 class="title"><a href="/buy/">Купить программу</a></h1>
    <div class="entry">
    <p>
    <form action="/buy/send/" method="POST" >
    <table width="100%" cellspacing="10" cellpadding="5">
    <tr>
    <td width="25%">Ваше ФИО:</td>
    <td><input type="text" name="fio" /></td>
    </tr>
    <tr>
    <td width="25%">E-mail для связи:</td>
    <td><input type="text" name="email" /></td>
    </tr>
    <tr>
    <td width="25%">Тип необходимой лицензии:</td>
    <td>
    <select name="license">
    <option value="0">Корпоративная</option>
    <option value="1">Домашняя</option>
    </select>
    </td>
    </tr>
    <tr>
    <td width="25%">Тип программы:</td>
    <td>
    <select name="type">
    <option value="0">Lite</option>
    <option value="1">Standart</option>
    <option value="2">Pro</option>
    </select>
    </td>
    </tr>
    <tr>
    <td width="25%">Дополнительная информация:</td>
    <td><textarea name="additional" rows="7"></textarea></td>
    </tr>
    <tr>
    <td colspan="2"><input type="submit" value="Отправить заявку"/></td>
    </tr>
    </table>
    </form>
    </p>
    </div>
    </div>
    </div>
    <!-- end content -->
    
    <div style="clear: both;">&nbsp;</div>
    </div>
    <!-- end page -->
    <?=$footer?>
    И код контроллера.

    PHP:
    class Byu_Controller extends Controller {
    public function 
    index() {
    $view               = new View('buy/index');
    $view->header = new View('header');
    $view->menu   = new View('menu');
    $view->footer  = new View('footer');

    $view->render(true);
    }
    }
    Ничего сложного. После отправки формы информация передаётся методу send. В него поместим код отправки письма менеджеру. Отправка будет происходить с помощью SwiftMailer (хэлпер email - http://docs.kohanaphp.com/helpers/email). Сначала получим нужные данные

    PHP:
    $fio        $_POST['fio'];
    $email    $_POST['email'];
    $license = ((int) $_POST['license']) ? "Домашняя" "Корпоративная";

    $type       = (int) $_POST['type'];
    switch(
    $type) {
    case 
    1$type "Lite"; break;
    case 
    2$type "Standart"; break;
    case 
    3$type "Pro"; break;
    }

    $additional nl2br($_POST['additional']);
    Затем формируем информацию для письма.

    PHP:
    $to            "[email protected]";
    $from       "[email protected]";
    $subject    "Заказ на покупку программы";
    $message  "Здравствуйте. {$fio}($email) оформил заказ на покупку нашей программы.<br />";
    $message .= "Выбранный тип лицензии - `{$license}`, тип программы - `{$type}`<br />";
    $message .= "Дополнительная информация от клиента: {$additional}";
    И отправим сообщение.

    PHP:
    email::send($to$from$subject$messageTRUE);
    Параллельно с этим нужно показать пользователю сообщение о том что заказ отправлен и скоро ему ответят. Возьмём для этого следующий шаблон и сохраним его под именем sended.php в той же директории.

    PHP:
    <?=$header?>
    <body>
    <?=$menu?>
    <!-- start page -->
    <div id="page">
    <!-- start content -->
    <div id="content">
    <div class="post">
    <div class="entry">
    <p>
    Спасибо за заказ. Наш менеджер обработает его в самое ближайшее время и вышлет ответ на указанный Вами E-mail.
    </p>
    </div>
    </div>
    </div>
    <!-- end content -->
    <div style="clear: both;">&nbsp;</div>
    </div>
    <!-- end page -->
    <?=$footer?>
    И внесём отображение в контроллер. В итоге код метода send примет следующий вид.

    PHP:
    public function send() {
    $fio        $_POST['fio'];
    $email      $_POST['email'];
    $license    = ((int) $_POST['license']) ? "Домашняя" "Корпоративная";

    $type       = (int) $_POST['type'];
    switch(
    $type) {
    case 
    1$type "Lite";
    case 
    2$type "Standart";
    case 
    3$type "Pro";
    }

    $additional nl2br($_POST['additional']);

    $to         "[email protected]";
    $from    "[email protected]";
    $subject "Заказ на покупку программы";
    $message "Здравствуйте. {$fio}($email) оформил заказ на покупку нашей программы.<br />";
    $message .= "Выбранный тип лицензии - `{$license}`, тип программы - `{$type}`<br />";
    $message .= "Дополнительная информация от клиента: {$additional}";

    email::send($to$from$subject$messageTRUE);

    $view  = new View("buy/sended");

    $view->header = new View('header');
    $view->menu   = new View('menu');
    $view->footer = new View('footer');

    $view->render(true);
    }
    Если Вы сейчас отправите форму, то увидите сообщение об обработке заказа. А с сайта, на адрес [email protected], уйдёт письмо с введённой информацией. Те кто используют Denwer могут убедится в этом просмотрев папку /tmp/!sendmail.
    Но, что бы клиент случайно не пропустил одно из важных полей, мы организуем проверку данных с помощью валидации (http://docs.kohanaphp.com/libraries/validation). Создадим объект класса Validation, передав ему в конструктор массив $_POST. После этого мы обозначим 2 правила. Обязательно должны быть заполнены поля с ФИО и E-mail`ом. И указанный E-mail должен иметь соответствующий формат.

    PHP:
    $post = new Validation($_POST);
    $post->add_rules('fio','required');
    $post->add_rules('email','required','email');
    Теперь, вызвав метод validate, мы можем проверить совпадают ли с указанными правилами поля массива $_POST. В зависимости от решения этого метода мы либо отправим письмо, либо сообщим о допущенных ошибках.
    Немного отойдём от основной темы. Для того что бы отобразить пользователю тексты ошибок мы должны создать языковой файл с ними. Пройдите в директорию /system/i18n/ru_RU и создайте там файл form_errors.php. В него мы должны поместить массив с фраз. Он будет состоять из двух ячеек, названных аналогично полям формы. В ячейках будет содержаться текст ошибок, у которого ключами будут имена правил.

    PHP:
    <?php

    $lang 
    = Array(
    'email' => Array(
    'required' => 'Вы должны обязательно указать E-mail для связи',
    'email'    => 'Введённый вами E-mail ошибочен'
    ),
    'fio' => Array(
    'required' => 'Вы должны указать Фамилию, Имя, Отчество'
    )
    );

    ?>
    Теперь нужно встроить в контроллер обработку данных. Массив же ошибок можно получить сразу после вызова метода validate, из функции errors. Ей в качестве параметра нужно передать имя используемого языкового файла без расширения. Полученный текст мы передадим в шаблон. При отображении просто будем проверять - если массив с ошибками есть то выводим их, если нет - сообщаем об отправке письма. Вот полный код получившегося метода send.

    PHP:
    public function send() {
    $view         = new View('buy/sended');
    $view->header = new View('header');
    $view->menu   = new View('menu');
    $view->footer = new View('footer');

    // Валидация. Установка правил.
    $post = new Validation($_POST);
    $post->add_rules('fio','required');
    $post->add_rules('email','required','email');

    // Отсылаем письмо если всё нормально
    if($post->validate()) {
    $fio        $_POST['fio'];
    $email      $_POST['email'];
    $license    = ((int) $_POST['license']) ? "Домашняя" "Корпоративная";

    $type       = (int) $_POST['type'];
    switch(
    $type) {
    case 
    0$type "Lite"; break;
    case 
    1$type "Standart"; break;
    case 
    2$type "Pro"; break;
    }

    $additional nl2br($_POST['additional']);

    $to      "[email protected]";
    $from    "[email protected]";
    $subject "Заказ на покупку программы";
    $message "Здравствуйте. {$fio}($email) оформил заказ на покупку нашей программы.<br />";
    $message .= "Выбранный тип лицензии - `{$license}`, тип программы - `{$type}`<br />";
    $message .= "Дополнительная информация от клиента: {$additional}";

    email::send($to$from$subject$messageTRUE);

    } else {
    // Если нет, передаём в отображение все ошибки
    $view->validation_errors $post->errors('form_errors');
    }

    $view->render(true);
    }
    Центральную часть отображения, в которой сейчас находится текст об успешной отправке, модифицируем вот так.

    Code:
    <?if(isset($validation_errors)):?>
    Внимание! Обнаружены следующие ошибки:
    <ul>
    <?foreach($validation_errors as $error):?>
    <li><?=$error?></li>
    <?endforeach;?>
    </ul><br />
    Вернитесь пожалуйста <a href="/buy/">назад</a> и заполните форму правильно.
    <?else:?>
    Спасибо за заказ. Наш менеджер обработает его в самое ближайшее время и вышлет ответ на указанный Вами E-mail.
    <?endif;?>
    Теперь зайдите на страницу оформления заказа и отправьте форму не заполнив поля. Вы должны увидеть страницу с ошибками.

    [​IMG]

    При правильном же заполнении формы пользователь увидит то же сообщение что и раньше.
     
  6. Kuzya

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

    Joined:
    27 Apr 2008
    Messages:
    166
    Likes Received:
    106
    Reputations:
    30
    3.4 Гостевая книга.
    Поработаем над гостевой. В базе данных, во второй таблице, kh_gb, уже имеется 2 отзыва о программе. Сделаем сначала их вывод, а затем и добавление.
    Сперва создайте пустую модель Gb_Model с конструктором, в котором создаётся объект класса Database (как в модели новостей)

    PHP:
    class Gb_Model extends Model {
    public function 
    __construct() {
    parent::__construct();
    $this->db = new Database();
    }
    }
    Затем создайте контроллер с пустым методом index и конструктором создающим объект модели.

    PHP:
    class Gb_Controller extends Controller {

    public function 
    __construct() {
    parent::__construct();
    $this->gb = new Gb_Model;
    }

    public function 
    index() {

    }
    }
    Далее, в модели создадим метод answersList, который будет возвращать массив отзывов гостевой книги.

    PHP:
    public function answersList() {
    return 
    $this->db->from("gb")->orderby("id","DESC")->get();
    }
    В контроллере мы вызовем отображение шаблона /application/views/gb/index.php и передадим в него список отзывов в параметре answersList.

    PHP:
    public function index() {
    $view           = new View('gb/index');
    $view->header   = new View('header');
    $view->menu     = new View('menu');
    $view->footer   = new View('footer');
    $view->answersList $this->gb->answersList();

    $view->render(true);
    }
    И сам код шаблона гостевой.

    Code:
    <?=$header?>
    <body>
    <?=$menu?>
    <!-- start page -->
    <div id="page">
    <!-- start content -->
    <div id="content">
    <div class="post">
    <?foreach ($answersList as $answer):?>
    <h2 class="title">
    <b>
    <?=$answer->author?>(<?=date('d-m-Y',$answer->pub_date)?>)
    </b>
    </h2>
    &nbsp;&nbsp;&nbsp;<?=$answer->text?>
    <?endforeach;?>
    </div>
    </div>
    <!-- end content -->
    <!-- start sidebar two -->
    
    <!-- end sidebar two -->
    <div style="clear: both;">&nbsp;</div>
    </div>
    <!-- end page -->
    <?=$footer?>
    Сейчас обратитесь к главной странице гостевой книги. Должно отобразиться 2 записи.
    Теперь разберёмся с формой добавления отзыва. Она будет содержать всего 2 поля - имя и текст сообщения.

    Code:
    <br />
    <br />
    <form action="/gb/send/" method="POST" >
    <table width="100%" cellspacing="10" cellpadding="5">
    <tr>
    <td width="15%">Ваше имя:</td>
    <td width="85%"><input type="text" name="name" /></td>
    </tr>
    <tr>
    <td colspan="2"><textarea name="text" rows="7"></textarea></td>
    </tr>
    <tr>
    <td colspan="2" width="30%"><input type="submit" value="Отправить отзыв"/></td>
    </tr>
    </table>
    </form>
    Добавьте его сразу после окончания цикла вывода отзывов. В модель добавим метод add, который будет вставлять в таблицу новую запись принимая 2 параметра соответствующих полям формы.

    PHP:
    public function add($name,$text) {
    $this->db->insert("gb",Array("author"=>$name,"text"=>$text,"pub_date"=>time()));
    }
    В контроллере мы позаботимся о безопасности с помощью хэлпера security (http://docs.kohanaphp.com/helpers/security). Воспользуемся его методом xss_clean(). А после добавления записи перенесём пользователя на главную страницу с помощью метода redirect, хэлпера url (http://docs.kohanaphp.com/helpers/url).

    PHP:
    public function send() {
    $name security::xss_clean($_POST['name']);
    $text security::xss_clean($_POST['text']);

    $this->gb->add($name,$text);

    url::redirect("/gb/");
    }
    Обратите внимание на то что если в файле /application/config/config.php параметр "global_xss_filtering" установлен в значение "true" то входящие данные автоматически проходят XSS-фильтрацию.
    Дальше введём валидацию. Нам нужно будет лишь проверять поля формы на пустоту.

    PHP:
    $post = new Validation($_POST);
    $post->add_rules("*","required");
    Откроем языковой файл с ошибками и добавьте туда ещё 2 поля из этой формы.

    PHP:
    'name' => Array(
    'required' => 'Вы должны указать своё имя'
    ),
    'text' => Array(
    'required' => 'Заполните поле сообщения'
    ),
    Добавим ещё фразу для поля captcha. Ниже мы соорудим проверку на робота.

    PHP:
    'captcha' => Array(
    'required' => 'Введите текст с картинки'
    )
    Для вывода ошибок создадим отдельный шаблон аналогичный шаблону ошибок заказа. Назовём его error.php.

    Code:
    <?=$header?>
    <body>
    <?=$menu?>
    <!-- start page -->
    <div id="page">
    <!-- start content -->
    <div id="content">
    <div class="post">
    <div class="entry">
    <p>
    Внимание! Обнаружены следующие ошибки:
    <ul>
    <?foreach($validation_errors as $error):?>
    <li><?=$error?></li>
    <?endforeach;?>
    </ul><br />
    Вернитесь пожалуйста <a href="/gb/">назад</a> и заполните форму правильно.
    </p>
    </div>
    </div>
    </div>
    <!-- end content -->
    <div style="clear: both;">&nbsp;</div>
    </div>
    <!-- end page -->
    <?=$footer?>
    Ну и соответствующим образом поменяем код функции добавления.

    PHP:
    public function send() {
    // Валидация
    $post = new Validation($_POST);
    // Все поля должны быть заполнены
    $post->add_rules("*","required");
    // Если всё правильно то добавляем запись
    if($post->validate()) {
    // Чистим данные
    $name security::xss_clean($_POST['name']);
    $text security::xss_clean($_POST['text']);
    // Добавляем их в базу
    $this->gb->add($name,$text);
    // Переносим пользователя
    url::redirect("/gb/");
    }
    // Если же валидация не прошла то показываем ошибки
    $view = new View("gb/error");

    $view->header = new View('header');
    $view->menu   = new View('menu');
    $view->footer = new View('footer');
    $view->validation_errors $post->errors("form_errors");

    $view->render(true);
    }
    Теперь стоит позаботится о спам-защите и воспользовать Captcha. Благо в Kohana уже есть встроенная библиотека для решения таких задач (http://docs.kohanaphp.com/libraries/captcha). Создадим объект этой библиотеки в методе index и передадим в отображение результат работы функции render - она выводит html-код показывающий защитную картинку.

    PHP:
    public function index() {
    $captcha = new Captcha;

    $view  = new View('gb/index');

    $view->header   = new View('header');
    $view->menu     = new View('menu');
    $view->footer    = new View('footer');
    $view->answersList $this->gb->ansersList();
    // Передаём код <img src...
    $view->captcha  $captcha->render();

    $view->render(true);
    }
    И в шаблон формы сразу после поля сообщения добавим код отображения картинки и поля ввода.

    Code:
    <tr>
    <td colspan="2"><?=$captcha?></td>
    </tr>
    <tr>
    <td width="15%">Текст с изображения</td>
    <td width="85%"><input type="text" name="captcha" /></td>
    </tr>
    Далее, в контроллер, в функцию добавления встроим проверку введённого с картинки текста. Для этого строку

    PHP:
    if($post->validate()) {
    поменяем на

    PHP:
    if($post->validate() AND Captcha::valid($_POST['captcha'])) {
    И в конце функции передадим в отображение (сразу после объявления свойства validation_errors) информацию о том что текст введён не правильно.

    PHP:
    if(!Captcha::valid($_POST['captcha']))
    $view->captcha_error true;
    А в отображении, прямо перед выводом ошибок валидации, мы встроим проверку на истинность параметра captcha_error.

    Code:
    <?if($captcha_error):?>
    <li>Неправильно введён текст с картинки</li>
    <?endif;?>
     
  7. Kuzya

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

    Joined:
    27 Apr 2008
    Messages:
    166
    Likes Received:
    106
    Reputations:
    30
    3.5 Страница загрузки
    Самый последний и самый лёгкий раздел. На этой странице пользователю будет предложено скачать программу в различных вариантах (архивы или exe-файл). Создайте контроллер download, а в нём метод отображающий страницу applications/views/download/index.php. Кода контроллера я приводить не буду потому что выше такие простые операции уже описывались. Вот код шаблона.

    Code:
    <?=$header?>
    <body>
    <?=$menu?>
    <!-- start page -->
    <div id="page">
    <!-- start content -->
    <div id="content">
    <div class="post">
    <h1 class="title"><a href="/download/">Скачать</a></h1>
    <div class="entry">
    <p>
    <a href='/bred.exe'>Как EXE-файл</a><br />
    <a href='/download/archive/zip/'>Как ZIP-архив</a><br />
    <a href='/download/archive/tar/'>Как TAR-архив</a><br />
    </p>
    </div>
    </div>
    </div>
    <!-- end content -->
    <div style="clear: both;">&nbsp;</div>
    </div>
    <!-- end page -->
    <hr />
    <?=$footer?>
    Как видно из шаблона, ссылка загрузки exe-файла ведёт прямо на него, а архивных файлов - на действие archive. И в случае с архивом, в ссылке передаётся его тип. Осуществлять формирование архивов мы будем с помощью модуля Archive (документация по нему - http://docs.kohanaphp.com/addons/archive). Просто создадим объект этого класса, добавим в него файл дистрибутива и выведем пользователю. Архив будем формировать в зависимости от переданного типа. По умолчанию будем использовать zip-архивы.

    PHP:
    public function archive($type) {
    // В зависимости от типа формируем архив
    switch($type) {
    case 
    "zip":
    $this->archive = new Archive("zip");
    break;
    case 
    "tar":
    $this->archive = new Archive("tar");
    break;
    // По умолчанию zip
    default:
    $type "zip";
    $this->archive = new Archive("zip");
    break;
    }

    // Добавляем в архив программу
    $this->archive->add("bred.exe");
    // Скачиваем под именем bred.расширение
    $this->archive->download("bred.{$type}");
    }
    Вот и всё. Наш сайт полностью готов.

    4. Заключение.
    Мне очень понравился данный фреймворк. Раньше я думал что фреймворков проще Code Igniter просто быть не может. Но, как говорится, нет пределов совершенству. Вообщем от работы с Kohana я в восторге. Надеюсь на Вас он произвёл то же самое впечатление. Удачи!
    P.S. Если у Вас что-то не получилось то Вы можете взять готовый исходный код в файле ready.zip.

    5. Приложенные файлы
    1.http://kuzya.name/files/kohana/ready.zip – готовый вариант сайта
    2.http://kuzya.name/files/kohana/design.zip — дизайн
    3.http://kuzya.name/files/kohana/database.sql — код базы данных
    4.http://kuzya.name/files/kohana/additional_files.zip — дополнительный файлы (скриншоты + дистрибутив Bred3).
     
  8. Kuzya

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

    Joined:
    27 Apr 2008
    Messages:
    166
    Likes Received:
    106
    Reputations:
    30
    Извиняюсь перед посетителями за то что после публикации первой части долго не было остальных. В силу определённых обстоятельств я не смог сразу опубликовать остальные куски статьи.
    Если ты ещё в возрасте "темы сисек" то не думаю что статья вообще будет интересна.
    Про Cake и CI тоже было "тупо скопипастено с моего ресурса" и ничего, людям нравилось. Просто статьи я публикую у себя, здесь и ещё на 1 форуме. У меня они появляются пораньше на пару дней (по личным соображениям).
    Удалишь - твоя воля.
     
    2 people like this.