framework.zend.com
Stable релиз 2.0 / 1.12

Построение приложения на базе Zend Framework. Часть 2. Zend_Form, Zend_Validate, Zend_Filter, Zend_Captcha, Zend_Translate

К комментариям

Содержание

Введение

Данная статья – это вторая часть туториала «Построение приложения на базе Zend Framework». С первой частью вы можете ознакомиться по ссылке. В первой статье мы создали простой сайт, функциональность которого ограничивалась отображением данных, полученных из базы. Такой функциональности может хватить для простого сайта визитки, состоящего из нескольких информационных страниц. Но, если мы хотим, чтобы пользователи не только читали информацию на нашем сайте, но и сами наполняли сайт, более активно участвовали в его жизни, тогда нам не обойтись без html форм. Zend Framework приходит на помощь, предлагая компонент Zend_Form – мощное и гибкое средство по генерации и обработке html форм. Вокруг него и будет строиться наше повествование.

Если вы работаете с Zend Framework 1.8.0 и старше прочтите это.

В этой статье рассматриваются следующие вопросы:

  • Работа с формами с помощью Zend_Form, включая работу с элементами формы, валидаторами, фильтрами, декораторами и визуальными группами;
  • Создание элементов формы на примере электронной почты;
  • Создание валидаторов на примере валидатора пароля, валидатора совпадения двух строк и валидатора проверки отсутствия записи в базе;
  • Создание фильтров на примере фильтра, переводящего строку в транслит;
  • Создание декораторов на примере декоратора, интегрирующего javascript-календарь для выбора даты;
  • Использования компонента Zend_Captcha и элемента формы Zend_Form_Element_Captcha для защиты от спама;
  • Перевод ошибок формы с помощью Zend_Translate;
  • Вставка информации в базу данных;
  • Вывод сообщений с использованием помощника FlashMessenger.

Код приложения, разрабатываемого в этой статье, базируется на коде из предыдущей части туториала. Для лучшего понимания статьи желательно сразу скачать код и просматривать его по ходу чтения материала. Код написан и протестирован для Zend Framework 1.7.1, но, скорее всего, будет работать как на предыдущих (>= 1.5), так и на последующих версиях. Работу с формами мы рассмотрим на примере формы регистрации.

К содержанию

Доработка архитектуры

Ввиду того, что приложение становится более сложным, мы выполним небольшой рефакторинг архитектуры и кода из первой части. Изменим структуру директорий. В каталоге library добавим папку App. Эта папка будет содержать нашу библиотеку, которая будет «надстройкой» над Zend Framework. В нашу библиотеку будет попадать код достаточно общий и независимый от самого приложения, которое находится в папке application. Это будут классы наследники от классов фреймворка, самописные валидаторы, фильтры, элементы форм, декораторы и т д. То есть, все то, что может быть достаточно легко переносимо между различными приложениями.

Классы в нашей библиотеке будут именоваться согласно стандартам Zend Framework, например имя класса App_Form_Element_Email будет соответствовать пути App/Form/Element/Email.php. Приставка App необходима для того, чтобы ваши классы не конфликтовали с классами фреймворка. Если же вы хотите в дальнейшем распространять свою библиотеку среди других разработчиков то приставка должна быть уникальной, например, это может быть ваш никнейм.

Изменится также структура папки models. Если в первой части в качестве модели у нас выступали только классы работы с базой, то теперь модель расширяется, становится более «толстой». В папке models мы добавим две новые папки – DbTable и Form, в которых будут размещаться классы, работающие с таблицами базы данных и наши формы, соответственно. Названиями этих папок мы четко определяем суть классов, которые лежат в них.

Здесь у вас могут возникнуть сомнения, ведь на первый взгляд формы это часть вида, а не модели. Это не совсем так, Zend_Form агрегирует элементы, метаданные, декораторы. Каждый элемент в свою очередь агрегирует метаданные, цепочки валидаторов и фильтров и декораторы. Декораторы действительно отвечают за представление формы, но ведь вы можете и не отображать форму вовсе. С интересным подходом интеграции форм и модели вы можете ознакомится в статье.

Папку application/system мы удалим, класс Error перенесем в library/App/Error.php, он получит новое имя App_Error. Bootstrap.php перемещается на уровень выше в application/Bootstrap.php, а файл с маршрутами перемещается в папку settings, так как по сути своей файл маршрутов является конфигурационным. В итоге, мы получим следующую структуру директорий:

Структура директорий Zend Framework

Теперь о том, что касается непосредственно регистрации. Нам необходим новый маршрут для страницы регистрации и новый контроллер AuthController, который будет содержать в себе действия, относящиеся к идентификации пользователя в системе. Регистрация относится к таким действиям, поэтому мы создадим пустое действие registerAction и соответствующий, пустой скрипт вида. В процессе статьи мы наполним действие и скрипт вида кодом.

Кроме этого, для работы с новыми функциями нам необходимо добавить следующие компоненты фреймворка в папку library/Zend - Zend_Form, Zend_Translate, Zend_Captcha, Zend_Session(необходим для Zend_Captcha и FlashMessenger), Zend_Validate, Zend_Date.

К содержанию

Основные принципы работы с Zend_Form

Хочу отметить, что компонент Zend_Form достаточно хорошо описан в официальном мануале на английском и, частично, на русском. Цель этого руководства – не повторить то, что уже написано в мануале, а показать пример использования Zend_Form на реальном приложении.

Компонент Zend_Form предоставляет нам полный набор функционала по работе с формами, при этом он избавляет нас от массы рутинной работы. Zend_Form это:

  • Генерация html кода форм;
  • Группирование и конфигурирование элементов и форм;
  • Фильтрация и проверка корректности данных, введенных в форму;
  • Встроенный механиз вывода сообщений об ошибках.

Компонент Zend_Form тесно связан с компонентами Zend_Validate, Zend_Filter, Zend_Config. Zend_Validate предоставляет валидаторы для использования в формах, Zend_Filter дает фильтры, а Zend_Config возможность задавать формы в текстовых конфигурационных файлах.

Основные понятия, напрямую связанные с Zend_Form, это:

  • Элементы формы (Zend_Form_Element_*)
  • Декораторы (Zend_Form_Decorator_*)
  • Визуальные группы (Display Groups)
  • Вложенные формы (Sub Forms)

Элементы Zend_Form отвечают реальным элементам html форм. Декораторы дают возможность настраивать внешний вид форм, то есть отвечают за html код. Визуальные группы позволяют объединять схожие по сути элементы, обычно, и по умолчанию, это происходит с помощью тега fieldset. Вложенные формы позволяют объединять элементы логически, это, в свою очередь, дает нам возможность создавать многостраничные формы, то есть такие формы, которые заполняются поэтапно на нескольких страницах.

Благодаря Zend_Form, мы избавляемся от дублирования кода, вся информация о конкретной форме хранится вместе с формой, и таким образом, наши формы можно использовать по нескольку раз.

В нашем приложении каждой сущности будет отвечать своя форма, а за каждую форму будет отвечать свой файл, расположенный в папке application/models/Form/. Например, класс формы регистрации, который мы рассмотрим далее, будет расположен в application/models/Form/Register.php и будет иметь название Form_Register.

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

Рассмотрим код класса App_Form:

class App_Form extends Zend_Form 
{
/**
* Инициализация формы
*
* return void
*/
public function init()
{
// Вызов родительского метода
parent::init();

// Создаем объект переводчика, он нам необходим для перевода сообщений об ошибках.
// В качестве адаптера используется php массив
$translator = new Zend_Translate('array', Zend_Registry::get('config')->path->languages . 'errors.php');

// Задаем объект переводчика для формы
$this->setTranslator($translator);

/* Задаем префиксы для самописных элементов, валидаторов, фильтров и декораторов.
Благодаря этому Zend_Form будет знать где искать наши самописные элементы */
$this->addElementPrefixPath('App_Validate', 'App/Validate/', 'validate');
$this->addElementPrefixPath('App_Filter', 'App/Filter/', 'filter');
$this->addElementPrefixPath('App_Form_Decorator', 'App/Form/Decorator/', 'decorator');
}
}

Вся настройка формы будет происходить в методе init(), он выполняется при инициализации формы, в конце конструктора Zend_Form.

Общий алгоритм работы с формами выглядит следующим образом:

  1. Создание объекта формы;
  2. Настройка элементов формы;
  3. Вывод html кода формы;
  4. Заполнение данных формы и отправка на сервер;
  5. Проверка и фильтрация данных;
  6. В случае, если данные прошли проверку, происходит дальнейшая обработка данных, например, занесение их в базу. В обратном случае форма снова отображается вместе с сообщениями об ошибках. Следует отметить, что форма сохраняет введенные пользователем данные и, при их выводе, производит замену потенциально опасных символов на их коды.

Каждый из этих пунктов будет подробно рассмотрен далее.

