При проектировании приложений на Zend Framework использование компонента Zend_Form существенно облегчает работу с сущностями – единожды созданная форма, с настроенными валидаторами, фильтрами и прочим, используется как при создании, так и при редактировании данных в БД.
Очень часто сущности содержат элементы типа File – будь то картинка-превью, имя файла для скачивания или фотография в галерее. Вот только если Вы захотите изменить сущность в форме которой есть элемент File, становится ясно что стандартный декоратор Zend_Form_Element_File не подходит для формы изменения сущности – т.к. он не позволяет отобразить наличие загруженного файла, не дает возможности удалить этот файл и т.п.
Иными словами, когда вы открываете сущность для изменения, все остальные элементы формы заполняются значениями из БД – при этом Zend_Form_Element_File этого делать не имеет.
В это статье хочу поделиться своей реализацией работы с Zend_Form_Element_File в рамках создания CRUD (create-read-update-delete) на ZendFramework 1.11
Под хабракатом вас ожидает подробное описание как создать декоратор, как прицепить его к форме и инструкции по работе с контроллером.
Zend_Form_Element_File – общая информация, особенности
При добавлении новой сущности в БД Zend_Form_Element_File выглядит следующим (стандартным) образом:
При изменении сущности, Zend_Form_Element_File по-умолчанию выглядит аналогично. Наша задача привести вид этого элемента к такому:
Соответственно, отображается текущее загруженное имя файла, сам элемент для указания нового файла (старый будет им заменен) и чекбокс для удаления уже загруженного файла и обнуления столбца с фотографией для редактируемой сущности.
Создаем собственный декоратор для Zend_Form_Element_File
Для этого в папке application/forms/Decorators создаем файл File.php со следующим классом
- <?php
- class Application_Form_Decorators_File extends Zend_Form_Decorator_Abstract
- {
- public function buildLabel()
- {
- $element = $this->getElement();
- $label = $element->getLabel();
- if ($translator = $element->getTranslator()) {
- $label = $translator->translate($label);
- }
- if ($element->isRequired()) {
- $label .= '*';
- }
- $label .= ':';
- return $element->getView()
- ->formLabel($element->getName(), $label);
- }
- public function buildInput()
- {
- $element = $this->getElement();
- $helper = $element->helper;
- return $element->getView()->$helper(
- $element->getName(),
- $element->getValue(),
- $element->getAttribs(),
- $element->options
- );
- }
- public function buildErrors()
- {
- $element = $this->getElement();
- $messages = $element->getMessages();
- if (empty($messages)) {
- return '';
- }
- return '<div class="errors">' .
- $element->getView()->formErrors($messages) . '</div>';
- }
- public function buildDescription()
- {
- $element = $this->getElement();
- $desc = $element->getDescription();
- if (empty($desc)) {
- return '';
- }
- return '<div class="description">' . $desc . '</div>';
- }
- public function render($content)
- {
- $element = $this->getElement();
- if (!$element instanceof Zend_Form_Element) {
- return $content;
- }
- if (null === $element->getView()) {
- return $content;
- }
- $separator = $this->getSeparator();
- $placement = $this->getPlacement();
- $label = $this->buildLabel();
- $input = $this->buildInput();
- $errors = $this->buildErrors();
- $desc = $this->buildDescription();
- $renderedContent = $element->getView()->partial(
- 'decorators/file.phtml',
- array('element'=>$element));
- $output = '<tr><td>'
- . $label .'</td><td>' . $renderedContent
- . $input
- . $errors
- . $desc
- . '</td></tr>';
- switch ($placement) {
- case (self::PREPEND):
- return $output . $separator . $content;
- case (self::APPEND):
- default:
- return $content . $separator . $output;
- }
- }
- }
Таким образом мы можем собрать внешний вид элемента «по-частям», добавив нужную информацию для вывода. С помощью инструкции
- $renderedContent = $element->getView()->partial(
- 'decorators/file.phtml',
- array('element'=>$element));
Попутно передаем информацию об текущем элементе (Zend_Form_Element_File) как $element (нужно для и
рендерим файл application/views/scripts/decorators/file.phtml со следующим содержимым:
- Вы загрузили фото '<?php echo $this->element->getDescription(); ?>'.<br>
- Вы можете изменить фото, указав новый файл. Если изменения фотографии не требуется, оставьте данное поле пустым. <br>
- Для того, чтобы удалить фото, поставьте отметку:
- <input name="<?php echo $this->element->getName(); ?>_checkbox" type="checkbox" value="on" /><br />
Применение декораторов в форме
Поскольку кастомный декоратор нужен только при изменении сущности, то при добавлении сущности оставляем стандартный декоратор:
- if ($this->_options['type'] == 'edit' && !is_null($this->_options['photo_file'])) {
- $photo->setDescription($this->_options['photo_file']);
- $photo->setDecorators(array(
- array('ViewScript', array('viewScript' => 'decorators/file.phtml')),
- 'File',
- 'Errors',
- array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
- array('Label', array('tag' => 'td')),
- array(array('row' => 'HtmlTag'), array('tag' => 'tr'))
- ));
- } else {
- $photo->setDecorators(array(
- 'File',
- 'Errors',
- array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
- array('Label', array('tag' => 'td')),
- array(array('row' => 'HtmlTag'), array('tag' => 'tr'))
- ));
- }
Использование декоратора в контроллере:
Для каждой загруженной картинки создается превью с помощью фильтра — соответственно при удалении картинки надо удалять и превью.
- // если поставлена галочка на удаление - то удаляем файл и пищем в БД null
- if (!is_null($this->getRequest()->getParam('photo_checkbox')) && $this->getRequest()->getParam('photo_checkbox') == 'on') {
- if (file_exists(PUBLIC_PATH . '/userfiles/images/full/' . $item->photo))
- unlink(PUBLIC_PATH . '/userfiles/images/full/' . $item->photo);
- if (file_exists(PUBLIC_PATH . '/userfiles/images/thumb/' . $item->photo))
- unlink(PUBLIC_PATH . '/userfiles/images/thumb/' . $item->photo);
- $item->photo = null;
- }
- // если указан новый файл и он загружен, то удаляем старый файл и пишем в БД новый.
- // в случае, если элемент File не заполнен, $form->photo->getFileName() == array(null) ;
- if (!is_null($form->photo->getFileName()) && count($form->photo->getFileName()) != 0) {
- if (file_exists(PUBLIC_PATH . '/userfiles/images/full/' . $item->photo)) {
- unlink(PUBLIC_PATH . '/userfiles/images/full/' . $item->photo);
- }
- if (file_exists(PUBLIC_PATH . '/userfiles/images/thumb/' . $item->photo)) {
- unlink(PUBLIC_PATH . '/userfiles/images/thumb/' . $item->photo);
- }
- $item->photo = $form->getValue('photo');
- }
Исходные коды:
Если тема актуальна, то в есть еще материал по поводу написания фильтров для обработки изображений, загружаемых через Zend_Form_Element_File (gdlib и imagic).
Спасибо за внимание, принимаются комментарии и предложения. Не так давно начал изучать Zend Framework, решения подобной задачи не нашел — пришлось разбираться самому. Если что-то сделано не совсем корректно и (или) не в стиле ZF — прошу отписать в комментариях, поправлю.
Текущая реализация успешно работает.