Yvision.kz
kk
Разное
Разное
399 773 постов42 подписчика
Всяко-разно
1
03:29, 12 февраля 2011

Zend Framework: создаем блог. Шаг 1

Аллоха, ювижинцы! Ниже наглая копипаста с моего стандалона. Понеслась.

Сегодня мы продолжим изучать Zend Framework. В прошлом посте мы с вами кратко познакомились с ним, установили и вывели стандартное приветствие в браузер. Пришло время разработать свое первое приложение.

Я решил пропустить традиционное "Hello, World!", выбрав не менее традиционный блог :). На его примере, думаю можно хорошенько разобрать многие более-менее используемые компоненты фреймворка.

Итак, составим нечто вроде ТЗ, чтобы ясно представлять себе будущий функционал.

  • Список постов на главной с постраничной навигацией. При клике на заголовок, должна открываться полная версия. Каждый пост имеет свою категорию. Категории выводятся где-нибудь в сайдбаре и при клике должен выводится список постов этой категории, также с постаничной навигацией.
  • К каждому посту можно оставить комментарий.
  • Аутентификация администратора.
  • CRUD категорий, постов и комментов. Админки как таковой не будет, просто админу будут выводится ссылки на CRUD.

В принципе все. Эдакий simple blog. Если у вас имеются какие-нибудь предложения по дополнительной функциональности - велкам в комменты. Теперь приступим к самому вкусному - кодингу. Далее предпологается, что вы уже установили и создали свой проект при момощи Zend_Tool. Теперь взгянем на сгенерированную структуру проекта.

Структура проекта Zend Framework

С непривычки такое обилие папок может запутать и напугать. Но немного привыкнув, все станет понятно и логично. В configs хранится конфигурация вашего проекта, в controllers, models, views, как можно догадаться 3 составляющие части MVC - контроллеры, модели и вьюшки. В docs документация, в library дистрибутив ZF (если вы последовали совету из предыдущего поста, то эта папка у вас должна быть пуста, т.е. она и так будет пуста, пусть такой и остается), в public вся доступная извне часть вашего приложения (CSS-стили, JS-скрипты, картинки, etc), в tests всякие тесты. Скрытый файл .zfproject.xml в корне - описание структуры вашего приложения.

Ну-с, со структурой более-менее разобрались, двигаемся дальше. Попытаюсь на пальцах объяснить принцип работы ZF. В каталоге public лежат два файла - .htaccess и index.php. Первый перенаправляет все запросы на единственный входной файл index.php, который реализует паттерн Front Controller. В index.php обьявляются всякие нужные константы, типа корня приложения. Далее создается экземпляр класса Zend_Application, у которого вызываются поочередно методы bootstrap() и run(). Вдаваться, что делают эти методы мы пока не будем, потому что я и сам толком не знаю, но если коротко, то инициализируют начальные настройки и запускают роутер (маршрутизатор). В корне каталога приложения application лежит один файл Bootstrap.php. Это класс, наследующийся от Zend_Application_Bootstrap_Bootstrap. Он используется для действий при запуске приложения. Далее в configs лежит файл конфигурации application.ini. Данные в нем хранятся в формете .ini и используют наследование секций. В первой секции production хранятся настройки production-приложения, т.е. уже запущенного реального проекта. Остальные секции наследуются от production и служат для тестирования и разработки соответственно. Давайте занесем наши данные для подключения к БД. В конец секции production добавляем:

  1. ; DB config
  2. resources.db.adapter = PDO_MYSQL
  3. resources.db.params.host = localhost
  4. resources.db.params.username = root
  5. resources.db.params.password =
  6. resources.db.params.dbname = zftest
  7. resources.db.params.charset = utf8

