Поведение для прикрепления множества файлов к модели без использования базы данных OMultiFileBehavior для Yii
Понадобилось на одном простеньком внутреннем проекте на Yii добавить возможность прикреплять к сущностям файлы. Быстрое гугление не привело к результату (наверное, задача такая банальная, что никто не выкладывает :-) Что ж, рискну я.
Итак, суть поведения (behavior) следующая: описываем его у нужной модели, передаём путь до сохранения файлов и имя поля, которое укажем в форме модели у виджета CMultiFileUpload. Поведение будет типа CActiveRecordBehavior будет срабатывать на событие afterSave у модели.
Код поведения
/** * Created by olegpro.ru. * User: Oleg Maksimenko <oleg.39style@gmail.com> * Date: 21.08.2015. Time: 16:42 */ class OMultiFileBehavior extends CActiveRecordBehavior { /** * @var string */ public $filesPath = ''; /** * @var string */ public $filesField = 'files'; /** * @param CEvent $event */ public function afterSave($event) { $files = CUploadedFile::getInstancesByName($this->filesField); if (isset($files) && is_array($files) && sizeof($files) > 0) { $dirFiles = $this->prepareFilesPath($event->sender); if (!is_dir($dirFiles)) { @mkdir($dirFiles, 0775, true); } if (is_dir($dirFiles)) { /** @var CUploadedFile $file */ foreach ($files as $fileNum => $file) { if ($file->saveAs(sprintf('%s/%s', $dirFiles, $file->name))) { } } } } } /** * @return array */ public function getAttachedFiles() { $files = array(); $dirFiles = $this->prepareFilesPath($this->owner); if (is_dir($dirFiles) && $handle = opendir($dirFiles)) { $i = 1; while (false !== ($entry = readdir($handle))) { if ($entry != '.' && $entry != '..') { $files[] = array( 'id' => $i++, 'name' => $entry, 'path' => str_replace(Yii::getPathOfAlias('webroot'), '', sprintf('%s/%s', $dirFiles, $entry)) ); } } closedir($handle); } return $files; } /** * @param CActiveRecord $model * @return mixed */ private function prepareFilesPath ($model){ return str_replace( array( '{id}' ), array( $model->id ), Yii::getPathOfAlias('webroot') . $this->filesPath ); } }
«Прикрепляем» наше поведение OMultiFileBehavior к нужной модели.
// Model class MyModel extends CActiveRecord { public function behaviors() { return array( 'OMultiFileBehavior' => array( 'class' => 'OMultiFileBehavior', 'filesPath' => '/files/software/{id}', 'filesField' => 'files', ) ); } }
Важно! В представление формы модели у модели надо добавить параметр enctype равный multipart/form-data. Это важно. Без этого CUploadedFile::getInstancesByName будет возвращать всегда пустой массив.
$form=$this->beginWidget('bootstrap.widgets.TbActiveForm',array( // ... 'htmlOptions' => array('enctype'=>'multipart/form-data'), ));
Так же нужно добавить вызов виджета CMultiFileUpload, которое выведет поле типа «файл» и будет показывать список уже прикрёпленных файлов в момент выбора.
// Form view echo CHtml::label('Files', ''); $this->widget('CMultiFileUpload', array( 'name' => 'files', 'accept' => 'xml|html|pdf|jpeg|jpg|gif|png|doc|docx|xls|xlsx', 'duplicate' => 'File is not existed!', 'denied' => 'Incorrect file type', 'htmlOptions'=>array( 'class'=>'form-control', ), ));
В детальное представление нашей модели добавим вывод уже прикреплённых файлов. $model->attachedFiles съинициирует вызов OMultiFileBehavior->getAttachedFiles() у нужной модели.
// Detail view if(isset($model->attachedFiles) && is_array($model->attachedFiles) && !empty($model->attachedFiles)){ <br /> <h3>Файлы</h3> $this->widget('bootstrap.widgets.TbGridView',array( 'dataProvider'=> new CArrayDataProvider($model->attachedFiles), 'type'=>'striped bordered', 'template' => '{items} {pager}', 'enableSorting' => false, 'enablePagination' => false, 'hideHeader' => true, 'columns'=>array( array( 'class'=>'CLinkColumn', 'labelExpression' => '$data["name"]', 'urlExpression' => '$data["path"]', 'linkHtmlOptions' => array('target' => '_blank') ), ) )); }
Ну вот и всё. Домашним заданием будет: добавить возможность удаления файлов, прикреплённых к модели.