Итак, мы создаем форму регистрации, опишем все параметры полей.

  • Электронная почта
  • Обязательное text поле;
  • Максимальная длина 80 символов;
  • Значение должно быть валидным адресом электронной почты;
  • Значение почты должно быть уникальным в базе данных.
  • Пароль
  • Обязательное password поле;
  • Максимальная длина 30 символов;
  • Значение должно быть валидным паролем.
  • Подтверждение пароля
  • Обязательное password поле;
  • Максимальная длина 30 символов;
  • Значение должно быть валидным паролем;
  • Значение поля должно совпадать с Паролем.
  • Имя
  • Обязательное text поле;
  • Максимальная длина 30 символов;
  • Значение должно состоять из алфавитных символов, цифр и пробелов.
  • Пол
  • Необязательное radio поле;
  • Содержит два значения, «Муж» и «Жен»
  • Дата рождения
  • Необязательное text поле;
  • Значение должно быть валидной датой в формате ДД.ММ.ГГГГ;
  • Для выбора даты используется javascript календарь от dynarch.com.
  • Возраст
  • Необязательное select поле.
  • О себе
  • Необязательное textarea поле.
  • CAPTCHA элемент
  • Обязательное text поле, использующееся для защиты от спама.
  • Согласие с правилами
  • Обязательное checkbox поле.
  • Кнопка «Зарегистрировать»
  • Кнопка типа submit для отправки формы.
  • Кнопка «Очистить»
  • Кнопка типа reset для очистки формы.

У нас получилась достаточно реальная форма регистрации, содержащая в себе практически все основные элементы форм.

Далее я привожу полный код формы с подробными комментариями.

class Form_Register extends App_Form
{
/**
* Создание формы
*/
public function init()
{
// Вызываем родительский метод
parent::init();

// Указываем action формы
$helperUrl = new Zend_View_Helper_Url();
$this->setAction($helperUrl->url(array(), 'auth_register'));

// Указываем метод формы
$this->setMethod('post');

// Задаем атрибут class для формы
$this->setAttrib('class', 'register');

// Используемый собственный элемент App_Form_Element_Email
$email = new App_Form_Element_Email('email', array(
'required' => true,
));

// Добавление элемента в форму
$this->addElement($email);

// Password элемент "Пароль". Значение проверяется валидатором App_Validate_Password
$password = new Zend_Form_Element_Password('password', array(
'required' => true,
'label' => 'Пароль:',
'maxlength' => '30',
'validators' => array('Password'),
));

$this->addElement($password);

// Элемент "Подтверждение пароля".
// Проверяется на совпадение с полем "Пароль" валидатором App_Validate_EqualInputs
$passwordApprove = new Zend_Form_Element_Password('password_approve', array(
'required' => true,
'label' => 'Подтвердите пароль:',
'maxlength' => '30',
'validators' => array(array('EqualInputs', true, array('password'))),
));

$this->addElement($passwordApprove);

// Text элемент "Имя". Проверяется на алфавитные символы и цифры, а также на длину
// Валидатор Alnum использует установленную локаль для определения алфавита символов.
$name = new Zend_Form_Element_Text('name', array(
'required' => true,
'label' => 'Имя:',
'maxlength' => '30',
'validators' => array(
array('Alnum', true, array(true)),
array('StringLength', true, array(0, 30))
),
'filters' => array('StringTrim'),
));

$this->addElement($name);

$name->setName('name');
// Radio элемент "Пол".
$sex = new Zend_Form_Element_Radio('sex', array(
'label' => 'Пол:',
'multiOptions'=> array('m' => 'Муж', 'f' => 'Жен'),
'validators' => array(array('InArray', true, array(array('m', 'f'), true)))
));

// Задаем разделитель пробел, для того что бы радио кнопки располагались в ряд
$sex->setSeparator(' ');

$this->addElement($sex);

// Элемент "Дата рождения". Элемент содержит нестандартный декоратор - javascript календарь
$dateBirth = new Zend_Form_Element_Text('date_birth', array(
'label' => 'Дата рождения:',
'maxlength' => '10',
'validators' => array(array('Date', true, array('dd.MM.yyyy'))),
'filters' => array('StringTrim'),
));

// Удаляем все существующие декораторы, назначенные по умолчанию
$dateBirth->clearDecorators();

// Назначаем новые, включая наш декоратор Calendar
// Это необходимо для того что бы изображение календаря размещалось сразу за полем ввода
$dateBirth
->addDecorator('ViewHelper')
->addDecorator('Calendar')
->addDecorator('Errors')
->addDecorator('HtmlTag', array('tag' => 'dd'))
->addDecorator('Label', array('tag' => 'dt'));

$this->addElement($dateBirth);

// Select элемент "Возраст".
$age = new Zend_Form_Element_Select('age', array(
'label' => 'Возраст:',
'multiOptions'=> array('', '11 - 20', '21 - 30', '31 - 40'),
'filters' => array('Int'),
));

$this->addElement($age);

// Textarea элемент "О себе"
$about = new Zend_Form_Element_Textarea('about', array(
'label' => 'О себе:',
'rows' => '5',
'cols' => '45',
'validators' => array(
array('StringLength', true, array(0, 5000))
),
'filters' => array('StringTrim'),
));

$this->addElement($about);

// Элемент CAPTCHA, защита от спама
$captcha = new Zend_Form_Element_Captcha('captcha', array(
'label' => "Введите символы:",
'captcha' => array(
'captcha' => 'Image', // Тип CAPTCHA
'wordLen' => 4, // Количество генерируемых символов
'width' => 260, // Ширина изображения
'timeout' => 120, // Время жизни сессии хранящей символы
'expiration'=> 300, // Время жизни изображения в файловой системе
'font' => Zend_Registry::get('config')->path->rootPublic . 'fonts/arial.ttf', // Путь к шрифту
'imgDir' => Zend_Registry::get('config')->path->rootPublic . 'images/captcha/', // Путь к изобр.
'imgUrl' => '/images/captcha/', // Адрес папки с изображениями
'gcFreq' => 5 // Частота вызова сборщика мусора
),
));

$this->addElement($captcha);

// Переопределяем сообщение об ошибке для валидатора NotEmpty
$validatorNotEmpty = new Zend_Validate_NotEmpty();
$validatorNotEmpty->setMessages(array(
Zend_Validate_NotEmpty::IS_EMPTY => 'agreeRules'));

// Checkbox элемент "Согласен с правилами".
$agreeRules = new Zend_Form_Element_Checkbox('agreeRules', array(
'required' => true,
'label' => 'Обещаю следовать правилам и любить отечество:',
'filters' => array('Int'),
'validators' => array($validatorNotEmpty),
));

$this->addElement($agreeRules);

// Кнопка Submit
$submit = new Zend_Form_Element_Submit('submit', array(
'label' => 'Зарегистрироваться',
));

$submit->setDecorators(array('ViewHelper'));

$this->addElement($submit);

// Кнопка Reset, возвращает форму в начальное состояние
$reset = new Zend_Form_Element_Reset('reset', array(
'label' => 'Очистить',
));

// Перезаписываем декораторы, что-бы выставить две кнопки в ряд
$reset->setDecorators(array('ViewHelper'));
$this->addElement($reset);

// Группируем элементы

// Группа полей связанных с авторизационными данными
$this->addDisplayGroup(
array('email', 'password', 'password_approve'), 'authDataGroup',
array(
'legend' => 'Авторизационные данные'
)
);

// Группа полей связанных с личной информацией
$this->addDisplayGroup(
array('name', 'last_name', 'sex', 'date_birth', 'age', 'about'), 'privateDataGroup',
array(
'legend' => 'Личная информация'
)
);

// Защита от спама
$this->addDisplayGroup(
array('captcha'), 'captchaGroup',
array(
'legend' => 'Будьте человеком!'
)
);

// Группа полей кнопок
$this->addDisplayGroup(
array('agreeRules', 'submit', 'reset'), 'buttonsGroup'
);

}
}

К содержанию

Работа с элементами формы (Zend_Form_Element_*)

Zend_Form содержит все элементы, соответствующие реальным элементам html форм. Например, Zend_Form_Element_Text соответствует элементу <input type="text">. Сейчас Zend_Form поставляется вместе со следующими элементами: Button, Captcha, Checkbox, File, Hash, Hidden, Image, MultiCheckbox, Multiselect, Password, Radio, Reset, Select, Submit, Text, Textarea. Большая часть из них имеет html аналоги.

Элемент Hash позволяет защититься от CSRF (Cross-Site Request Forgery) атак. Данный тип атак направлен на имитирование запроса пользователя к стороннему сайту, другими словами, данные на ваш сайт пытаются передать не через форму на вашем сайте, а с помощью стороннего скрипта. Элемент Hash гарантирует, что запрос на вывод формы и запрос обработки данных формы будут использовать одинаковую пользовательскую сессию. Например, если заполнение какой-либо формы защищено авторизацией, злоумышленник не сможет отослать данные с помощью стороннего скрипта, не пройдя предварительно авторизацию.