Думаю здесь все понятно. Обычные данные для подключения к БД. Последней стоит кодировка. Мы будем юзать utf8. Также если у вас Денвер, поменяйте в httpd.cong windows-1251 на utf8. В редакторе кода, кстати тоже установите эту кодировку. Стоит отметить, что мы будем использовать MySQL через PDO, так что у вас должен быть установлен соотвествующий модуль. У меня по умолчанию установлено PDO_MySQL, на Денвере по моему PDO_SQLite. Так что посмотрите вывод фунции phpinfo() и доустановите отсутствующие модули. Да, и еще: комментарии в .ini файлах ставятся через точку с запятой (;), что удобно в отличие например от XML, где комментариев нет. Теперь давайте взглянем на структура БД нашего блога:

  1. CREATE TABLE IF NOT EXISTS `zf_posts` (
  2. `id` MEDIUMINT(5) UNSIGNED NOT NULL AUTO_INCREMENT,
  3. `category_id` TINYINT(2) UNSIGNED NOT NULL,
  4. `url` VARCHAR(100) NOT NULL,
  5. `title` VARCHAR(100) NOT NULL,
  6. `blog_post` MEDIUMTEXT NOT NULL,
  7. `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  8. PRIMARY KEY (`id`)
  9. ) ENGINE = MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
  10.  
  11. CREATE TABLE IF NOT EXISTS `zf_categories` (
  12. `id` TINYINT(2) UNSIGNED NOT NULL AUTO_INCREMENT,
  13. `url` VARCHAR(100) NOT NULL,
  14. `title` VARCHAR(100) NOT NULL,
  15. PRIMARY KEY (`id`)
  16. ) ENGINE = MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci;
  17.  
  18. CREATE TABLE IF NOT EXISTS `zf_comments` (
  19. `id` MEDIUMINT(7) UNSIGNED NOT NULL AUTO_INCREMENT,
  20. `post_id` MEDIUMINT(5) UNSIGNED NOT NULL,
  21. `name` VARCHAR(50) NOT NULL,
  22. `email` VARCHAR(50) NOT NULL,
  23. `url` VARCHAR(50),
  24. `blog_comment` TEXT NOT NULL,
  25. `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
  26. PRIMARY KEY (`id`)
  27. ) ENGINE = MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci;

Этот файл с полным дампом БД под названием schema.sql, вы найдете в прилагаемом архиве, в папке docs. Здесь у нас имеются 3 таблицы: посты, категории и комменты. На них останавливаться не будем.

Начнем с моделей. Здесь будут храниться наши CRUD-методы. Переходим в директорию models и создаем в ней новый каталог DbTable. В нем будут храниться наши модели. Стоит рассказать принцип именования моделей. Каждая модель соответствует одной таблице в БД. Называть класс нужно по назвнию таблицы с префиксом Model. Например наша модель постов будет называться Application_Model_DbTable_Posts. Здесь Application это каталог нашего приложения, Model папка с моделями, DbTable папка где храняться модели наших таблиц и наконец Posts - название таблицы. Довольно длинное название, но если вы ознакомились со стандартами кодинга Zend Framework, а я настоятельно рекомендую сделать это, то вы поймете, что к чему. Сделано это для имитации пространства имен. В PHP 5.3 наконец появилась нативная поддержка неймспейсов, думаю в будущих версиях ZF начнут их применять. Нам нужно получать пост по ID, для вывода отдельного поста. Вот листинг модели постов:

  1. <?php
  2.  
  3. /**
  4.  * Posts model
  5.  *
  6.  * @author Kanat Gailimov, http://gailimov.info
  7.  * @copyright Copyright (c) Kanat Gailimov (http://gailimov.info) 2011
  8.  */
  9.  
  10. class Application_Model_DbTable_Posts extends Zend_Db_Table_Abstract
  11. {
  12. /**
  13.   * Db table name
  14.   *
  15.   * @var string
  16.   */
  17. protected $_name = 'zf_posts';
  18.  
  19. /**
  20.   * Get post by ID
  21.   *
  22.   * @param int $id ID of post
  23.   * @return array
  24.   */
  25. public function getById($id)
  26. {
  27. $id = intval($id);
  28. $row = $this->fetchRow('id = ' . $id);
  29. if (!$row) {
  30. throw new Exception('Ахтунг! Выборка поста не удалась :(');
  31. }
  32. return $row;
  33. }
  34. }

Код подробно прокомментирован, но думаю пояснить все же стоит. Первым делом создаем класс, наследующийся от Zend_Db_Table_Abstract. Делее создаем protected свойство, в котором будет храниться название нашей таблицы. Затем создаем метод для выборки постов по ID'шнику, в который передаем этот самый ID'шник. Для этого используе метод fetchRow() класса Zend_Db_Table_Abstract. Этот метод возращает одну строку в таблице.

