Включите JavaScript и Флэш!

Пишем новостную ленту с помощью Zend Framework

Делаем сайт с помощью 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);
 


Теперь приступим к созданию модели данных для новостей.
Файл /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');
        $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;
    }
}
 



Вот пожалуй и всё на сегодня.

Ключевые слова: PHP, Zend Framework 26 января, года 2010го

Комментарии:

Станислав

1

547 дней назад

Смените дизайн сайта, либо хотя бы сделайте, что бы ваш текст нормально читался.
Ваши полотна, читать ужасно.
Хоть и интересно.

avatar

iexx

0

544 дня назад

OKie, что-нить придумаю в ближайшее время

Novarg

1

547 дней назад

Какую версию ZF вы использовали?
В последней нет Zend_Controller_Plugin_Acl

avatar

iexx

0

544 дня назад

Так его и не должно там быть =) это плагин - я его приложил поэтому к аттачу

Novarg

0

546 дней назад

Практически при любом действии с контентом в админке возникает ошибка

Cannot send headers; headers already sent in D:\HomServ\home\zf\application\admin\controllers\NewsController.php, line 1

В чем может быть проблема?

amurdel

1

545 дней назад

Ошибка в том что пхп попытался установить заголовок для отправляемых данных а они уже отправились. Поэтому ищите в каком нибудь файле пробелы перед "

amurdel

0

545 дней назад

Ошибка в том что пхп попытался установить заголовок для отправляемых данных а они уже отправились. Поэтому ищите в каком нибудь файле пробелы перед "

Novarg

0

543 дня назад

Пока помогает смена
$this-_redirect('/admin/news');

на $this-_forward('index', 'news', 'admin');

avatar

iexx

0

543 дня назад

А есть у кого-нить ещё эта же проблема?

Я пересмотрел у себя - у меня все ок! =)

Может вставили где-нибудь перенос строки в начале файла например?

Novarg

0

542 дня назад

Ошибка возникает даже при использовании набора ваших исходников (ссылка вначале статьи)

Может потому что я использую ЗФ собранный из свн репозитория, хотя врятли конечно, просто незнаю уже на что и думать.

Да и кстати спасибо огромное за статью, она позволила разобраться во многих вещах. Сразу начинать с офф мануала тяжеловато :)

avatar

iexx

0

541 день назад

Ну попробуйте скачать релизную версию ZF и перезалить файлы. Я ещё раз просмотрел всё. Взял набор исходников из архива, долил туда ZF 1.7.1 - и никаких проблем нет.

ers

0

529 дней назад

Очень хорошая статья, только как сделать чтобы сайт запускался не прямо из виртуального хоста, а скажем из папочки? Я так понимаю нужно везде прописать baseurl ? но после этого логин и регистрация не работают.

avatar

iexx

0

529 дней назад

К сожалению пути жёстко прописаны, поэтому в шаблонах и JS скриптах надо править URL.

Поправь пути в /js/main.js

Gorik

0

292 дня назад

Спасибо интересно!

shrize

0

249 дней назад

спасибо. очень пояснительно

Макс

0

249 дней назад

Интересная статья но пос\чему то не заносит русский текст в новости (( в чем может быть дело ?

avatar

iexx

0

248 дней назад

Может БД в неверной кодировке у тебя?

volodia

0

133 дня назад

cool!

...

Cap image