Работа с элементами обычно происходит следующим образом:

1. Инициализация элемента. При этом нужно, как минимум, указать имя элемента.

$name = new Zend_Form_Element_Text("name");

2. Настройка элемента. Обычно при этом для элемента настраиваются: метка, значение, обязателен элемент или нет, валидаторы, фильтры, декораторы, атрибуты, специфические настройки элемента. Например, для элемента типа text настройка может выглядеть следующим образом:

// Метка
$name->setLabel('Имя:');
// Элемент обязателен
$name->setRequired(true);
// Значение элемента
$name->setValue('Инокентий');
// Валидатор Alnum - проверка на недопустимые символы,
$name->addValidator('Alnum', true, array(true));
// Валидатор StringLength - проверка длины,
$name->addValidator('StringLength', true, array(0, 30));
// Фильтр обрезающий пробелы по краям значения
$name->addFilter('StringTrim');
// Аттрибут maxlength со значением 30
$name->setAttrib('maxlength', 30);

Хочу отметить разницу между методами set+Параметр и add+Параметр. Рассмотрим, например, метку элемента. У текстового элемента не может быть несколько меток, поэтому Zend_Form предлагает единственный метод, устанавливающий это значение setLabel, при этом предыдущее значение (если оно было) будет затерто. Но есть параметры, которых может быть несколько. Например, валидаторы, одно значение можно проверять несколькими валидаторами, поэтому мы используем метод addValidator, при этом каждое значение добавляется к предыдущему, так образуется цепочка. Также существуют методы: addValidators – позволяет добавить сразу набор валидаторов, и метод setValidators который добавляет сразу набор валидаторов, но в отличии от addValidators удаляет все валидаторы что были заданы до этого. Подобная схема работает и в других подобных случаях (фильтры, декораторы).

3. Добавление. После настройки элемента его необходимо добавить в нашу форму:

$this->addElement($name);

Кроме описанного выше подхода Zend_Form предоставляет возможность задать параметры элемента сразу при создании объекта элемента, передав параметры в конструктор. Например, описанный выше текстовый элемент можно создать следующим образом:

$name = new Zend_Form_Element_Text('name', array(
'required' => true,
'label' => 'Имя:',
'value' => 'Инокентий',
'maxlength' => '30',
'validators' => array(
array('Alnum', true, array(true)),
array('StringLength', true, array(0, 30))
),
'filters' => array('StringTrim'),
));

$this->addElement($name);

Принцип обработки массива довольно простой, Zend_Form пробегает по циклу параметров, проверяет существование метода (set+Параметр), задающего определенный параметр, например, по ключу required проверяется метод setRequired; если метод существует, он вызывается, если же нет, то применяется метод setAttrib, задающий атрибут.

Альтернативным способом задания элементов является использование конфигурационных файлов и Zend_Config

Если вы используете один и тот же элемент в нескольких формах, разумно будет создать свой собственный элемент, это избавит вас от дублирования при настройке элемента. В нашем приложении мы создали собственный элемент – электронную почту:

class App_Form_Element_Email extends Zend_Form_Element_Text 
{
public function init()
{
$this->setLabel('Электронная почта:');
$this->setAttrib('maxlength', 80);
$this->addValidator('EmailAddress', true);
$this->addValidator('NoDbRecordExists', true, array('users', 'email'));
$this->addFilter('StringTrim');
}
}

Так как электронная почта – это текстовое поле, то элемент является наследником Zend_Form_Element_Text. Далее мы сможем многократно использовать этот элемент в любых формах.

Необходимо также отметить, что Zend_Form генерирует верстку элементов формы в соответствии с заданным DocType объекта view, который использует Zend_Form.

К содержанию

Zend_Form и Zend_Validate, создание валидатора

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

Валидаторы можно использовать независимо, но чаще всего они используются в связке с Zend_Form. Следующий пример демонстрирует независимое использование валидаторов:

$validator = new Zend_Validate_EmailAddress();
if ($validator->isValid($email)) {
// email прошел валидацию
} else {
// email не прошел валидацию; вывод причин этого
foreach ($validator->getMessages() as $message) {
echo "$message\n";
}
}

Ключевой метод isValid непосредственно производит валидацию и возвращает результат, булевое значение.

Но, как я уже писал выше, гораздо удобнее использовать валидаторы совместно с Zend_Form. Валидаторы добавляются для элементов формы. Существует несколько методов для этого - addValidator(s) и setValidators, различие между этими методами я уже описывал выше.

С помощью нескольких валидаторов мы можем строить цепочки валидаторов, это удобно, когда мы хотим проверить значение сразу несколькими валидаторами. Объявление метода addValidator выглядит следующим образом:

addValidator($validator, $breakChainOnFailure = false, $options = array())

Параметр breakChainOnFailure позволяет прекратить проход по всей цепочке валидаторов, если один из валидаторов выдал отрицательный результат.

Например, в следующем коде мы строим цепочку из двух валидаторов, при этом, если первый валидатор определяет значение как не корректное, второй валидатор уже не будет выполнять проверку:

$name->addValidator('Alnum', true, array(true));
$name->addValidator('StringLength', true, array(0, 30));

Достаточно много задач можно решить с помощью стандартных валидаторов, но иногда приходится писать свои. В нашем приложении возникла необходимость в трех валидаторах, аналогов которых нет в стандартной поставке Zend_Validate. Рассмотрим создание валидаторов на примере валидатора пароля. В целом этот валидатор можно было бы заменить на стандартный Zend_Validate_Regex, но создание собственного валидатора позволит более гибко управлять им в будущем, ведь требования к паролю могут меняться.

Предположим, пароль может содержать любые символы из следующего набора: [email protected]#$%^&*()-_+=\/{}[].,?<>:;a-z0-9 и должен быть длинной от 6ти до 30ти символов.

Код валидатора пароля:

class App_Validate_Password extends Zend_Validate_Abstract 
{
/**
* Метка ошибки
* @var const
*/
const INVALID = 'passwordInvalid';

/**
* Метка ошибки
* @var const
*/
const INVALID_LENGTH = 'passwordBadLength';

/**
* Текст ошибки
* @var array
*/
protected $_messageTemplates = array(
self::INVALID => 'Value does not appear to be a valid password',
self::INVALID_LENGTH => 'Value should have more than 6 and less then 30 symbols'
);

/**
* Проверка пароля
*
* @param string $value значение которое поддается валидации
*/
public function isValid($value)
{
// Благодаря этому методу значение будет автоматически подставлено в текст ошибки при необходимости
$this->_setValue($value);

// Валидатор проверки длины
$validatorStringLength = new Zend_Validate_StringLength(7, 30);

// Проверка на допустимые символы
if (!preg_match("/^[[email protected]#\\$%\\^&\\*\\(\\)\\-_\\+=\\\\\/\\{\\}\\[\\].,\\?<>:;a-z0-9]*$/i", $value)) {
// С помощью этого метода мы указываем какая именно ошибка произошла
$this->_error(self::INVALID);
return false;
}
elseif (!$validatorStringLength->isValid($value)) {
$this->_error(self::INVALID_LENGTH);
return false;
}

return true;
}
}

Итак, во первых, все наши валидаторы будут наследниками класса Zend_Validate_Abstract. Во вторых, минимальное, что нам нужно сделать, для того, чтобы наш валидатор был достаточно полноценным, это определить метод isValid и задать сообщения об ошибках. В этом примере мы имеем только два типа ошибок, поэтому мы создаем две константы с метками ошибок. Констант должно быть столько, сколько различных ошибок может иметь проверяемое значение.

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

И последний, созданный в статье, валидатор позволяет проверять отсутствие некоторой записи в таблице базы данных. Например, при регистрации мы должны быть уверены, что электронная почта, которую мы указываем, уникальна. Валидатор имеет имя App_Validate_NoDbRecordExists

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

К содержанию

Zend_Form и Zend_Filter, создание фильтра

Фильтры используются для преобразования данных к нужному виду в соответствии с определенными правилами. Фильтры, как и валидаторы, используются независимо или в связке с Zend_Form. Для настройки фильтров используются методы addFilter(s) и setFilters. В коде нашей формы мы используем стандартный фильтр StringTrim. Но нам также понадобится фильтр, который отсутствует среди набора стандартных фильтров. Это фильтр, который переводит кириллический текст в транслит.

