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

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.  
  19.     public function buildInput()
  20.     {
  21.         $element = $this->getElement();
  22.         $helper  = $element->helper;
  23.         return $element->getView()->$helper(
  24.             $element->getName(),
  25.             $element->getValue(),
  26.             $element->getAttribs(),
  27.             $element->options
  28.         );
  29.     }
  30.  
  31.     public function buildErrors()
  32.     {
  33.         $element  = $this->getElement();
  34.         $messages = $element->getMessages();
  35.         if (empty($messages)) {
  36.             return '';
  37.         }
  38.         return '<div class="errors">' .
  39.                $element->getView()->formErrors($messages) . '</div>';
  40.     }
  41.  
  42.     public function buildDescription()
  43.     {
  44.         $element = $this->getElement();
  45.         $desc    = $element->getDescription();
  46.         if (empty($desc)) {
  47.             return '';
  48.         }
  49.         return '<div class="description">' . $desc . '</div>';
  50.     }
  51.  
  52.     public function render($content)
  53.     {
  54.         $element = $this->getElement();
  55.         if (!$element instanceof Zend_Form_Element) {
  56.             return $content;
  57.         }
  58.         if (null === $element->getView()) {
  59.             return $content;
  60.         }
  61.  
  62.         $separator = $this->getSeparator();
  63.         $placement = $this->getPlacement();
  64.         $label     = $this->buildLabel();
  65.         $input     = $this->buildInput();
  66.         $errors    = $this->buildErrors();
  67.         $desc      = $this->buildDescription();
  68.  
  69.         $renderedContent = $element->getView()->partial(
  70.         'decorators/file.phtml',
  71.         array('element'=>$element));
  72.  
  73.  
  74.         $output = '<tr><td>'
  75.                 . $label .'</td><td>' . $renderedContent
  76.                 . $input
  77.                 . $errors
  78.                 . $desc
  79.                 . '</td></tr>';
  80.  
  81.         switch ($placement) {
  82.             case (self::PREPEND):
  83.                 return $output . $separator . $content;
  84.             case (self::APPEND):
  85.             default:
  86.                 return $content . $separator . $output;
  87.         }
  88.     }
  89. }


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

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


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


  1. Application_Form_Decorators_File
  2. Application_Form_Photogallery
  3. PhotogalleryController


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

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

Текущая реализация успешно работает.

Похожие публикации

Средняя зарплата в IT

120 000 ₽/мес.
Средняя зарплата по всем IT-специализациям на основании 9 207 анкет, за 1-ое пол. 2021 года Узнать свою зарплату
Реклама
AdBlock похитил этот баннер, но баннеры не зубы — отрастут

Подробнее

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

    +1
    Я например обычно использую viewScript для всей формы, а стандартные декораторы отключаю нафиг. Т.е. можно прям в форме перед рендерингом элемента, отрисовать картинку.
    И еще одно маленькое замечание по коду: вот вы сначала удаляете картинку, а только потом в базу пишете, а что если произойдет сбой и данные в бд не сохраняться, а файл то уже того… удален. Тут собственно не плохо бы транзакцию использовать.
      0
      либо вначале делать изменения в БД, а потом уже если изменение удачное, то удалять файл. Спасибо, учту.
        0
        Это тоже не верно, т.к. вы можете успешно сделать изменение в БД, а файл удалить не сможете, например в этот момент он будет кем — то использоваться.
          0
          В контексте когда один администратор работает с админкой такое невоможно. Если говорить в общем и целом, то да, Вы правы — транзакции более надежны, но чуть сложнее в реализации.
            0
            Вы же не уточнили, что ваш код применим только для админки, это существенно меняет дело :)
              0
              Ну писался он именно для админки. Но Ваше замечание вполне к месту, я обязательно учту это дело. Лучше перестраховаться, мало ли там чего.
        0
        Э… а как Вы файловые операции в транзакцию БД завернете?
          0
          $db->beginTransaction()
          try {
              $model->save();
              if(!$file->save()) {
                   throw new Zend_Exception();
              }
              $db->commit();
          } catch(Zend_Exception $e) {
              $db->rollBack();
          }
          
            0
            А если при комите произойдет ошибка и транзакция откатится? Файл то уже того… удален, а данные в БД остались
        0
        Мой вам совет, пишите продукт для себя или для кого — то, бэкенд или фронт, это не должно иметь значения, код должен быть безопасным на столько, насколько это возможно.
          +1
          Я отказался от зендовских форм, стало проще.
            –1
            попробуйте сделать конструктор форм для пользователя без использования Zend_Form — поймете всю прелесть компонента
            0
            Мне кажется, что
            — ваш декоратор дублирует родной код, в частности рисование label, errors и т.п. можно оставить родным дектораторам. Обычно декоратор декорирует, а не «рисует всё»
            — так pastebin.com/yMAK8tct лучше, чем так pastebin.com/w5ycEDM6, а можно ещё и неймспейс ваших фильтров добавить и подключать аналогично иным (родным) фильтрам [вкусовщина, да]
            — рисовать декоратором чекбокс, а потом принимать его значение в обход form->getValues() напрямую из реквеста — некрасиво. Идеологически вернее было бы этому чаекбоксу появится не в связи с новым декторатором, а в связи с новым элементом «checkbox» в форме.
            — создавать внутри формы модели и использовать их — некрасиво. Правильно — передавать данные форме при создании, тем более что в этом конкретном случае — это опции выпадающего списка

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

            Самое читаемое