Делаем сайт с помощью Zend Framework
Данный фрэймворк набирает хорошую популярность, однако ввиду малого количество хороших примеров, я решил и посвятить некоторую часть своего времени написанию примеров по ZF. Итак, первая вводная статья посвящена написанию приложения: «новостная лента с постраничной навигацией».Первое действие, которое необходимо сделать это загрузить последнюю версию фрэймворка с сайта: http://framework.zend.com/download/overview.
Далее можно скачать example всего нижеприведённого кода http://iexx.biz/uploads/download/id/35
Ну что ж, начнём. Создадим следующую структуру каталогов:
application\
\admin - Административная часть сайта \controllers – Здесь будут находится наши контроллеры \views \scripts - Директория для HTML шаблонов \default – Клиентская часть \controllers \views \scripts\ css\ - CSS styles forms\ - Классы форм, Валидаторы, конфигурационные файлы для форм. \ini_configs images\ - Изображения js\ - JavaScripts library\ - ZendFramework классы и другие утилиты \Zend models\ - Тут будем хранить модели данных userfiles\ - Пользовательские файлы( изображения для галерей, аватары и др.) index.php – Главный загрузочный файл routes.ini.php – здесь будем описывать пути (routes) acl.settings.php – Настройки доступа ( разделение прав пользователей ) .htaccess
Содержимое .htaccess:
RewriteEngine on
RewriteRule .* index.php
В директориях: /userfiles, /images, /css, /js должен лежать файл: .htaccess с таким содержимым: RewriteEngine off.
Начнём с основного загрузочного файла, он же bootstrap, index.php:
<?php
// Влючаем вывод ошибок
ini_set('display_errors', 'On');
error_reporting(E_ALL);
define('FS_PATH', dirname(__FILE__));
$inc_path_array = array('.', './library', './library/Zend', './forms', './models', get_include_path());
// Пропишем пути для доступа к zend библиотекам, формам и моделям данных
set_include_path(implode(PATH_SEPARATOR, $inc_path_array));
include_once "Zend/Loader.php";
// Влючаем автоподгрузку классов
Zend_Loader::registerAutoload();
// Запускаем компонент zend layout
Zend_Layout::startMvc();
// Загружаем конфигурационный файл
$config = new Zend_Config_Ini(dirname(FS_PATH) . '/config.ini.php');
Zend_Registry::set('config', $config);
// Объявляем абсолютный URL
define('HTTP_PATH', $config->general->site->path);
// Префикс таблиц баз данных
define('DB_PREFIX', $config->general->db->prefix);
// Подключаемся к БД
$db = Zend_Db::factory($config->general->db->adapter, $config->general->db->toArray());
Zend_Db_Table::setDefaultAdapter($db);
// Фикс для корректной работы с UTF-8
$db->query('SET NAMES UTF8');
// Запускаем сессию
Zend_Session::start();
// Настраиваем механизм хранения данных аутентификации
Zend_Auth::getInstance()->setStorage(new Zend_Auth_Storage_Session('auth'));
// Настраиваем контроллер
$frontController = Zend_Controller_Front::getInstance();
// Вызывать исключения или нет – берём из конфиг файла ( нужно для разделения тестового и реального сервера)
$frontController->throwExceptions((bool)$config->general->site->throwExceptions);
// Указываем директорию где искать модули (admin, default, …)
$frontController->addModuleDirectory('./application');
$frontController->setBaseUrl();
// Подгружаем конфиг.файл прав доступа
require_once('acl.settings.php');
// Определяем полномочия пользователя (из сессии). По умолчанию - гость
$role = Zend_Auth::getInstance()->hasIdentity() ? Zend_Auth::getInstance()->getIdentity()->permiss : 'guest';
// Регистрируем плагин для работы с полномочиями. Этот плагин будет рассмотрен далее.
$acl_plugin = new Zend_Controller_Plugin_Acl($acl, $role);
// Настраиваем страницу при отказе в доступе( 'ACTION', 'CONTROLLER', 'MODULE')
$acl_plugin->setErrorPage('join', 'user', 'default');
$frontController->registerPlugin($acl_plugin);
// Устанавливаем пути (routes) из конфиг файла.
$routes_config = new Zend_Config_Ini('routes.ini.php', 'allroutes');
$router = $frontController->getRouter();
$router->addConfig($routes_config, 'routes');
try {
// Run!
$frontController->dispatch();
}catch (Exception $e) {
// Сообщение об ошибке
echo '<body style="background-color: black; color: white"><h2>Sorry, this page unavailable now</h2>' . nl2br($e->getMessage()) . ' , ' . nl2br($e->getTraceAsString()) . '</body>';
}
На этом всё для этого файла
Следующий файл: config.ini.php
Он должен находится на директорию выше файла index.php. Это нужно из соображений безопасности, а также для того, чтобы можно было хранить разные настройки для сайта, а основной код – в общем SVN. Вот его примерное содержание:
[general]
db.adapter = PDO_MYSQL
db.dbname = news
db.host = localhost
db.password = password_for_news
db.prefix = news_
db.username = new_user
site.throwExceptions = true
site.path = http://zf_exam
[forms]
site.email_from = zf_example
site.email_email = dontreply@ zf_example
[emails]
recover.subject = Восстановление пароля
Файл routes.ini.php:
Прописываем роуты тут. Пока для примера сделаем вот такой. Далее будем расширять.
Вместо http://zf_exam/user/join у нас будет работать просто http://zf_exam/join
[allroutes]
; JOIN
routes.join.type = Zend_Controller_Router_Route_Static
routes.join.route = join
routes.join.defaults.module = default
routes.join.defaults.controller = user
routes.join.defaults.action = join
; View post
routes.viewpost.type = "Zend_Controller_Router_Route_Regex"
routes.viewpost.route = "news/(.+)\.html"
routes.viewpost.defaults.module = default
routes.viewpost.defaults.controller = news
routes.viewpost.defaults.action = view
routes.viewpost.map.1 = id
Файл acl.settings.php:
<br/>
<?php
$acl = new Zend_Acl();
$acl->addRole(new Zend_Acl_Role('guest'))
->addRole(new Zend_Acl_Role('member'), 'guest')
->addRole(new Zend_Acl_Role('admin'), 'member')
;
$acl->add(new Zend_Acl_Resource('user'))
->add(new Zend_Acl_Resource('news'))
->add(new Zend_Acl_Resource('index'))
;
/** Admin module */
$acl->add(new Zend_Acl_Resource('admin'))
;
/** Creating permissions */
$acl->allow('member', 'user')
->allow('member', 'index')
->allow('guest', 'news')
->allow('guest', 'index')
->allow('guest', 'user', 'join')
->allow('guest', 'user', 'login')
->allow('guest', 'user', 'forgot')
->allow('admin', 'admin')
;
Zend_Registry::set('acl', $acl);
<?php
$acl = new Zend_Acl();
$acl->addRole(new Zend_Acl_Role('guest'))
->addRole(new Zend_Acl_Role('member'), 'guest')
->addRole(new Zend_Acl_Role('admin'), 'member')
;
$acl->add(new Zend_Acl_Resource('user'))
->add(new Zend_Acl_Resource('news'))
->add(new Zend_Acl_Resource('index'))
;
/** Admin module */
$acl->add(new Zend_Acl_Resource('admin'))
;
/** Creating permissions */
$acl->allow('member', 'user')
->allow('member', 'index')
->allow('guest', 'news')
->allow('guest', 'index')
->allow('guest', 'user', 'join')
->allow('guest', 'user', 'login')
->allow('guest', 'user', 'forgot')
->allow('admin', 'admin')
;
Zend_Registry::set('acl', $acl);
Теперь приступим к созданию модели данных для новостей.
Файл /models/News.php
<?php
class News extends Zend_Db_Table {
protected $_name;
protected $_primary = 'id';
public function __construct()
{
$this->_name = DB_PREFIX . 'news';
parent::__construct();
}
}
Можно было сразу указать в $_name название таблицы, но мы ведь хотим использовать префиксы, чтобы можно было в эту же БД поместить например и другие проекты.
<?php
class Users extends Zend_Db_Table {
protected $_name;
protected $_primary = 'uid';
public function __construct()
{
$this->_name = DB_PREFIX . 'users';
parent::__construct();
}
}
Все файлы баз данных находятся в архиве для проекта.
Теперь мы займёмся написанием контроллера для главной страницы. По умолчанию при вызове http://zf_exam – вызывается страница http://zf_exam/default/index/index. Будет определена страница с default-модулем, index-контроллером и index-действием(action). Создаём файл: /application/default/controllers/IndexController.php.
<?php
class IndexController extends Zend_Controller_Action {
private $db;
// Метод, вызываемый при инициализации
public function init() {
// Для более удобного использования объекта DB адаптера
$this->db = Zend_Db_Table::getDefaultAdapter();
}
// Метод вызываемый после процесса диспетчеризации
public function postDispatch()
{
// Обрабатываем системные сообщения. Сначала получаем сообщения из сессии в переменную View, далее очищаем переменную из сессии
$this->view->messages = $this->_helper->flashMessenger->getMessages();
$this->_helper->flashMessenger->clearMessages();
}
/**
* Основной метод, вызываемый по умолчанию
*
*/
public function indexAction()
{
// Указываем заголовок для страницы
$this->view->page_title = 'ZF EXAMple - Главная';
// Создаём объект модели данных
$posts = new News;
// Показывать по 1 новости на страницу
$items_per_page = 1;
// Создаём Zend_DB_select - так как он нам понадобится для Paginator
$select = $this->db->select()
->from(DB_PREFIX . 'news')
->where("published='y'")
->order('adddate DESC')
;
// Создаём Paginator - для постарничной навигации
$paginator = new Zend_Paginator(new Zend_Paginator_Adapter_DbSelect($select));
$paginator->setCurrentPageNumber($this->_getParam('page', 1))
->setItemCountPerPage($items_per_page)
->setPageRange(20)
;
// Получаем HTML готового навигатора из шаблона index/page.phtml
$this->view->pagerHTML = $this->view->paginationControl($paginator, 'Sliding', 'pager.phtml');
// Выбираем текущие элементы и отправляем их в шаблон
$this->view->items = $paginator->getCurrentItems();
}
}
<phpcode>
<br/>
Далее опишем шаблон для index действия. Он будет отображать у нас список новостей со ссылками на просмотр новости и постраничную навигацию. Всё это будет обвёрнуто в общий HTML шаблон – Layout.phtml.
Файл: /application/defaul/views/scripts/index/index.phtml:<br/>
<br/>
<phpcode>
<div class="posts">
<? foreach($this->items as $item) : ?>
<div class="item">
<h1 class="story_title">
<a href="/news/<?=$item['id']?>.html">
<?=$this->escape($item['title'])?>
</a>
</h1>
<div class="post_content">
<?=$item['short_content']?>
</div>
<p class="adddate"><?=$item['adddate']?></p>
</div>
<? endforeach; ?>
<?=$this->pagerHTML?>
</div>
Далее приведён листинг основного файла шаблона layout.phtml:
<?=$this->doctype(Zend_View_Helper_Doctype::XHTML1_TRANSITIONAL)?>
<html>
<head>
<? $page_title = $this->page_title ? $this->page_title : 'zf';?>
<?=$this->headTitle($page_title)?>
<?=$this->headMeta()->appendHttpEquiv('Content-Type', 'text/html; charset=UTF-8')?>
<link href="/css/main.css" media="screen" rel="stylesheet" type="text/css" />
</head>
<body>
<? $_userID = Zend_Auth::getInstance()->hasIdentity() ? Zend_Auth::getInstance()->getIdentity()->uid : false;?>
<div class="main">
<div class="header">
<div class="main_menu">
<span>
<a href="/">Главная</a>
<? if(!$_userID) :?>
<a href="/join">#вход</a>
<?else:?>
<a href="/user/logout">#выход</a>
<? if(Zend_Auth::getInstance()->getIdentity()->permiss == 'admin') :?>
| <a href="/admin">ADMINISTRATOR</a>
<? endif;?>
<? endif;?>
</span>
</div>
</div>
<? /*
Вот именно сюда и будет вставлен контент из файла вида действия. (/application/default/views/scripts/index/index.phtml например)
*/ ?>
<?=$this->layout()->content?>
</div>
</body>
</html>
Для постраничного навигатора нам также необходим шаблон (/application/default/views/scripts/pager.phtml):
<?php if ($this->pageCount): ?>
<div class="item_pager">
<?php if (isset($this->previous)): ?>
<a href="<?= $this->url(array('page' => $this->previous)); ?>"></a>
<?php endif; ?>
<?php foreach ($this->pagesInRange as $page): ?>
<?php if ($page != $this->current): ?>
<a href="<?= $this->url(array('page' => $page)); ?>"><?= $page; ?></a>
<?php else: ?>
<a href="<?= $this->url(array('page' => $page)); ?>" class="current"><?= $page; ?></a>
<?php endif; ?>
<?php endforeach; ?>
<?php if (isset($this->next)): ?>
<a href="<?= $this->url(array('page' => $this->next)); ?>"></a>
<?php endif; ?>
</div>
<?php endif; ?>
Ну что ж, если до этого этапа вы всё сделали правильно, то у вас должны отобразиться новости ( по одной на страницу ) и постраничной навигатор. Если что-то не так – вернитесь назад и повторите все действия – либо напишите об ошибках в комментариях ниже.
Теперь мы напишем страницу просмотра новости. Ссылка на новость у нас выглядит так: http://zf_exam/news/1.html.
Если Вы посмотрите на наш файл routes.ini.php, то из него увидете, что при переходе по ссылке выше будет обработан default-модуль, News – контроллер и View – действие. Будет также установлен параметер id, равный судя по ссылке единице.
routes.viewpost.route = "news/(.+)\.html"
routes.viewpost.defaults.module = default
routes.viewpost.defaults.controller = news
routes.viewpost.defaults.action = view
routes.viewpost.map.1 = id
/application/default/controllers/NewsController.php будет выглядеть у нас так:
<?php
class NewsController extends Zend_Controller_Action {
private $db;
public function init()
{
$this->db = Zend_Db_Table::getDefaultAdapter();
}
public function postDispatch()
{
$this->view->messages = $this->_helper->flashMessenger->getMessages();
$this->_helper->flashMessenger->clearMessages();
}
/**
* Описываем Action для просмотра новости
*/
public function viewAction()
{
// Объект для модели данных
$news = new News;
try {
// Извлекаем параметр id
$id = $this->_getParam('id');
// Ищём строку в модели данных исходя из первичного ключа id
$itemInfo = $news->find($id);
if (!$itemInfo) {
throw new Exception('Invalid id');
}
// Преобразуем в массив и отдаём в View
$this->view->itemInfo = $itemInfo->getRow(0)->toArray();
$this->view->page_title = 'zf - Статьи - ' . $this->view->itemInfo['title'];
}catch (Exception $e) {
throw new Exception($e);
}
}
}
Теперь опишем соотствующий файл вида:
/application/default/views/scripts/news/view.phtml:
<div class="viewItem">
<h3><?=$this->escape($this->itemInfo['title'])?></h3>
<div><?=$this->itemInfo['content']?></div>
<div class="adddate">Added: <?=$this->itemInfo['adddate']?></div>
</div>
Часть «Просмотр новости» завершена. Далее нам необходимо управлять новостями и иметь возможность добавлять новые, но для этого нам нужно быть зарегистрированными в системе и иметь полномочия администратора. Напишем простую страницу входа/регистрации. Создадим для этого контроллер users (/application/default/controllers/UsersController.php):
<?php
class UserController extends Zend_Controller_Action {
private $db;
public function init()
{
$this->db = Zend_Db_Table::getDefaultAdapter();
}
public function postDispatch()
{
$this->view->messages = $this->_helper->flashMessenger->getMessages();
$this->_helper->flashMessenger->clearMessages();
}
public function joinAction()
{
// Проверяем, пришли ли к нам POST данные
if ($this->_request->isPost()) {
// Поскольку это AJAX вызов, вы должны отключить рендеринг View ( users/join.phtml)
$this->_helper->viewRenderer->setNoRender();
// А также рендеринг основного HTML-шаблона - Layout (layout.phtml)
$this->_helper->layout->disableLayout();
$error = null;
try {
// Создаём объект нашей формы
$form = new RegisterForm;
// Получаем POST Данные
$post = $this->_request->getPost();
// Проверяем поля
if (!$post['email'] || !$post['login'] || !$post['pwd']) {
throw new Exception('Заполните все поля');
}
// Производим валидацию формы
if (!$form->isValidPartial($post)) {
$messages = $form->getMessages();
$message_str = '';
// Собираем ошибки
foreach ($messages as $element_name => $descr) {
if (is_array($descr)) {
foreach ($descr as $code => $err_descr) {
$message_str .= $form->$element_name->getLabel() . ": $err_descr<br/>";
}
}
}
// Кодируем тексты ошибок при помощи JSON. Только эти данные вернутся в браузер.
$this->_response->appendBody(Zend_Json_Encoder::encode(array('error' => $message_str)));
return;
}
// Валидация успешна - можем взять отфильтрованные данные с формы
$values = $form->getValues();
// Подготавливаем данные для вставки
$prepared = array();
$prepared['permiss'] = 1; // Уровень доступа
$prepared['ulogin'] = $values['login'];
$prepared['upassword'] = sha1($values['pwd']); // Шифруем пароль
$prepared['email'] = $values['email'];
$prepared['adddate'] = new Zend_Db_Expr('CURDATE()'); // Вставляем текущую дату. - Для этого нужно создать исключение
// Создаём объект модели данных users и вставляем запись
$users = new Users;
$userID = $users->insert($prepared);
}catch (Exception $e) {
$error = $e->getMessage();
}catch (Zend_Db_Exception $e) {
$error = $e->getMessage();
}
if ($error) {
// В случае ошибки - выводим ошибки в браузер
$this->_response->appendBody(Zend_Json_Encoder::encode(array('error' => $error)));
return;
}
// Сохраняем параметры в _request объект для передачи в Login - Action - таким образом мы сразу же произведём и вход
$this->_request->setParam('email', $values['email']);
$this->_request->setParam('pwd', $values['pwd']);
// Делаем переход на Login action - см. Action ниже
$this->_forward('login', 'User');
}
}
public function loginAction()
{
if ($this->_request->isPost()) {
// Опять отключаем все рендереры
$this->_helper->viewRenderer->setNoRender();
$this->_helper->layout->disableLayout();
// Берём параметры
$email = $this->_request->getParam('email');
$password = $this->_request->getParam('pwd');
$storeSession = $this->_request->getParam('storeSession', false);
try {
if (empty($email) || empty($password)) {
throw new Exception('Заполните поля');
}
// Делаем TRIM фильтрацию
$filter = new Zend_Filter_StringTrim();
$email = $filter->filter($email);
$password = $filter->filter($password);
// Делаем попытку входа. Устанвливаем DB adapter, таблицу с пользователями, параметры email, pwd
$authAdapter = new Zend_Auth_Adapter_DbTable($this->db);
// Описываем параметры таблицы
$authAdapter->setTableName(DB_PREFIX . 'users')
->setIdentityColumn('email')
->setCredentialColumn('upassword');
// Устанавливаем входяхие параметры
$authAdapter->setIdentity($email)
->setCredential(SHA1($password));
// Производим аутентификацию
$result = $authAdapter->authenticate();
// Выбираем информацию о пользователе
$user_info = $authAdapter->getResultRowObject();
// Получаем код ответа
$code = $result->getCode();
switch ($code) {
default:
case Zend_Auth_Result::FAILURE_IDENTITY_NOT_FOUND:
throw new Exception('Неверный емайл');
break;
case Zend_Auth_Result::FAILURE_CREDENTIAL_INVALID:
throw new Exception('Неверный пароль');
break;
case Zend_Auth_Result::SUCCESS:
// Пишем данные в сессию
Zend_Auth::getInstance()->getStorage()->write($authAdapter->getResultRowObject(null, array('upassword', 'confirm_code')));
// Uppdate last Login
$usersModel = new Users;
$usersModel->update(array('lastlogin' => new Zend_Db_Expr('NOW()')), 'uid = ' . $user_info->uid);
break;
}
}catch (Exception $e) {
$this->getResponse()->appendBody(Zend_Json_Encoder::encode(array('error' => $e->getMessage())));
return;
}
// Если нажали на сохранить - сохраняем несколько дней
if ($storeSession) {
Zend_Session::rememberMe(864000);
}
// Возвращаем код DONE вместе с логином пользователя
$this->getResponse()->appendBody(Zend_Json_Encoder::encode(array('done' => Zend_Auth::getInstance()->getIdentity()->ulogin)));
}
}
public function logoutAction()
{
// Отключили render
$this->_helper->viewRenderer->setNoRender();
// Очистили сессию
Zend_Auth::getInstance()->clearIdentity();
$this->_redirect('/');
}
}
Далее, дабы не захламлять страницу HTML кодом я не буду приводить её листинг, это обычная join/login форма. На неё можно посмотреть скачав архив.
Вы можете зайти под учётной записью admin@admin.ru и паролем ‘1111’. После в верхнем меню вы увидите ссылку на админку, которую нам предстоит сделать. Начнём с файла /application/admin/controllers/IndexController.php
<?php
// Именно так именуются классы контроллера отличного от Default
class Admin_IndexController extends Zend_Controller_Action {
private $db;
public function init()
{
$this->db = Zend_Db_Table::getDefaultAdapter();
}
public function postDispatch()
{
$this->view->messages = $this->_helper->flashMessenger->getMessages();
$this->_helper->flashMessenger->clearMessages();
}
/**
* Просто покажем шаблон
*/
public function indexAction()
{
}
}
View для него: /application/admin/views/scripts/index/index.phtml:
<phpcode>
<div class="admin_item">
<a href="/admin/news">News</a>
<a href="/admin/news/add">Add a News</a>
</div>
Layout (/application/admin/views/scripts/layout.phtml) для админки:
<?=$this->doctype(Zend_View_Helper_Doctype::XHTML1_TRANSITIONAL)?>
<html>
<head>
<?=$this->headTitle('zf example')?>
<?=$this->headMeta()->appendHttpEquiv('Content-Type', 'text/html; charset=UTF-8')?>
<link href="/css/main.css" media="screen" rel="stylesheet" type="text/css" />
</head>
<body>
<div class="main">
<div class="header">
<div class="main_menu">
<a href="/">Главная сайта</a>
<a href="/admin">Администраторская</a>
</div>
</div>
</div>
<?=$this->layout()->content?>
</div>
</body>
</html>
<phpcode>
<br/><br/>
А вот и наш основной файл админки: /application/admin/controllers/NewsController.php
<br/><br/>
Пусть вас не пугает незнакомый компонент vcGrid. Ему будет посвящена отдельная статья, а пока примите как есть.
Я использую именно сейчас повсюду, поэтому заменять стандартным и переписывать ох как не хочется ;(
<br/><br/>
<phpcode>
<?php
class Admin_NewsController extends Zend_Controller_Action
{
private $_db;
public function init()
{
$this->_db = Zend_Db_Table::getDefaultAdapter();
}
public function postDispatch()
{
$this->view->messages = $this->_helper->flashMessenger->getMessages();
$this->_helper->flashMessenger->clearMessages();
}
public function indexAction()
{
// Определяем способ сортировки
define('AJAX_MODE', true);
$this->view->is_ajax = (bool)$this->_getParam('ajax', 0);
$this->view->is_pager_ajax = (bool)$this->_getParam('pagerAjax', 0);
// Элементов на страницу
$items_per_page = 100;
// Текущая страница
$current_page = $this->_getParam('page', 1);
// Параметр сортировки
$in_sort = $this->_getParam('sort', 0);
// Создаём объект Visual Component Grid
$grid = new vcGrid();
// Описивыем заголовки ( ключ - возможное имя из базы, значение - Название заголовка в таблице,
// если заголовок - объект исключения vcGripHeaderExpr - значит для него не будет использоваться сортировка
$headers = array(
'id' => 'ID',
'title' => 'Title',
'adddate' => 'Added',
'published' => 'Published?',
'delete_fld' => new vcGripHeaderExpr('Delete'),
'edit_fld' => new vcGripHeaderExpr('Edit')
);
// Основной select запрос
$select = $this->_db->select()
->from(array('n' => DB_PREFIX . 'news'))
;
// Устанавливаем Pager Limits
$data = $grid->SetLimit($current_page, $items_per_page)
// Заголовки
->SetHeaders($headers)
// Select Object
->SetSelect($select)
// Текущий параметр сортировки и параметр по умолчанию
->SetOrder($in_sort, 'adddate', 'DESC')
// Устанавливаем объект вида
->SetView($this->view)
// Текущий URL ( для валидной работы пейджера)
->SetBaseURL('/admin/news/index')
// Определяем позицию пейджера
->SetPagerPosition(vcGrid::SHOW_PAGER_BOTTOM)
// Определяем шаблона для пейджера
->SetPagerTemplate('pagination_manage_items.phtml')
// CSS класс для таблицы
->SetCSSTableClass('manage_table')
// Возвращаем данные после обработки пейджера для их модификации
->GetData();
// Переопределяем поля, форматируем некоторые данные
foreach ($data as $key => &$item) {
$adddate = new Zend_Date($item['adddate'], 'Y-m-d');
$title = $item['title'];
$item['title'] = '<a href="/news/' . $item['id'] . '.html">' . $title . '</a>';
$item['adddate'] = $adddate->toString('d.m.Y');
$item['published'] = $item['published'] == 'y' ? '<a href="/admin/news/publish/value/0/id/' . $item['id'] . '" />YES</a>' : '<a href="/admin/news/publish/value/1/id/' . $item['id'] . '">N0</a>';
$item['delete_fld'] = '<a href="/admin/news/delete/id/' . $item['id'] . '" onclick="return confirm(\'Are you sure you want to delete this item?\')"><img src="/images/delete_pic.gif" title="Delete" /></a>';
$item['edit_fld'] = '<a href="/admin/news/edit/id/' . $item['id'] . '"><img src="/images/editpic.gif" title="Edit Item"/></a>';
}
// Устанавливаем изменённые данные
$grid->SetData($data);
// Производим рендеринг vcGRID
$this->view->vcGrid = $grid->render();
// Если это AJAX запрос на обновление данных - отключаем стандартные рендеры и отправляем отрендереный vcGRID
if ($this->view->is_ajax || $this->view->is_pager_ajax) {
$this->_helper->layout->disableLayout();
$this->_helper->viewRenderer->setNoRender();
$this->getResponse()->appendBody($this->view->vcGrid);
}
}
// Форма добавления новости
public function addAction()
{
// Создаём объект нашей формы новостей
$form = new NewsForm;
// Делаем попытку сохранить новость
$this->saveNews($form);
// Передаём форму для рендеринга формы
$this->view->form = $form;
}
// Форма редактирования новости
public function editAction()
{
$id = (int)$this->_getParam('id');
$form = new NewsForm;
// Меняем Action формы - на редактирование
$form->setAction('/admin/news/edit/id/' . $id);
// Устанавливаем HIDDEN поле - параметр для редактирования
$form->id->setValue($id);
// Устанавливаем рендеринг другого Action - addAction
$this->_request->setActionName('add');
try {
// Извлекаем данные о новости
if (!$id) throw new Exception('Invalid ID');
$newsModel = new News;
$res = $newsModel->find($id);
if (!$res) throw new Exception('Invalid ID');
$res = $res->getRow(0)->toArray();
// Отбираем только необходимые поля
$fields = array('id', 'title', 'content', 'short_content');
$form_defs = array();
foreach ($res as $key => $value) {
if (in_array($key, $fields)) {
$form_defs[$key] = $value;
}
}
// Пытаемся сохранить данные
$this->saveNews($form, $form_defs);
// Устанавливаем значения по умолчанию для объекта формы - $form
$form->setDefaults($form_defs);
}catch (Exception $e) {
$this->_helper->FlashMessenger->addMessage(array('error' => $e->getMessage()));
$this->_redirect('/admin/news');
}
$this->view->form = $form;
}
// Сохранение новости, $form_defs будет ненулевым при редактировании новости
private function saveNews(Zend_Form $form, $form_defs = null)
{
// При редактировании извлекаем ID
if ($form_defs) {
$newsID = $form_defs['id'];
}else {
$newsID = 0;
}
// URL для редиректа
$self_url = $newsID ? '/admin/news/edit/id/' . $newsID : '/admin/news/add';
// POST данные приняты
if ($this->_request->isPost()) {
// Валидация формы
if (!$form->isValid($_POST)) {
// Получаем список ошибок и делаем переадресацию
$messages = $form->getMessages();
foreach ($messages as $element_name => $descr) {
if (is_array($descr)) {
foreach ($descr as $code => $err_descr) {
$this->_helper->FlashMessenger->addMessage(array('error' => strtoupper($element_name) . ": $err_descr"));
}
}
}
$this->_redirect($self_url);
return;
}
// Получаем отфильтрованные данные
$form_values = $form->getValues();
$data = array();
$data['title'] = $form_values['title'];
$data['content'] = $form_values['content'];
$data['short_content'] = $form_values['short_content'];
try {
$newsModel = new News;
// При редактировании обновляем данные
if ($newsID) {
$newsModel->update($data, 'id=' . $newsID);
// Создаём новую новость
}else {
$data['uid'] = Zend_Auth::getInstance()->getIdentity()->uid;
$data['adddate'] = new Zend_Db_Expr('NOW()');
$newsModel->insert($data);
}
// извещаем об успешной операции - переадресуемся
$this->_helper->FlashMessenger->addMessage(array('done' => $data['title'] . ' news saved.'));
$this->_redirect('/admin/news');
}catch (Exception $e) {
// Отправляем ошибку - переадресуемся на текущую страницу
$this->_helper->FlashMessenger->addMessage(array('error' => $e->getMessage()));
$this->_redirect($self_url);
}
}
}
// Удаляем новость - тут всё просто, всё было описано ранее
public function deleteAction()
{
$newsID = (int)$this->_getParam('id');
$newsModel = new News;
try {
if (!$newsID) throw new Exception('Invalid ID');
$res = $newsModel->find($newsID);
if (!$res) throw new Exception('Invalid ID');
$res = $res->getRow(0);
$newsModel->delete('id = ' . $newsID);
$this->_helper->FlashMessenger->addMessage(array('done' => $res->title . ' deleted.'));
}catch (Exception $e) {
$this->_helper->FlashMessenger->addMessage(array('error' => $e->getMessage()));
}
$this->_redirect('/admin/news');
}
public function publishAction()
{
$newsID = (int)$this->_getParam('id');
$value = (int)$this->_getParam('value');
$value = $value ? 'y' : 'n';
$newsModel = new News;
try {
if (!$newsID) throw new Exception('Invalid ID');
$newsModel->update(array('published' => $value), 'id = ' . $newsID);
}catch (Exception $e) {
$this->_helper->FlashMessenger->addMessage(array('error' => $e->getMessage()));
}
$this->_redirect('/admin/news');
}
}
А теперь о шаблонах. Вот так выглядит /application/admin/views/scripts/news/index.phtml:
<div class="std_content">
<a href="/admin/news/add">Add a news</a><br/> <br/> <br/>
<div class="content">
<div class="input_errors">
<?if (isset($this->messages) && !empty($this->messages)) : ?>
<?foreach ($this->messages as $message) :?>
<div class="<?=key($message) == 'error' ? 'messenger_error' : 'messenger_done';?>"><?=array_pop($message)?></div>
<?endforeach;?>
<?endif;?>
</div>
<div class="manage_content">
<?=$this->vcGrid?>
</div>
</div>
</div>
Ну и сама форма добавления/редактирования новости (/application/admin/view/scripts/news/add.phtml):
<div class="std_content">
<form method="POST" class="admin_forms" action="<?=$this->form->getAction()?>">
<h3>добавление новости</h3>
<?if (isset($this->messages) && !empty($this->messages)) : ?>
<?foreach ($this->messages as $message) :?>
<div class="<?=key($message) == 'error' ? 'messenger_error' : 'messenger_done';?>"><?=array_pop($message)?></div>
<?endforeach;?>
<?endif;?>
<label>Title: <?=$this->formText($this->form->title->getName(), $this->form->title->getValue())?></label>
<div style="margin:10px 0">
<div>Content Short:</div>
<?=$this->formTextarea($this->form->short_content->getName(), $this->form->short_content->getValue());?>
</div>
<div style="margin:10px 0">
<div>Content:</div>
<?=$this->formTextarea($this->form->content->getName(), $this->form->content->getValue());?>
</div>
<?=$this->formHidden($this->form->id->getName(), $this->form->id->getValue())?>
<input type="submit" value="submit"/>
</form>
</div>
Так, теперь разберём формы, которые мы использовали для добавлении/редактировании новостей (/forms/NewsForm.php).
<?php
class NewsForm extends Zend_Form
{
const FORM_FILE = 'ini_configs/NewsForm.ini.php';
public function init()
{
$config = new Zend_Config_Ini(self::FORM_FILE);
$this->setConfig($config->NewsForm);
}
}
На этапе инициализации мы просто подсовываем ini-file (/forms/ini_configs/NewsForm.ini.php) с параметрами формы. Вот так выглядит ini-файл :
; General
NewsForm.action = "/admin/news/add"
NewsForm.method = "post"
NewsForm.attribs.id = "addNews"
NewsForm.attribs.enctype = "application/x-www-form-urlencoded"
NewsForm.elements.title.type = "text"
NewsForm.elements.title.options.validators.strlen.validator = "StringLength"
NewsForm.elements.title.options.validators.strlen.options.min = "2"
NewsForm.elements.title.options.validators.strlen.options.max = "255"
NewsForm.elements.title.options.filters.striptags.filter = "StripTags"
NewsForm.elements.title.options.required = true
NewsForm.elements.title.options.label = "Title"
NewsForm.elements.content.type = "text"
NewsForm.elements.content.options.required = true
NewsForm.elements.short_content.type = "text"
NewsForm.elements.short_content.options.required = true
; for edit mode
NewsForm.elements.id.type = "hidden"
Ну и форма регистрации (/forms/RegisterForm.php) :
<?php
class RegisterForm extends Zend_Form
{
public function init()
{
$form_elements = array();
// Описываем Action формы и метод отправки данных
$this->setAction('/user/join')
->setMethod('post');
// Создаём элемент - login
$fname = new Zend_Form_Element_Text('login');
// ОБъявляем валидатор и создаём для него сообщение об ошибке
$validator = new Validate_AlreadyExistsLogin;
$validator->setMessage('Такой логин занят, выберите другой');
$fname
->addValidator($validator) // Валидатор на совпадение с другими логинами
->addValidator(new Zend_Validate_Alnum) // Alnum - валидатор
->addValidator(new Zend_Validate_StringLength(1, 30)) // Валидатор длины строки
->setRequired(true) // Это обязательное поле
->addFilter(new Zend_Filter_StripTags) // Фильтр для него - исключение тегов
->addFilter(new Zend_Filter_StringTrim) // Ещё фильтр для обрезания лишних пробелов
;
array_push($form_elements, $fname);
// ОБъявляем валидатор и создаём для него сообщение об ошибке. На этот раз это Email
$validator = new Validate_AlreadyExistsEmail;
$validator->setMessage('Такой емайл уже зарегистрирован');
$email = new Zend_Form_Element_Text('email');
->addValidator(new Zend_Validate_StringLength(6, 50))
->addValidator($validator)
->addValidator(new Zend_Validate_EmailAddress)
->addFilter(new Zend_Filter_StripTags())
->setRequired(true)
->setLabel('Email')
->addFilters(array('StringToLower'));
array_push($form_elements, $email);
$password = new Zend_Form_Element_Text('pwd');
$validator = new Zend_Validate_StringLength(4, 50);
$validator->setMessage('Введите пароль больше 4х символов');
$password->addValidator(new Zend_Validate_Alnum())
->setLabel('Enter password')
->setRequired(true)
->setLabel('Password')
->addFilter(new Zend_Filter_StripTags())
->addValidator(new Zend_Validate_Alnum)
->addValidator($validator);
array_push($form_elements, $password);
// Добавляем элементы в форму
$this->addElements($form_elements);
}
}
// Валидатор проверяющий существование такого Email
class Validate_AlreadyExistsEmail extends Zend_Validate_Abstract {
const ALREADY_ISSET = 'alreadyIsset';
protected $_messageTemplates = array(
self::ALREADY_ISSET => 'This email is already registered'
);
public function isValid($value, $context = null) {
$value = (string) $value;
$users = new Users;
$value = Zend_Db_Table::getDefaultAdapter()-> quote($value);
$data = $users->fetchRow("email = $value");
if (is_null($data)) {
return true;
}
$dta = $data->toArray();
if (empty($dta)) {
return true;
}
$this->_setValue($value);
$this->_error(self::ALREADY_ISSET);
return false;
}
}
// Валидатор проверяющий существование такого Логина
class Validate_AlreadyExistsLogin extends Zend_Validate_Abstract {
const ALREADY_ISSET = 'alreadyIsset';
protected $_messageTemplates = array(
self::ALREADY_ISSET => 'already isset'
);
public function isValid($value, $context = null) {
$value = (string) $value;
$users = new Users;
$value = Zend_Db_Table::getDefaultAdapter()-> quote($value);
$data = $users->fetchRow("ulogin = $value");
if (is_null($data)) {
return true;
}
$dta = $data->toArray();
if (empty($dta)) {
return true;
}
$this->_setValue($value);
$this->_error(self::ALREADY_ISSET);
return false;
}
}
Вот пожалуй и всё на сегодня.
Комментарии:
Станислав
547 дней назад
Смените дизайн сайта, либо хотя бы сделайте, что бы ваш текст нормально читался.
Ваши полотна, читать ужасно.
Хоть и интересно.
iexx
544 дня назад
OKie, что-нить придумаю в ближайшее время
Novarg
547 дней назад
Какую версию ZF вы использовали?
В последней нет Zend_Controller_Plugin_Acl
iexx
544 дня назад
Так его и не должно там быть =) это плагин - я его приложил поэтому к аттачу
Novarg
546 дней назад
Практически при любом действии с контентом в админке возникает ошибка
Cannot send headers; headers already sent in D:\HomServ\home\zf\application\admin\controllers\NewsController.php, line 1
В чем может быть проблема?
amurdel
545 дней назад
Ошибка в том что пхп попытался установить заголовок для отправляемых данных а они уже отправились. Поэтому ищите в каком нибудь файле пробелы перед "
amurdel
545 дней назад
Ошибка в том что пхп попытался установить заголовок для отправляемых данных а они уже отправились. Поэтому ищите в каком нибудь файле пробелы перед "
Novarg
543 дня назад
Пока помогает смена
$this-_redirect('/admin/news');
на $this-_forward('index', 'news', 'admin');
iexx
543 дня назад
А есть у кого-нить ещё эта же проблема?
Я пересмотрел у себя - у меня все ок! =)
Может вставили где-нибудь перенос строки в начале файла например?
Novarg
542 дня назад
Ошибка возникает даже при использовании набора ваших исходников (ссылка вначале статьи)
Может потому что я использую ЗФ собранный из свн репозитория, хотя врятли конечно, просто незнаю уже на что и думать.
Да и кстати спасибо огромное за статью, она позволила разобраться во многих вещах. Сразу начинать с офф мануала тяжеловато :)
iexx
541 день назад
Ну попробуйте скачать релизную версию ZF и перезалить файлы. Я ещё раз просмотрел всё. Взял набор исходников из архива, долил туда ZF 1.7.1 - и никаких проблем нет.
ers
529 дней назад
Очень хорошая статья, только как сделать чтобы сайт запускался не прямо из виртуального хоста, а скажем из папочки? Я так понимаю нужно везде прописать baseurl ? но после этого логин и регистрация не работают.
iexx
529 дней назад
К сожалению пути жёстко прописаны, поэтому в шаблонах и JS скриптах надо править URL.
Поправь пути в /js/main.js
Gorik
292 дня назад
Спасибо интересно!
shrize
249 дней назад
спасибо. очень пояснительно
Макс
249 дней назад
Интересная статья но пос\чему то не заносит русский текст в новости (( в чем может быть дело ?
iexx
248 дней назад
Может БД в неверной кодировке у тебя?
volodia
133 дня назад
cool!