class App_Filter_Translit implements Zend_Filter_Interface 
{

/**
* Производит фильтрацию в соответствии с назначением фильтра
*
* @param string $value
* @return string
*/
public function filter($value)
{

// Массив символов
$letters = array(
"а" => "a", "б" => "b", "в" => "v", "г" => "g", "д" => "d", "е" => "e",
"ё" => "e", "ж" => "zh", "з" => "z", "и" => "i", "й" => "j", "к" => "k",
"л" => "l", "м" => "m", "н" => "n", "о" => "o", "п" => "p", "р" => "r",
"с" => "s", "т" => "t", "у" => "u", "ф" => "f", "х" => "h", "ц" => "c",
"ч" => "ch", "ш" => "sh", "щ" => "sh", "ы" => "i", "ь" => "", "ъ" => "",
"э" => "e", "ю" => "yu", "я" => "ya",
"А" => "A", "Б" => "B", "В" => "V", "Г" => "G", "Д" => "D", "Е" => "E",
"Ё" => "E", "Ж" => "ZH", "З" => "Z", "И" => "I", "Й" => "J", "К" => "K",
"Л" => "L", "М" => "M", "Н" => "N", "О" => "O", "П" => "P", "Р" => "R",
"С" => "S", "Т" => "T", "У" => "U", "Ф" => "F", "Х" => "H", "Ц" => "C",
"Ч" => "CH", "Ш" => "SH", "Щ" => "SH", "Ы" => "I", "Ь" => "", "Ъ" => "",
"Э" => "E", "Ю" => "YU", "Я" => "YA",

);

// Проходим по массиву и заменяем каждый символ фильтруемого значения
foreach($letters as $letterVal => $letterKey) {
$value = str_replace($letterVal, $letterKey, $value);
}

return $value;
}
}

Минимальное, что должен сделать наш фильтр – это реализовать интерфейс Zend_Filter_Interface и определить метод filter.

К содержанию

Декораторы Zend_Form, создание декоратора

Достаточно много затруднений при работе с Zend_Form у разработчиков вызывают декораторы. На самом деле, совершенно не обязательно выводить форму (вы можете не использовать метод render()). Но, если же вы решите это сделать, то декораторы будут использоваться для формирования html кода различных составляющих формы.

Если выражаться по-простому, то декораторы отвечают за html код формы и ее составляющих. От декораторов зависит, в каком теге будет выведена метка поля, какие теги будут обрамлять элементы и метки и т д.

Произвольное число декораторов может быть применено к любой составляющей формы (элементы, визуальные группы, вложенные формы или сам объект формы). Декораторы вызываются в том же порядке, в котором они регистрируются. Декоратор может заменять содержимое, переданное ему или добавлять что-либо после или перед содержимым.

Я не буду подробно останавливаться на декораторах, так как, помимо мануала на официальном сайте, существует перевод замечательной статьи Matthew Weier O'Phinney о декораторах.

Регистрация декораторов происходит с помощью методов addDecorator(s) и setDecorators. По умолчанию для различных составляющих формы регистрируется набор стандартных декораторов. Если мы хотим этого избежать, при создании элемента необходимо указать параметр disableLoadDefaultDecorators.

Например, загрузка декораторов по умолчанию для элементов формы выглядит следующим образом:

$this->addDecorator('ViewHelper')
->addDecorator('Errors')
->addDecorator('Description', array('tag' => 'p', 'class' => 'description'))
->addDecorator('HtmlTag', array('tag' => 'dd'))
->addDecorator('Label', array('tag' => 'dt'));

Эти декораторы используются чаще всего. ViewHelper формирует непосредственно html код элемента (например <input type="text">). Errors добавляет html код в котором, будут выводиться ошибки формы. Description позволяет задавать описание элемента, HtmlTag обрамляет содержимое указанным тегом, в данном случае dd. И, наконец, декоратор Label обрамляет метку элемента указанным тегом, в данном случае dt.

Манипулируя декораторами, можно добиться практически любого html кода формы. Для сброса назначенных декораторов используется метод clearDecorators(). Итак, предположим, что стандартный набор декораторов какого-либо элемента нас не удовлетворяет. В таком случае мы сбрасываем стандартный набор и прописываем свои. Рассмотрим это на примере.

В форме регистрации нам необходимо, чтобы дату рождения можно было выбирать с помощью javascript календаря. Но для работы с javascript календарем нам необходимо подключить его и настроить. Для этого мы создадим свой декоратор App_Form_Decorator_Calendar, его код мы разместим в файле library/App/Form/Decorator/Calendar:

class App_Form_Decorator_Calendar extends Zend_Form_Decorator_Abstract
{
/**
* Получение строк подключения Javacript и CSS для календаря
* Статическая переменная $jsAndCss отвечает за то, чтобу подключение
* осуществлялось только один раз
*
* @return string
*/
private function _getJsAndCss()
{
static $jsAndCss = null;

if($jsAndCss === null) {

$jsAndCss = '
<style type="text/css">@import url(/js/calendar/skins/aqua/theme.css);</style>
<script type="text/javascript" src="/js/calendar/calendar.js"></script>
<script type="text/javascript" src="/js/calendar/lang/calendar-ru.js"></script>
<script type="text/javascript" src="/js/calendar/calendar-setup.js"></script>
';

return $jsAndCss;
}
return '';
}


/**
* Получение кода ссылки и изображения каледаря. Настройка календаря
*
* @return string
*/
private function _getCalendarLink()
{

$calendarLink = '
<a href="#" id="' . $this->getElement()->getName() . '_calendar">
<img class="calendar-image" src = "/js/calendar/calendar.gif">
</a>

<script type="text/javascript">
Calendar.setup(
{
inputField : "' . $this->getElement()->getName() . '",
ifFormat : "%d.%m.%Y",
button : "' . $this->getElement()->getName() . '_calendar",
firstDay : 1
}
);
</script>
';

return $calendarLink;
}


/**
* Рендеринг декоратора
*
* @param string $content
* @return string
*/
public function render($content)
{
// Получаем объект элемента к которому применяется декоратор
$element = $this->getElement();
if (!$element instanceof Zend_Form_Element) {
return $content;
}
// Проверяем объект вида зарегистрированного для формы
if (null === $element->getView()) {
return $content;
}

// Расположение декоратора, "после" или "перед" элементом, по умолчанию "после"
$placement = $this->getPlacement();
// Разделитель между элементом и декоратором
$separator = $this->getSeparator();

// Взависимости от настроек расположения декоратора возвращаем содержимое
switch ($placement) {
// После элемента
case 'APPEND':
return $this->_getJsAndCss() . $content . $separator . $this->_getCalendarLink();
// Перед элементом
case 'PREPEND':
return $this->_getJsAndCss() . $this->_getCalendarLink() . $separator . $content;
case null:
// По умолчанию просто возвращаем содержимое календаря
default:
return $this->_getJsAndCss() . $this->_getCalendarLink();
}

}
}

Наши декораторы будут наследниками класса Zend_Form_Decorator_Abstract и должны реализовать метод render(). Как видно из кода, javascript, отвечающий за загрузку, просто присоединяется к предыдущему содержимому, причем, в зависимости от настроек до или после содержимого.

Теперь нам необходимо «пересобрать» декораторы, потому что мы не можем просто добавить наш декоратор Calendar к стандартному набору. Нам необходимо, чтобы календарь был сразу после тега input, этого мы добиваемся следующим кодом:

$dateBirth->clearDecorators();
$dateBirth
->addDecorator('ViewHelper')
->addDecorator('Calendar')
->addDecorator('Errors')
->addDecorator('HtmlTag', array('tag' => 'dd'))
->addDecorator('Label', array('tag' => 'dt'));

К содержанию

Zend_Captcha и Zend_Form_Element_Captcha

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

Zend_Captcha предлагает реализацию технологии CAPTCHA с помощью следующих адаптеров:

  • Word
  • Dumb
  • Figlet
  • Image
  • ReCaptcha

Адаптер Word является абстрактным, от него наследуются все, кроме ReCaptcha. Адаптер Dumb генерирует строку, которая должна быть вписана в обратном порядке, Figlet генерирует строку с помощью псевдографики, Image - зашумленное изображение, а ReCaptcha использует Zend_Service_ReCaptcha. Подробнее с технологией ReCaptcha вы можете ознакомиться на http://recaptcha.net/

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

Например, код для работы с адаптером Figlet может выглядеть следующим образом:

// Создание объекта вида
$view = new Zend_View();

// Настройка параметров
$captcha = new Zend_Captcha_Figlet(array(
'name' => 'foo',
'wordLen' => 6,
'timeout' => 300,
));

// Генерация символов и вывод
$id = $captcha->generate();
echo $captcha->render($view);

Проверка данных введенных пользователем или программой:

if ($captcha->isValid($_POST['foo'], $_POST)) {
// Validated!
}

Мы будем использовать самый распространенный вариант CAPTCHA – генерацию зашумленного изображения с символами. Для этого нам понадобится три вещи: компонент Zend_Captcha, расширение GD, скомпилированное с поддержкой TrueType или Freetype и файл шрифта.

Zend_Captcha_Image генерирует PNG изображения, хранящиеся, на вашей файловой системе. Периодически при генерации запускается сборщик мусора и удаляет старые файлы изображений.

