Pull to refresh

Использование Zend_Form_Element_File в CRUD

Zend Framework
Sandbox
image

При проектировании приложений на 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 выглядит следующим (стандартным) образом:
image

При изменении сущности, Zend_Form_Element_File по-умолчанию выглядит аналогично. Наша задача привести вид этого элемента к такому:
image
Соответственно, отображается текущее загруженное имя файла, сам элемент для указания нового файла (старый будет им заменен) и чекбокс для удаления уже загруженного файла и обнуления столбца с фотографией для редактируемой сущности.

Создаем собственный декоратор для Zend_Form_Element_File

Для этого в папке application/forms/Decorators создаем файл File.php со следующим классом

  1. <?php
  2. class Application_Form_Decorators_File extends Zend_Form_Decorator_Abstract
  3. {
  4. public function buildLabel()
  5. {
  6. $element = $this->getElement();
  7. $label = $element->getLabel();
  8. if ($translator = $element->getTranslator()) {
  9. $label = $translator->translate($label);
  10. }
  11. if ($element->isRequired()) {
  12. $label .= '*';
  13. }
  14. $label .= ':';
  15. return $element->getView()
  16. ->formLabel($element->getName(), $label);
  17. }
  18. public function buildInput()
  19. {
  20. $element = $this->getElement();
  21. $helper = $element->helper;
  22. return $element->getView()->$helper(
  23. $element->getName(),
  24. $element->getValue(),
  25. $element->getAttribs(),
  26. $element->options
  27. );
  28. }
  29. public function buildErrors()
  30. {
  31. $element = $this->getElement();
  32. $messages = $element->getMessages();
  33. if (empty($messages)) {
  34. return '';
  35. }
  36. return '<div class="errors">' .
  37. $element->getView()->formErrors($messages) . '</div>';
  38. }
  39. public function buildDescription()
  40. {
  41. $element = $this->getElement();
  42. $desc = $element->getDescription();
  43. if (empty($desc)) {
  44. return '';
  45. }
  46. return '<div class="description">' . $desc . '</div>';
  47. }
  48. public function render($content)
  49. {
  50. $element = $this->getElement();
  51. if (!$element instanceof Zend_Form_Element) {
  52. return $content;
  53. }
  54. if (null === $element->getView()) {
  55. return $content;
  56. }
  57. $separator = $this->getSeparator();
  58. $placement = $this->getPlacement();
  59. $label = $this->buildLabel();
  60. $input = $this->buildInput();
  61. $errors = $this->buildErrors();
  62. $desc = $this->buildDescription();
  63. $renderedContent = $element->getView()->partial(
  64. 'decorators/file.phtml',
  65. array('element'=>$element));
  66. $output = '<tr><td>'
  67. . $label .'</td><td>' . $renderedContent
  68. . $input
  69. . $errors
  70. . $desc
  71. . '</td></tr>';
  72. switch ($placement) {
  73. case (self::PREPEND):
  74. return $output . $separator . $content;
  75. case (self::APPEND):
  76. default:
  77. return $content . $separator . $output;
  78. }
  79. }
  80. }


Таким образом мы можем собрать внешний вид элемента «по-частям», добавив нужную информацию для вывода. С помощью инструкции

  1. $renderedContent = $element->getView()->partial(
  2. 'decorators/file.phtml',
  3. array('element'=>$element));


Попутно передаем информацию об текущем элементе (Zend_Form_Element_File) как $element (нужно для и
рендерим файл application/views/scripts/decorators/file.phtml со следующим содержимым:

  1. Вы загрузили фото '<?php echo $this->element->getDescription(); ?>'.<br>
  2. Вы можете изменить фото, указав новый файл. Если изменения фотографии не требуется, оставьте данное поле пустым. <br>
  3. Для того, чтобы удалить фото, поставьте отметку:
  4. <input name="<?php echo $this->element->getName(); ?>_checkbox" type="checkbox" value="on" /><br />


Применение декораторов в форме


Поскольку кастомный декоратор нужен только при изменении сущности, то при добавлении сущности оставляем стандартный декоратор:
  1. if ($this->_options['type'] == 'edit' && !is_null($this->_options['photo_file'])) {
  2. $photo->setDescription($this->_options['photo_file']);
  3. $photo->setDecorators(array(
  4. array('ViewScript', array('viewScript' => 'decorators/file.phtml')),
  5. 'File',
  6. 'Errors',
  7. array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
  8. array('Label', array('tag' => 'td')),
  9. array(array('row' => 'HtmlTag'), array('tag' => 'tr'))
  10. ));
  11. } else {
  12. $photo->setDecorators(array(
  13. 'File',
  14. 'Errors',
  15. array(array('data' => 'HtmlTag'), array('tag' => 'td', 'class' => 'element')),
  16. array('Label', array('tag' => 'td')),
  17. array(array('row' => 'HtmlTag'), array('tag' => 'tr'))
  18. ));
  19. }


Использование декоратора в контроллере:


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

  1. // если поставлена галочка на удаление - то удаляем файл и пищем в БД null
  2. if (!is_null($this->getRequest()->getParam('photo_checkbox')) && $this->getRequest()->getParam('photo_checkbox') == 'on') {
  3. if (file_exists(PUBLIC_PATH . '/userfiles/images/full/' . $item->photo))
  4. unlink(PUBLIC_PATH . '/userfiles/images/full/' . $item->photo);
  5. if (file_exists(PUBLIC_PATH . '/userfiles/images/thumb/' . $item->photo))
  6. unlink(PUBLIC_PATH . '/userfiles/images/thumb/' . $item->photo);
  7. $item->photo = null;
  8. }
  9. // если указан новый файл и он загружен, то удаляем старый файл и пишем в БД новый.
  10. // в случае, если элемент File не заполнен, $form->photo->getFileName() == array(null) ;
  11. if (!is_null($form->photo->getFileName()) && count($form->photo->getFileName()) != 0) {
  12. if (file_exists(PUBLIC_PATH . '/userfiles/images/full/' . $item->photo)) {
  13. unlink(PUBLIC_PATH . '/userfiles/images/full/' . $item->photo);
  14. }
  15. if (file_exists(PUBLIC_PATH . '/userfiles/images/thumb/' . $item->photo)) {
  16. unlink(PUBLIC_PATH . '/userfiles/images/thumb/' . $item->photo);
  17. }
  18. $item->photo = $form->getValue('photo');
  19. }


Исходные коды:


  1. Application_Form_Decorators_File
  2. Application_Form_Photogallery
  3. PhotogalleryController


Если тема актуальна, то в есть еще материал по поводу написания фильтров для обработки изображений, загружаемых через Zend_Form_Element_File (gdlib и imagic).

Спасибо за внимание, принимаются комментарии и предложения. Не так давно начал изучать Zend Framework, решения подобной задачи не нашел — пришлось разбираться самому. Если что-то сделано не совсем корректно и (или) не в стиле ZF — прошу отписать в комментариях, поправлю.

Текущая реализация успешно работает.
Tags:zend frameworkzend_formzend_form_elementphp5.3
Hubs: Zend Framework
Total votes 9: ↑7 and ↓2 +5
Views2.3K

Popular right now

Профессия Project Manager
June 17, 202191,000 ₽Нетология
Факультет DevOps
June 17, 2021230,000 ₽GeekBrains
Факультет тестирования ПО
June 17, 2021144,000 ₽GeekBrains
Data Scientist
June 18, 2021126,000 ₽Нетология