Займемся контроллерами. По умолчанию ZF генерирует для нас два файла с контроллерами: ErrorController.php и IndexController.php. Первый для обработки ошибок, второй собственно для отображения страниц. URL'ы в ZF строятся таким образом: http://sitename.loc/controller/action/params, где controller - контроллер, action - метод или как его еще называют экшен (действие), params - параметры. Контроллеры именуются по схеме - ControllerNameController, т.е. в camel case (при том первая буква заглавная) и плюс в конце обязательно добавляется Controller. Сам класс называется точно так же как файл, т.е. здесь не действуют стандартные имитаторы пространств имен ZF, и наследуется от Zend_Controller_Action. Методы именуются по схеме methodAction, т.е. название метода в нижнем регистре плюс слово Action. Мы будем использовать сгенерированный indexController. В нем уже содержатся два метода init() и indexAction(). Нам также нужен еще один метод для просмотра отдельного поста. Можно объявить его в коде ручками, но мы воспользуемся Zend_Tool. Вы спросите почему? Ну чтобы показать как можно генерировать методы, и еще Zend_Tool помимо самих экшенов создаст нам соответствующие вьюшки. Итак, открываем консоль, переходим в директорию с проектом и вводим команду:

  1. zf create action post index

Здесь post - название экшена (будет postAction()), index - контоллер. В каталоге application/views/scrips/index/ появится файл post.phtml. Это вид для нашего экшена. В ZF вьюшки это php-файл, с окончанием .phtml. Но о видах потом. Сейчас давайте посмотрим на код нашего контроллера:

  1. <?php
  2.  
  3. class IndexController extends Zend_Controller_Action
  4. {
  5.  
  6. public function init()
  7. {
  8. // Уставливаем название блога
  9. $this->view->title = 'Тестовый блог на Zend Framework';
  10. // Устанавливаем разделитель для тега title с помощью хелперов
  11. // headTitle() и setSeparator()
  12. $this->view->headTitle()->setSeparator(' | ');
  13. // Передаем заголовок в тег title, с помошью хелпера headTitle()
  14. $this->view->headTitle($this->view->title);
  15. }
  16.  
  17. public function indexAction()
  18. {
  19. // Создаем экземпляр модели постов
  20. $posts = new Application_Model_DbTable_Posts();
  21. // Выбираем все посты
  22. // Формируем условие
  23. $select = $posts->select()->order('created_at DESC')
  24. ->order('id DESC');
  25. // Выполняем запрос
  26. $this->view->posts = $posts->fetchAll($select);
  27. }
  28.  
  29. /**
  30.   * View post
  31.   *
  32.   * @return void
  33.   */
  34. public function postAction()
  35. {
  36. // Берем ID'шник из параметра
  37. $id = intval($this->_getParam('id', 0));
  38. if ($id > 0) {
  39. // Создаем экземпляр модели постов и выбираем посты по ID
  40. $post = new Application_Model_DbTable_Posts();
  41. $this->view->post = $post->getById($id);
  42. // Устанавливаем заголовок для поста в тег title
  43. $this->view->postTitle = $this->view->post['title'];
  44. $this->view->headTitle($this->view->postTitle);
  45. }
  46. }
  47.  
  48. }

Поясню код. Сначала обьявляем наш класс IndexController, наследующийся от Zend_Controller_Action. Далее объявляем открытые методы. init() - это действия, которые будут выполняться всегда. Типа констуктора. Сюда мы устанавливаем заголовок блога и разделитель. indexAction() - действие для главной страницы. Здесь мы выбираем посты с помощью метода fetchAll() класса Zend_Db_Table_Abstract. postAction() - показ отдельного поста. Здесь мы получаем параметр с помощью метода _getParam() и, если ID больше нуля выбираем пост, с помощью нашего ранее созданного метода getPostById($id).

Настало время третьей составляющей паттерна MVC, а именно view. За вид в Zend Framework отвечает компонент Zend_View. Мы для удобства будем использовать layout'ы, т.е. макеты в нашем виде. Фишка layout'ов, в том, что не нужно в каждом файле представления писать что-то типа include 'inc/header.php'. Вместо этого создается отдельный файл в котором описывается главный шаблон. В нужных местах прописываются переменные, в которые будут помещаться динамические части. Чтобы создать лайаут воспользуемся Zend_Tool. Прописываем в консоли команду:

  1. zf enable layout

