---
title: "Zend Framework: создаем блог. Шаг 1"
description: "Аллоха, ювижинцы! Ниже наглая копипаста с моего стандалона. Понеслась. Сегодня мы продолжим изучать ..."
author: "KanatYvi"
published: "2011-02-12T03:29:19+00:00"
modified: "2011-02-12T23:35:55+00:00"
locale: "ru"
canonical_url: "https://yvision.kz/post/zend-framework-sozdaem-blog-shag-1-119762"
markdown_url: "https://yvision.kz/post/zend-framework-sozdaem-blog-shag-1-119762/markdown"
site_name: "Yvision.kz"
---

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

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

Аллоха, ювижинцы! Ниже наглая копипаста с моего [стандалона](http://gailimov.info). Понеслась.

Сегодня мы продолжим изучать Zend Framework. В [прошлом посте](http://gailimov.info/post/zend-framework-vvedenie-i-ustanovka/) мы с вами кратко познакомились с ним, установили и вывели стандартное приветствие в браузер. Пришло время разработать свое первое приложение.

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

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

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

- К каждому посту можно оставить комментарий.

- Аутентификация администратора.

- CRUD категорий, постов и комментов. Админки как таковой не будет, просто админу будут выводится ссылки на CRUD.

В принципе все. Эдакий simple blog. Если у вас имеются какие-нибудь предложения по дополнительной функциональности - велкам в комменты. Теперь приступим к самому вкусному - кодингу. Далее предпологается, что вы уже [установили и создали](http://gailimov.info/post/zend-framework-vvedenie-i-ustanovka/) свой проект при момощи Zend_Tool. Теперь взгянем на сгенерированную структуру проекта.

![Структура проекта Zend Framework](http://storage.yvision.kz/images/user/kanatgailimov/LoPLx48b1DH0yxIIgNH53dBlw3GVW1.png)

С непривычки такое обилие папок может запутать и напугать. Но немного привыкнув, все станет понятно и логично. В configs хранится конфигурация вашего проекта, в controllers, models, views, как можно догадаться 3 составляющие части MVC - контроллеры, модели и вьюшки. В docs документация, в library дистрибутив ZF (если вы последовали совету из [предыдущего поста](http://gailimov.info/post/zend-framework-vvedenie-i-ustanovka/), то эта папка у вас должна быть пуста, т.е. она и так будет пуста, пусть такой и остается), в 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 добавляем:

``` ; DB configresources.db.adapter = PDO_MYSQLresources.db.params.host = localhostresources.db.params.username = rootresources.db.params.password = resources.db.params.dbname = zftestresources.db.params.charset = utf8 ```

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

``` CREATE TABLE IF NOT EXISTS `zf_posts` ( `id` MEDIUMINT(5) UNSIGNED NOT NULL AUTO_INCREMENT, `category_id` TINYINT(2) UNSIGNED NOT NULL, `url` VARCHAR(100) NOT NULL, `title` VARCHAR(100) NOT NULL, `blog_post` MEDIUMTEXT NOT NULL, `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`)) ENGINE = MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci; CREATE TABLE IF NOT EXISTS `zf_categories` ( `id` TINYINT(2) UNSIGNED NOT NULL AUTO_INCREMENT, `url` VARCHAR(100) NOT NULL, `title` VARCHAR(100) NOT NULL, PRIMARY KEY (`id`)) ENGINE = MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci; CREATE TABLE IF NOT EXISTS `zf_comments` ( `id` MEDIUMINT(7) UNSIGNED NOT NULL AUTO_INCREMENT, `post_id` MEDIUMINT(5) UNSIGNED NOT NULL, `name` VARCHAR(50) NOT NULL, `email` VARCHAR(50) NOT NULL, `url` VARCHAR(50), `blog_comment` TEXT NOT NULL, `created_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, PRIMARY KEY (`id`)) ENGINE = MyISAM CHARACTER SET utf8 COLLATE utf8_general_ci; ```

Этот файл с полным дампом БД под названием schema.sql, вы найдете в прилагаемом [архиве](http://gailimov.info/files/zftest.tar.gz), в папке docs. Здесь у нас имеются 3 таблицы: посты, категории и комменты. На них останавливаться не будем.

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

``` fetchRow('id = ' . $id); if (!$row) { throw new Exception('Ахтунг! Выборка поста не удалась :('); } return $row; }} ```

Код подробно прокомментирован, но думаю пояснить все же стоит. Первым делом создаем класс, наследующийся от 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 помимо самих экшенов создаст нам соответствующие вьюшки. Итак, открываем консоль, переходим в директорию с проектом и вводим команду:

``` zf create action post index ```

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

``` view->title = 'Тестовый блог на Zend Framework'; // Устанавливаем разделитель для тега title с помощью хелперов // headTitle() и setSeparator() $this->view->headTitle()->setSeparator(' | '); // Передаем заголовок в тег title, с помошью хелпера headTitle() $this->view->headTitle($this->view->title); } public function indexAction() { // Создаем экземпляр модели постов $posts = new Application_Model_DbTable_Posts(); // Выбираем все посты // Формируем условие $select = $posts->select()->order('created_at DESC') ->order('id DESC'); // Выполняем запрос $this->view->posts = $posts->fetchAll($select); } /** * View post * * @return void */ public function postAction() { // Берем ID'шник из параметра $id = intval($this->_getParam('id', 0)); if ($id > 0) { // Создаем экземпляр модели постов и выбираем посты по ID $post = new Application_Model_DbTable_Posts(); $this->view->post = $post->getById($id); // Устанавливаем заголовок для поста в тег title $this->view->postTitle = $this->view->post['title']; $this->view->headTitle($this->view->postTitle); } } } ```

Поясню код. Сначала обьявляем наш класс 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. Прописываем в консоли команду:

``` zf enable layout ```

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

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

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

``` document.createElement('header'); document.createElement('nav'); document.createElement('section'); document.createElement('article'); document.createElement('aside'); document.createElement('footer'); baseUrl() ?>/css/style.css" media="screen" /> headTitle() ?>

# [title ?>](baseUrl() ?>) layout()->content ?>

#### Категории - [Первая категория](#) - [Вторая категория](#) © [Канат Гайлимов](http://gailimov.info)

```

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

``` layout()->content ?> ```

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

``` posts) Посты закончились

 posts as $post) : ?>

## [escape($post->title) ?>](url(array('controller' => 'index', 'action' => 'post', 'id' => $post->id)) ?>) blog_post ?> escape($post->created_at) ?>

```

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

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

```

## post['title'] ?> post['blog_post'] ?> post['created_at'] ?>

```

Теперь наконец можете набрать в браузере адрес своего проекта и посмотреть результат. В следующем посте мы сделаем вывод категорий и постов в них. Может и комментарии захватим. Так что подписывайтесь на [RSS](http://feeds.feedburner.com/gailimov). Архив с исходниками можно забрать [отсюда](http://gailimov.info/files/zftest.tar.gz).

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

---

Source: [https://yvision.kz/post/zend-framework-sozdaem-blog-shag-1-119762](https://yvision.kz/post/zend-framework-sozdaem-blog-shag-1-119762)