Есть несколько основных параметров, которые можно настраивать для нашего адаптера:

  • Timeout - Время жизни сессии, содержащей сгенерированную строку символов
  • WordLen – Количество генерируемых символов
  • Expiration – Время жизни файла изображения, хранящегося в файловой системе
  • GcFreq – Параметр, отвечающий за частоту запуска сборщика мусора. Сборщик будет запускаться каждый 1/$gcFreq вызов. По умолчанию GcFreq = 100. Очевидно, что если выставить этот параметр в 1, то сборщик будет запускаться при каждой новой генерации изображения.
  • Font – Путь к файлу шрифта
  • ImgDir – Физический путь к папке, где хранятся изображения
  • ImgUrl – URL папки, где хранятся изображения

Есть еще параметры FontSize, Height, Width, отвечающие за размер шрифта, высоту и ширину изображения соответственно. Каждый из этих параметров можно либо передать в виде опции в конструктор (как показано на примере адаптера Figlet выше), либо задать методом с названием set+Имяпараметра, например setWordLen().

Чаще всего CAPTCHA используется при заполнении форм. Zend Framework уже позаботился о нас и предлагает использовать Zend_Form_Element_Captcha, который всю черную работу по генерации, выводу и проверке он сделает сам, нам же останется только настроить его:

$captcha = new Zend_Form_Element_Captcha('captcha', array(
'label' => "Введите символы:",
'captcha' => array(
'captcha' => 'Image', // Тип CAPTCHA
'wordLen' => 4,
'width' => 260,
'timeout' => 120,
'expiration'=> 300,
'font' => Zend_Registry::get('config')->path->rootPublic . 'fonts/arial.ttf',
'imgDir' => Zend_Registry::get('config')->path->rootPublic . 'images/captcha/',
'imgUrl' => '/images/captcha/',
'gcFreq' => 5
),
));

Полученное изображение будет выглядеть примерно следующим образом:

Для CAPTCHA мы выделим отдельную визуальную группу.

К содержанию

Визуальные группы

Zend_Form предоставляет возможность группировать элементы форм в визуальные группы. Как я уже писал выше, обычно это объединение происходит с помощью тега fieldset. Например, следующим кодом мы создаем группу «Авторизационные данные»:

$this->addDisplayGroup(
array('email', 'password', 'password_approve'), 'authDataGroup',
array(
'legend' => 'Авторизационные данные'
)
);

Здесь первый массив содержит набор элементов группы, далее идет имя группы и настройки. Название группы попадает в тег legend по умолчанию. Элементы группы будут выведены в том порядке, в котором они заданы.

К содержанию

Перевод сообщений об ошибках

Стандартные, поставляемые с Zend_Validate, валидаторы, содержат сообщения об ошибках на английском языке. Резонно, что мы хотим, чтобы наши ошибки отображались на русском. Кроме этого, мы можем захотеть изменить текст некоторых ошибок, чтобы он был приближен к нашему приложению. Для всего этого нам понадобится компонент Zend_Translate. Zend_Translate имеет возможность работать с несколькими адаптерами. Адаптеры – это, грубо говоря, хранилища, в которых мы будем держать наши переводы.

Так как на текущем этапе мы еще не создаем полнофункционального многоязычного приложения, то нам вполне подойдет адаптер в виде php массива, ведь это еще и самый быстрый адаптер. Настройку переводчика мы проведем в файле library/App/Form.php в методе init() класса App_Form

Итак, создаем экземпляр Zend_Translate, указываем тип адаптера и путь к нему

$translator = new Zend_Translate('array', Zend_Registry::get('config')->path->languages . 'errors.php');

Выдержка из адаптера выглядит следующим образом:

Zend_Validate_Int::NOT_INT            => 'Значение не является целочисленным значением',   
Zend_Validate_NotEmpty::IS_EMPTY => 'Поле не может быть пустым',
Zend_Validate_StringLength::TOO_SHORT => 'Длина введённого значения меньше чем %min% символов',
Zend_Validate_StringLength::TOO_LONG => 'Длина введённого значения больше чем %max% символов',
App_Validate_EqualInputs::NOT_EQUAL => 'Пароли не совпадают',

Как можно заметить, ключи массива совпадают с константами ошибок, которые мы рассматривали в разделе работы с валидаторами. Именно за счет этого достигается перевод сообщений об ошибках.

Теперь нам останется только сообщить нашей форме о «переводчике»

$this->setTranslator($translator);

Как следует поступить, если для определенного элемента мы хотим задать особое сообщение. Например, у нас есть чекбокс, отвечающий за согласие с правилами. Если мы оставим все как есть, то в случае невыставления чекбокса, мы получим текст ошибки - «Поле не может быть пустым». Это не так красиво, как, например, текст «Регистрируясь, вы должны согласиться с правилами». Но ведь мы уже перевели константу:

Zend_Validate_NotEmpty::IS_EMPTY => 'Поле не может быть пустым',

Для переопределения этого значения нам нужен следующий код отвечающий за чекбокс:

        // Переопределяем сообщение об ошибке для валидатора NotEmpty
$validatorNotEmpty = new Zend_Validate_NotEmpty();
$validatorNotEmpty->setMessages(array(
Zend_Validate_NotEmpty::IS_EMPTY => 'agreeRules'));

// Checkbox элемент "Согласен с правилами".
$agreeRules = new Zend_Form_Element_Checkbox('agreeRules', array(
'required' => true,
'label' => 'Обещаю следовать правилам и любить отечество:',
'filters' => array('Int'),
'validators' => array($validatorNotEmpty),
));

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

К содержанию

Вывод формы в скрипте вида

Итак, мы создали форму, нам нужно вывести ее на экран. Теперь мы начнем наполнять наше пустое действие registerAction контроллера AuthController. Запишем туда следующий код:

// Инициализируем форму регистрации
$formRegister = new Form_Register();

// Передаем форму в скрипт вида
$this->view->formRegister = $formRegister;

Таким образом, объект формы станет доступным в скрипте вида application/views/scripts/auth/register.tpl. Нам остается лишь ее вывести:

<?php echo $this->formRegister ?>

Не правда ли, просто? Ведь все настройки собраны в нашем объекте App_Form_Register и нам не пришлось писать практически ни одной строчки html кода. Однако, такая простота имеет и обратную сторону. У нас возникает проблема настройки внешнего вида формы, точнее, эта проблема возникает у верстальщика. Ведь раньше он мог внести правки путем изменения только шаблона, а теперь он будет вынужден модифицировать декораторы или обращаться к программисту.

Возможным решением проблемы может быть отображение вашей формы поэлементно, для этого вам необходимо обращаться к элементам формы следующим образом:

<?php echo $this->form->element; ?>

Где element - это имя элемента формы. Таким образом, верстальщик сможет вставлять html код между элементами и менять их местами. Но, таким образом, вы все равно не сможете, например, в шаблоне(скрипте вида) указать css класс для определенного элемента формы. Еще одним, более "гибким" вариантом вывода формы, может быть использование помощников вида (FormLabel, FormText и другие). Можно пойти еще дальше и выводить элементы следующим образом:

<?php echo $this->formRegister->name->getLabel(); ?>
<input
type="text"
name="<?php echo $this->formRegister->name->getName(); ?>"
value="<?php echo $this->formRegister->name->getValue(); ?>"
maxlength="<?php echo $this->formRegister->name->getAttrib('maxlength'); ?>"
/>

Здесь я вывел элемент «Имя» из нашей формы регистрации. Таким образом, мы можем добиться полной настройки внешнего вида форм из шаблона. Правда, в таком случае вам еще прийдется самим позаботиться о выводе ошибок. Лично я рекомендую использовать подобную практику лишь в особых случаях, а, по возможности, сразу договариваться с верстальщиком и настраивать внешний вид формы через декораторы; это избавит вас от массы лишнего кода.

Также обратите внимание на декоратор ViewScript, он позволяет указывать для формы определенный скрипт вида.

Кроме того, в руках у верстальщика есть еще CSS. В следующем разделе вы убедитесь, что достаточно симпатичный внешний вид формы можно создать, используя лишь CSS и стандартные декораторы. Итак, повтороюсь: Zend_Form не навязывает обязательное использование декораторов, вы можете использовать свой вариант вывода формы, при этом, не теряя все остальные прелести Zend_Form. Поэтому не стоит отказываться от Zend_Form только потому, что вам не нравятся декораторы.

К содержанию

Настройка внешнего вида формы

Для настройки внешнего вида формы был использован следующий css код:

form.register dt, dd{padding:2px;}
form.register label {color:#3E4E68}
form.register label.required {font-weight:bold}
form.register dd{padding-bottom:10px;}
form.register fieldset{margin-left:-60px;width:450px;padding:10px;border:1px solid #A3B1C9}
form.register fieldset legend{color:#3E4E68;font-size:1.4em;}
form.register #captcha-input{margin-top:5px;}
form.register ul.errors {color:red}
form.register #fieldset-buttonsGroup{border:0px;}

В результате мы получили такую форму:

Форма сгенерированая с помощью Zend_Form

С еще одним примером оформления формы использующей стандартные декораторы вы можете найти по ссылке.

К содержанию

Обработка формы

Мы рассмотрели все, что необходимо для генерации формы. Как же происходит обработка данных после нажатия кнопки submit. Доработаем код действия registerAction контроллера AuthController:

public function registerAction()
{
// Инициализируем форму регистрации
$formRegister = new Form_Register();

// Проверяем типа запроса, если POST значит пришли данные формы
if ($this->_request->isPost()) {

// Проверяем на валидность поля формы
if ($formRegister->isValid($this->_getAllParams())) {

// Инициализируем объект отвечающий за таблицу пользователей
$tableUsers = new DbTable_Users();

// Формируем масив для вставки в базу данных
// Пароль преобразовываем в sha1 хеш добавляя "соль" для безопасности
$userData = array(
'email' => $formRegister->getValue('email'),
'password' => sha1($formRegister->getValue('password') . 'jdh37dgvs'),
'name' => $formRegister->getValue('name'),
'sex' => $formRegister->getValue('sex'),
'date_birth' => $formRegister->getValue('date_birth'),
'age' => $formRegister->getValue('age'),
'about' => $formRegister->getValue('about'),
);

// Вставляем данные в базу данных
$tableUsers->insert($userData);

// Задаем сообщение о успешной регистрации
$this->_helper->FlashMessenger->setNamespace('messages')->addMessage('Поздравляем с успешной рег-ей');
// Перенаправление на главную страницу
$this->_helper->redirector->gotoRoute(array(), 'default');

}
}

// Передаем форму в скрипт вида
$this->view->formRegister = $formRegister;
}

Алгоритм обработки достаточно прост: мы проверяем тип запроса страницы, если это POST, то вызываем функцию валидации формы isValid. Если данные не содержат ошибок, мы добавляем нового пользователя в базу данных, в противном случае отображаем форму и сообщения об ошибках.

Если данные были успешно добавлены в базу, логично сообщить об этом пользователю каким-нибудь красивым сообщением, вроде «Поздравляем с успешной регистрацией». Для этих целей в Zend Framework есть специальный помощник действий – FlashMessenger. Его мы и будем использовать. В приведенном выше коде вы можете видеть, как это происходит. Все наши сообщения об успешных результатах мы будем добавлять в пространство «messages».

После добавления сообщения мы выполняем перенаправление с помощью помощника действий redirector.

Таким образом, нам осталось отобразить сообщение на экран. Для этого мы создадим плагин для front контроллера. Подробнее этот процесс описан далее, а сейчас мы рассмотрим более подробно, как происходит добавление данных в базу.

К содержанию

Добавление данных в базу данных

Таблица пользователей, с которой мы работали в прошлой части туториала, расширилась, теперь она состоит из следующих полей:

  `id` int(11) NOT NULL AUTO_INCREMENT,
`email` varchar(80) NOT NULL,
`password` varchar(80) NOT NULL,
`name` varchar(30) NOT NULL,
`name_translit` varchar(50) NOT NULL,
`sex` enum('m','f') DEFAULT NULL,
`date_birth` date DEFAULT NULL,
`age` tinyint(4) DEFAULT NULL,
`about` text,

Все поля, кроме id и name_translit, имеют аналогичные поля в форме регистрации, причем имена полей и имена элементов формы совпадают. Это удобно при вставке или изменении данных в базе, а также для заполнения значений формы данными из базы с помощью функции populate() (обратите на нее внимание). Поле name_translit введено для демонстрации работы фильтра, переводящего строку в транслит. То есть, в этом поле хранится имя, написанное транслитом.

Для добавления данных в базу создадим новую модель DbTable_Users, она будет отвечать за работу с таблицей пользователей. Определим в ней метод insert:

    public function insert($userData) 
{
// Создаем объект фильтра
$filterTranslit = new App_Filter_Translit();

// Производим транслитерацию имени
$userData['name_translit'] = $filterTranslit->filter($userData['name']);

// Приводим дату к формату Mysql
if ($userData['date_birth'] != '') {
$dateBirth = new Zend_Date($userData['date_birth'], 'dd.MM.yyyy');
$userData['date_birth'] = $dateBirth->toString('yyyy-MM-dd');
}
else {
$userData['date_birth'] = null;
}

// Вызываем родительский метод вставки в базу данных
parent::insert($userData);

return true;
}

Как вы можете увидеть, в коде мы производим некоторые манипуляции перед вставкой в базу данных, это - транслитерации имени и перевод даты в формат, понятный для СУБД Mysql. Для этого мы используем компонент Zend_Date – мощный инструментарий для работы с датами.

Zend_Date позволяет производить форматированный вывод даты, в соответствии с временной зоной и локалью. Кроме этого, позволяет добавлять, отнимать и сравнивать даты, а также многое другое. Мы используем этот компонент для перевода даты из формата, пришедшего с формы, в формат, понятный для Mysql. Для этого мы сначала создаем объект Zend_Date, передавая ему, в качестве параметров, значение даты и формат в котором она содержится. Далее мы используем метод toString для создания строки в нужном формате.

К содержанию

FlashMessenger и создание плагина для front контроллера

После выполнения всех действий мы хотим отобразить сообщения об успешных результатах. Для этого нам необходимо получить эти сообщения и передать в объект вида для отображения. Мы не хотим делать это вручную в каждом действии, где это может понадобиться, поэтому мы создадим front контроллер плагин, который будет перехватывать событие postDispatch (Момент окончания этапа диспетчеризации):

class App_Controller_Plugin_FlashMessenger extends Zend_Controller_Plugin_Abstract
{
public function postDispatch(Zend_Controller_Request_Abstract $request)
{
// Инициализируем помощник FlashMessenger и получаем сообщения
$actionHelperFlashMessenger = new Zend_Controller_Action_Helper_FlashMessenger();
$messagesSuccess = $actionHelperFlashMessenger->setNamespace('messages')->getMessages();

// Если сообщений нет, или процес диспетчеризации не закончен успешно, просто выходим из плагина
if (empty($messagesSuccess) || !$request->isDispatched()) {
return;
}

// Получаем объект Zend_Layout
$layout = Zend_Layout::getMvcInstance();
// Получаем объект вида
$view = $layout->getView();
// Добавляем переменную для вида
$view->messages = $messagesSuccess;

// Устанавливаем объект вида с новыми переменным и производим рендеринг скрипта вида в сегмент messages
$viewRenderer = Zend_Controller_Action_HelperBroker::getStaticHelper('ViewRenderer');
$viewRenderer
->setView($view)
->renderScript('messages.tpl', 'messages')
;
}
}

Далее нам необходимо создать скрипт вида messages.tpl, в котором собственно и будет происходить вывод сообщений об ошибках. Мы расположим его в папке application/views/. Этот скрипт имеет следующий код:

<div class="success">
<?php foreach ($this->messages as $message) : ?>
<p> <?php echo $message; ?> </p>
<?php endforeach;?>
</div>

Так как мы произвели рендеринг скрипта вида в сегмент messages, то, добавив в главном макете layout.tpl следующий код:

<?php echo $this->layout()->messages; ?>

мы добьемся необходимого результата.

И, в завершение, нужно не забыть зарегистрировать наш плагин в front контроллере:

$front->registerPlugin(new App_Controller_Plugin_FlashMessenger())

К содержанию

Заключение

Скачать код с библиотеками Zend вы можете по ссылке (2.3 мб), без библиотек по ссылке (351 Кб)

Для установки скрипта вам будет необходимо выполнить действия, аналогичные к действиями в части 1. Посмотреть скрипт в работе можно по ссылке.

Выражаю благодарность Александру Стешенку за помощь при написании статьи.

В следующей части серии статей основной упор планируется сделать на идентификацию и аутентификацию с помощью компонент Zend_Auth, Zend_Acl, Zend_Session.

P.S. Вышеизложенная информация это не свод инструкций, это в первую очередь информация для размышления, не забывайте что у вас своя голова на плечах.

P.P.S. Для обсуждения статьи я создал тему на форуме. Если вы хотите обсудить код, или выразить объемное мнение, пожалуйста пишите в эту тему. Механизм комментариев пока плохо приспособлен для объемного полноценного обсуждения с кодом.

Лучший способ следить за обновлениями сайта это подписаться на RSS
Если информация была полезной для вас, вы можете поддержать сайт.
Комментарии:
Sych 08.01.2009 10:40 #
Неплохая статья - фильтр можно упростить до одного вызова str_replace, из одного массива делаем два (что и на что) и подсовываем в функцию.
Ответить
san 08.01.2009 11:56 #
Спасибо Sych. Действительно можно упростить, правда в контексте тематики статьи это не столь существенно.
Ответить
dbykov 10.01.2009 21:38 #
Столько времени убил на декораторы, а использовать form-element; ? не сообразил. спасибо за подсказку.
Ответить
Александр Махомет 10.01.2009 23:02 #
Пожалуйста :)
Ответить
Алексей Качаев 10.01.2009 21:39 #
Спасибо за статью, очень полезно :)
Не подскажите, а как можно валидировать введенные данных ajax-запросом, т.е. без перегрузки странички?
Ответить
Александр Махомет 10.01.2009 23:04 #
И вам пожалуйста :)
Задайте лучше вопрос на форуме, там больше людей и комментарии пока плохо совместимы с кодом...
Ответить
lcf 12.01.2009 12:20 #
Отличная статья, вызывающий большое уважение труд.
Ответить
Александр Махомет 12.01.2009 16:23 #
спасибо :)
Ответить
AndreyZ 14.01.2009 20:27 #
А как сделать чтобы статья открывалась не по ID
а по урлу типа http://ilovezf/articles/budte-dovolni