Эта команда создаст каталог application/layout/scripts и поместит в него файл layout.phtml. Также в application.ini будет добавлена строчка:

  1. resources.layout.layoutPath = APPLICATION_PATH "/layouts/scripts/"

Приведу код нашего макета:

  1. <!DOCTYPE html>
  2. <meta charset=utf-8>
  3. <!--[if IE]>
  4. <script>
  5. document.createElement('header');
  6. document.createElement('nav');
  7. document.createElement('section');
  8. document.createElement('article');
  9. document.createElement('aside');
  10. document.createElement('footer');
  11. </script>
  12. <![endif]-->
  13. <link rel="stylesheet" href="<?php echo $this->baseUrl() ?>/css/style.css" media="screen" />
  14. <?php echo $this->headTitle() ?>
  15. </head>
  16. <div id="wrapper">
  17. <header id="header">
  18. <h1><a href="<?php echo $this->baseUrl() ?>"><?php echo $this->title ?></a></h1>
  19. </header>
  20. <?php echo $this->layout()->content ?>
  21. <aside>
  22. <section>
  23. <header>
  24. <h4>Категории</h4>
  25. </header>
  26. <ul>
  27. <li><a href="#">Первая категория</a></li>
  28. <li><a href="#">Вторая категория</a></li>
  29. </ul>
  30. </section>
  31. </<footer>
  32. <p>© <a href="http://gailimov.info">Канат Гайлимов</a> <?php echo date('Y') ?></p>
  33. </footer>
  34. </div> <!-- wrapper -->
  35. </body>
  36. </html>

Здесь объявляется стандартный доктайп HTML 5. Конечно, можно воспользоваться хелпером ZF, но я предпочел написать ручками. Далее подключаются css-файл, код которого я приводить не буду, но он будет включен в архив. Заметьте, здесь используется хелпер baseUrl(), который "знает" путь к каталогу нашего приложения. Так что, где бы ни был расположен наш блог, все пути будут правильными. headTitle() - возравщает заголовок нашего блога в теге title. Помните, в контроллере мы устанавливали значение для него? Самое интересное происходит в строке:

  1. <?php echo $this->layout()->content ?>

Сюда будет подставляться наш основной контент. Давайте теперь взглянем на файл со списком постов для главной (views/scripts/index/index.phtml):

  1. <section id="content">
  2. <section id="posts">
  3. <?php if (count($this->posts) < 1) : ?>
  4. <article>
  5. <p>Посты закончились</p>
  6. </article>
  7. <?php else : foreach ($this->posts as $post) : ?>
  8. <article>
  9. <header>
  10. <h2><a href="<?php echo $this->url(array('controller' => 'index', 'action' => 'post', 'id' => $post->id)) ?>"><?php echo $this->escape($post->title) ?></a></h2>
  11. </header>
  12. <?php echo $post->blog_post ?>
  13. <p class="date"><?php echo $this->escape($post->created_at) ?></p>
  14. </article>
  15. <?php endforeach; endif ?>
  16. </section> <!-- posts -->
  17. </section> <!-- content -->

Здесь ничего сложного, обычная проверка на массива с постами и вывод в цикле foreach. Новымы можгут быть лишь хелперы url() и escape(), служащие для построения ссылок и escape() для экранирования HTML-тегов. В параметры первому передается ассоциативный массив со значениями контроллера, экшена и параметры экшена.

А вот вьюха для отдельного поста (views/scripts/index/post.phtml):

  1. <section id="content">
  2. <section id="posts">
  3. <article>
  4. <header>
  5. <h2><?php echo $this->post['title'] ?></h2>
  6. </header>
  7. <?php echo $this->post['blog_post'] ?>
  8. <p class="date"><?php echo $this->post['created_at'] ?></p>
  9. </article>
  10. </section> <!-- posts -->
  11. </section> <!-- content -->

Теперь наконец можете набрать в браузере адрес своего проекта и посмотреть результат. В следующем посте мы сделаем вывод категорий и постов в них. Может и комментарии захватим. Так что подписывайтесь на RSS. Архив с исходниками можно забрать отсюда.

P.S. Критика, разрыв поста в клочья приветствуется :).

1
1043
5