тока осваиваю zend, чёт я не могу никак сам сделать
а задача такая есть =)
Ответить
Александр Махомет 14.01.2009 20:34 #
Также, как и без Zend. Вопросы не по статье - задавайте пожалуйста на форуме.
Ответить
rashad 23.07.2009 13:41 #
роутер Zend_Router
Ответить
Олег 20.01.2009 06:07 #
Спасибо за статью. Очень помогла. Раньше мне казалось более удобным обходиться без Zend_Form, т.е. делать формы руками.
Не подскажете, когда можно ждать третью статью? Очень хотелось бы почитать.
Ответить
BlackSilver 06.02.2009 09:26 #
Большое спасибо за статью. Надеюсь, она поможет в освоении фреймворка.
Ответить
ILYAS 14.02.2009 23:27 #
Спасибо, статья что надо! Она помогла мне на начальном этапе изучения ZF, а я в этом деле новичок.

Декоратор календаря тоже можно упростить. Вместо использования \"шаманской\" статической переменной, для однократного подключения файлов стилей и скриптов можно использовать соответствующие помошники вида (headLink и headScript) - получится элегантное решение в общем стиле фрэймворка =)
Ответить
Никита 21.02.2009 14:55 #
спасибо!) пора продолжать)
Ответить
Михаил 26.02.2009 10:08 #
Пара дополнений:
Если сайт ставить не на виртуальных хост, а в отдельную папку с изменением $base_url в конфиге, то рисунок каптчи и календарь не работают, т.к. ссылки на js и сss фалы сделаны от корня. Необходимо внести изменения к код:
apllication/models/Form/Register.php - при создании элемента каптчи в поле imgUrl:
'imgUrl' = Zend_Registry::get('config')-url-base .'/images/captcha/',

Аналогичные изменения нужно внести в декоратор-календарь librar/App/Form/Decorator/Calendar.php
Ответить
w1zard 08.03.2009 15:59 #
Статья действительно очень познавательная. Вышла она уже относительно давно, только сейчас её освоил :) Чрезвычайно много для себя подчерпнул, особенно в понимании для чего нужны плагины и как их использовать. Спасибо большое! :)

Михаил 26.02.2009 10:08
Тоже самое хотел написать, когда читал про календарь. Желательно это поправить в статье
Ответить
Di 16.03.2009 20:42 #
"Не правда ли, просто? Ведь все настройки собраны в нашем объекте App_Form_Register и нам не пришлось писать практически ни одной строчки html кода."

Эта строчка убила))) Какое просто?!) Ради несчастной формы было потрачено куча времени и еще большая куча, чтобы понять (вычитать в манах), как её реализовывать + валидация + перевод...
Честно, вот совсем не пахнет "rapid development".
Ответить
lcf 16.03.2009 21:50 #
Ну читать всегда придется. Вообще-то как задумывалось изначально пхп программисты тоже должны были читать сначала что-то а потом программировать. Но гибкость языка развратила умы многих и ту часть что про чтение многие опускают. Пожалуй, большинство. А на самом деле в чтении мануалов нет ничего плохого, поверьте.
Насчет всего остального... rapid development? Вы говорите о rapid development одной формы? Если такова ваша задача - фреймворк использовать, разумеется не нужно. Если вы говорите о каком-то другом девелопменте... то либо вы узко мыслите, либо что-то не договорили.
Вообще компонент Zend_Form вызывает особо много споров, и, дабы не начинать холивары по пусту и доказывать друг другу что-то, зф программисты обычно просто дописывают в своих статьх - мол, ребята, компонентом этим пользоваться никто не обязывает. Вот и вы, если вы разработчик на зф, можете от использования этого компонента легко отказаться и работать с обычным представлением форм... или каким-то другим функционалом по отображению форм, который вам нравится. Это никак не повлияет на остальные компоненты фреймворка.
Лично от себя скажу (опять же, доказывать ничего не хочу никому, неблагородное это занятие, так что воспринимать как личное мнение) - я использую компонент Zend_Form и мне кажется это очень удобным. Да, это подойдет не всегда - если нужно хранить всю верстку в шаблонах для того чтобы с ними работал верстальщик (даже если верстальщик вы сам) или еще какие узкие места (хотя Zend_Form очень гибок и в этом смысле - позволяет задавать скрипт вида для отрисовки формы), однако и мест где это применимо очень много. Я например использую его особенно активно в различного рода административных интерфейсах, где имеет место быть большое количество однотипных форм и какой там html нам всё равно - важен лишь внешний вид и её управляющая функция формы (говорю это вроде как оправдываясь насчет внешнего вида, но на самом деле Zend_Form позволяет настраивать внешний вид формы абсолютно как угодно). В таких управляющих системах форма зачастую - один из основных элементов и перенос формы в плоскость объектно ориентированного программирования позволяет получить программисту все плюсы ООП в самом неожиданном месте - в html формах. Мне нет нужды писать по сто раз одно и тоже, я пользуюсь наследованием, устанавливаю в объектах формы различные аттрибуты. В итоге, вы не поверите, все это дело происходит очень быстро, места для ошибки остается мало и получаешь от всего процесса только положительные эмоции. Это и есть rapid development.
Если же форма в разрабатываемой системе это неважный момент несущий более декоративный характер, то я бы тоже не стал делать такую форму в виде объекта.
Ответить
Di 16.03.2009 23:33 #
Может вы и правы, и мои мозги еще слишком сильно процедурно-оринтированы) Честно говоря, с каждой страницей "Quick start" с оф. сайта Зенда моя челюсть падала всё ниже и ниже, потому как, ну хоть тресни, это совсем не QUICK start. Вообще говоря, челюсть всё еще в свободном полете, но осознание того, что это всё-таки не просто так, что однажды озарение посетит и меня, и я пойму, как прекрасен этот ООП и MVC мир с миллионом строчек кода и файлов, которые, конечно, обеспечивают мега-гибкость, но явно не мега-интуитивность работы и производительность... осознание всё-таки придет и я буду также легко фигачить проекты и с помощью ZF=)
Кстати, пользуясь случаем, спрошу, вот у меня пока тупо сайт со множеством иерархично расположенных контентных страниц, на которых местами подключаются модули новостей, например. Вы не видели статью, как это всё просто реализовать да еще и админкой?)
Нет, статью для меня еще одну не стоит писать =), но хотя бы можете дать курс исследования ZF, чтобы реализовать такой сайт?
Ответить
lcf 17.03.2009 10:25 #
Эмн.) Ну нужно разбить задачу на подзадачи во первых. И кажду задачу в отдельности решать. С помощью статей, форума. А так чтобы как-то конкретно сразу забабахать "тупо сайт" я хз :)
Ответить
Di 06.09.2010 21:21 #
Спустя более полутора лет снова вернулся за одной вещью к статье. Видя свои старые комменты, хочется просто сказать пару вещей.)

За это время я сделал не один проект на ZF, помогаю в разработке пары классов, которые, может быть, войдут в ZF 2.0, и должен сказать, это действительно быстрая разработка и очень удобный фреймворк. Правда rapid development начинается, когда полностью осваиваешь хотя бы QuickStart, а на это у меня ушел не один месяц, но оно того стоило.)
Ответить
Yar 25.04.2011 15:42 #
Дело не в процедурно-ориентированных мозгах. У меня была простая тестовая задачка. Есть список фирм-производителей, у каждого - список моделей автомобилей, у каждой модели - список подходящих покрышек. Сделать приложение для управления такой базой. Элементарно. Родительский класс \"таблица\" с методами лист, апдейт, эдд, дилит, дочерние классы - контроллеры с соответствующими данными полей и экшинами, соотв. методам родительского.
Идеально ложится в концепции ООП и MVC. Два часа работы вместе с симпатичной версткой. Если бы не одно \"НО\", - все это обязательно в ZF. В результате третьи сутки сижу над этой статьей и официальной документацией и не могу четко определить кто кому Рабинович - кто кого и откуда должен буттрапить, через ини конфигить и автолоадить, чтобы хотя-бы подгрузить мои классы.
Ответить
Dr0n 19.03.2009 18:56 #
Спасибо все двольно неплохо разжеванно.
зы.Очень бы хоелось видеть кнопочку "версия для печати". Ну или pdf файлик статьи., т.к. не очень удобно распечатывать в данном виде.
Ответить
al0xa 25.03.2009 10:57 #
У меня возникла след. проблема:

При создании обьекта Zend_Controller_Action_Helper_FlashMessenger в postDispatch он убивает данные добавленные ранее через addMessage().

Ответить
Andrey 27.03.2009 08:33 #
У меня выдает ошибку
Fatal error: Undefined class constant 'MYSQL_ATTR_INIT_COMMAND' in C:\wamp\www\application\settings\config.php on line 3
Ответить
Andrey 27.03.2009 09:39 #
Разобрался. Нужно было подключить PDO. В wamp для этого можно кликнуть по PHP settings - PHP Extensions
http://ru2.php.net/manual/ru/pdo.installation.php
Ответить
KoIIIeY 16.04.2009 12:25 #
Отличное описалово создания капчи :)
Спасибо.
Ответить
KoIIIeY 16.04.2009 12:28 #
И, кстати, если хорошо настроить роуты, то не придется заносить пути для капчи(на шрифт, папки для картинок) в регистр.
Ответить
lcf 16.04.2009 12:32 #
роуты (маршруты) это немножко из другой оперы.
Они не связаны никак с физическим размещением чего бы то ни было.
Ответить
KoIIIeY 21.04.2009 19:56 #
Пардон, да действительно из другой оперы.  
Ответить
Romiz 03.05.2009 17:16 #
По поводу FlashMessenger:
может вместо плагина для передачи сообщений стоило сделать хелпер вида?
Ответить
Александр Махомет 22.09.2009 23:03 #
Да, это пожалуй даже лучше.
Ответить
Алекс 26.06.2009 10:15 #
при скачивании исходников в папке JS index.html содержит две ошибки на отсутствие двух тегов, нет тега <P> и <div>
Ответить
chooha 31.07.2009 10:15 #
Александр Махомет, а вы не моглиб объяснить как работает эта строка:
sha1($formRegister->getValue('password') . 'jdh37dgvs')

как потом при логировании происходит разбор пароля?
ведь мы сначала к 'password' добавляем 'jdh37dgvs', а потом кодируем.
и что, кстати, означает строка 'jdh37dgvs'? она просто придуманая?
Ответить
Александр Махомет 01.08.2009 11:48 #
При логированни сравнивается хеш пароля, это делается чтобы в базе данных не хранились в открытом виде (безопасность)

Строка которая добавляется к паролю называется соль, ее цель усложнить подбор пароля по хешу. В общем это все касается безопасности, почитайте какие-то статьи в интернете на эту тему.
Ответить
andreykostromin 22.09.2009 16:24 #
Подскажите, как создавать формы с переменным количеством полей. Например, на javascript'е добавляется поле одно или несколько дополнительных полей email. как тут сделать?
Ответить
andreykostromin 22.09.2009 16:47 #
попробую сделать, тут отвечать не надо, протупил
/forum/index.php?topic=1075.15
/forum/index.php?topic=958.15
Ответить
Benedikt 22.09.2009 22:22 #
А 3-я часть будет?
Ответить
Александр Махомет 22.09.2009 23:02 #
Этого никто не знает.
Ответить
Lossberg 15.10.2009 19:29 #
string(1062) "ERROR: Session must be started before any output has been sent to the browser; output started in C:\Site\ilovezf\application\settings\config.php/

Получаю такую ошибку при запуске кода из статьи... В Самом конфиге вроде бы ничего не выводиться
Ответить
Lossberg 17.10.2009 17:02 #
при этом нигде в скрипта я вооще не нашел ничего о сессиях. тем более со старта
Ответить
Дмитрий 14.01.2010 16:00 #
Вопрос:
У меня в layout.phtml после сабмита выводится сразу и сообщение и форма.
<?php echo $this->layout()->messages; ?>
<?php echo $this->layout()->content; ?>
Так и должно быть или я что-то упустил?
Ответить
Дмитрий 19.02.2010 19:32 #
Супер статья, спасибо
Ответить
Денис 23.02.2010 13:28 #
Статическая переменная $jsAndCss отвечает за то, чтобу подключение

Исправьте ошибку.

if ($formRegister->isValid($this->_getAllParams())) {
не обязательно отдавать getallparams в новой версии ZF

Вообще статья шикарная! Подправьте её под новую версию ZF и цены не будет! Не поленитесь!

Очень многое узнал из статьи.

Ответить
Илья 11.05.2010 15:06 #
Почему-то не видены классы находящиеся в models.
Если в вызывающий контроллер вставить в начало
например:
require_once 'Form/Register.php';
require_once 'DbTable/Users.php';
в файл AuthController.php, то видит.
Может в config.php чего-то не хватает?
Я скачал скрипт как есть с сайта.
Ответить
Егор 26.05.2010 23:35 #
Супер, огромное спасибо!
Ответить
Артём 03.06.2010 16:58 #
Потрясно!
Ответить
romans 06.10.2010 06:07 #
Интересный пример.

Для сравнения написал такую же форму на ATK4

http://demo.atk4.com/demo.html?t=17

вместо 135 строкек вышло 41 (без коментариев в обоих случаях).

Не понимаю почему в Zend простые вещи пишуться сложно.

(код обработки формы, с записью в базу через модель - отсутствует с примера)

$c=$this->add(‘Controller_User’)->set($f->getAllData());
$c->set(‘password’,$this->api->auth->encryptPassword($c->get(‘password’)));
$c->update();
$p->js()->univ()->dialogOK(’Success’,‘Registration is successful’,$p->js()->_enclose()->univ()->location(‘/‘))->execute();

Ответить
Jet 16.11.2010 14:23 #
после установки по ссылке /register/ получаю

Fatal error: Undefined class constant 'NOT_YYYY_MM_DD' in /home/jet/www/jet-board/application/languages/errors.php on line 14

:(
Ответить
ai 05.02.2011 22:56 #
Константа NOT_YYYY_MM_DD переименована
Найди её новое название в файле \library\Zend\Validate\Date.php и используй его
Ответить
Геннадий 23.02.2011 12:29 #
Здравствуйте.
Разбираю статью для изучения декораторов для форм, в частности для подключения календаря на свою форму. Новичок в ZF. Скачал проект, запустил без виртуальных хостов. Не работали Captcha и календарь. Благодаря комменту Михаила за 26.02.2009 10:08 Captcha заработала, а с календарём проблема - он просто не отображается, а если в Register.php перед $this->addElement($dateBirth); поставить $dateBirth->getDecorators(); то выскакивает ошибка "Plugin by name 'Calendar' was not found in the registry; used paths:
Zend_Form_Decorator_: Zend/Form/Decorator/". И вообще ничего не отображается. Не могу решить проблему 2й день, вызовы типа "addPrefixPath()" не помогают, помогите пожалуйста.
Расскажите пожалуйста, как именно этот проект находит класс декоратора календаря, т.к. и своём и в этом проекте одна проблема - вышеуказанная ошибка.
Ответить
Олег 12.07.2011 09:17 #
Спасибо за статью. Проблема в том, что код формы невалидный. У вам часть элементов формы обрамлено dt, dd, а часть нет (внутри dl). Какой на ваш взгяд самый простой способ изменить данную ситуацию.
Ответить
Vladimir 06.12.2011 09:38 #
Упоминание, как быстро создать пустой проект ZF
http://habrahabr.ru/blogs/zend_framework/95706/
-------------
Примечание: В каталоге где установлен Zend Framework находится подкаталог bin содержание сценарии:
zf.sh и zf.bat для Unix-based ОС и ОС Windows соответственно.
zf create project mysuperproject
В результате выполнения этой команды будет создан стандартный проект ZendFramework влючающий стандартные контроллеры и сценарии вида.
Ответить
Уважаемые пользователи. Комментарии не для того чтобы:
  1. Спрашивать почему у вас не работает код, для этого есть тема форума закрепленная за статьей.
  2. Спрашивать как реализовать ту или иную функциональность, для этого необходимо создать свою тему на форуме.

Комментарии для того чтобы: высказать свое аргументированное мнение о статье, указать какие участки вызывают непонимание, что нужно исправить/улучшить, просто сказать спасибо.

Комментарии имеют древовидную структуру.
Если вы хотите ответить на определенный комментарий - нажмите на ссылку "Ответить" возле этого комментария.

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