@Vermut756

Загрузка файла и ActiveRecord — как? А именно, как сначала сделать saveAs файла, затем model->save?

Есть стандартная задача - таблица с полями, одно из полей содержит файл. Вернее, оно будет содержать имя файла, а сам файл лежать в специальной папки. Это решение тоже вполне стандартное.

Итак, есть у меня модель наследник ActiveRecord, есть стандартный сгенеренный CRUD-контроллер, все работает, но надо добавить этот самый файл.
Что я делаю:
1. В модели объявляю само поле (это как если бы была обычная Model, кстати долго не мог понять почему у меня пишет что не может getProperty, мое внимание отвлекли все эти rules и fields и почему-то начал думать, что если написать там, то ActiveRecord сам создаст это поле в классе, но на самом деле он их создает только для столбцов таблицы)
public $myfile_logo;
2. Затем в модели же добавляю правила валидации для поля:
public function rules()
    {
        return [
            ...
            [['myfile_logo'], 'file', 'skipOnEmpty' => false, 'extensions' => 'png, jpg'],
            ...
        ];
    }

3. Затем в ней же объявляю вот такой метод:
public function uploadMyFiles() {
		
		//just gets it from $_FILES
            $this->myfile_logo = UploadedFile::getInstance($this, 'myfile_logo');
		
		//TODO this->validate() should be here,
		// or all this should be in save() and validate should be in save
		
		//$this->validate(['myfile_logo']); hasn't sense because ActiveRecord anyway validates all
		
		//just move_uploaded_file
		$newFileId = $this->myfile_logo->baseName . '_' . time() . '_' . uniqid();
		$newFileName = $newFileId . '.png'; //TODO do not stupidly cast any extension to png
		$saveRes = $this->myfile_logo->saveAs('files/images/' . $newFileName);
		
		if ($saveRes){
			$this->img_logo = $newFileId;
			
			return true;
		} else {
			return false;
		}
	}

4. Далее, как нетрудно догадаться, в контролере я сперва вызываю из модели эту самую функцию uploadMyFiles, чтоб она переместила файл в постоянную папку, а полученное имя файла вставила в img_logo (это уже поле БД), сразу после этого там же в контроллере следует вызов save() чтобы модель сохранилась в БД вместе с этим img_logo. Короче, вот прямо так:
$model = new Fruit();

if ($model->load(Yii::$app->request->post()) && $model->uploadMyFiles() && $model->save()) {
...


Но мои планы рушит валидатор - ведь после того, как я вызвал uploadMyFiles, я вызываю save, он там внутри вызывает ActiveRecord::insert(), а он Model::validate() который пытается проверить временный файл, а временного файла-то уже нет.

Я попытался исправить положение, добавив выборочную валидацию файла в uploadMyFiles, которой, как я верно заметил в TODO, там как раз самое место:
$this->validate(['myfile_logo']);
В коде выше эта строка закомментирована.
Но, как оказалось, такой трюк не работает - Model::validate() из ActiveRecord::insert() все равно тупо валидирует все поля, включая и это, уже проверенное вообще-то.

Пока что временно я "решил" проблему, просто убрав валидацию файла вообще, то есть закомментировав правило в rules().

Но это не решение.
Поэтому вопрос - как это сделать логичнее?

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

P.S. Копать готовые библиотеки вроде kartik не хочу. Они мне не очень нравятся - кривые, не знаешь сколько придется переделывать, иногда вообще проще написать свою. Разве что поискать как там решена проблема - они вроде бы нормально работают с ActiveRecord, есть примеры.
  • Вопрос задан
  • 418 просмотров
Пригласить эксперта
Ответы на вопрос 2
slo_nik
@slo_nik Куратор тега Yii
Добрый день.
В документации всё расписано.

p.s. Для загрузки файла я использовал mihaildev\elfinder\InputFile, вполне приемлемое решение.
Подключается элементарно
<?= $form->field($model, 'logo')->widget(InputFile::className(),['path' => 'logo_dir', 'options' => ['readonly' => true]]) ?>
Ответ написан
tkutru
@tkutru
A programming cat.
Есть три основных варианта.
1. Правильный: Соблюдать принцип единственной ответственности.
Т.е. если есть какая-то модель и к ней можно дополнительно прикреплять картинки, то заведите отдельный класс для формы загрузки, в ее свойствах можно отдельно указать набор свойств для модели и отдельно - загружаемые файлы.
Собственно в правилах валидации формы проверять файлы на соответствие форматам. При сохранении формы отдельно валидируйте-сохраняйте файл(ы), и отдельно - модель.
www.yiiframework.com/doc-2.0/guide-input-file-uplo...
Если файлы станут самостоятельной сущностью - заведите для них отдельную AR модель.

2. Менее правильный: исправить ошибку в текущем решении.
Проблема в том, что ваш метод uploadMyFiles вызывается до валидатора. Соответственно, можете вызывать его не в контроллере, а в методе модели afterValidate (он будет вызываться перед сохранением модели и после прохождения всех валидаций).

3. Прочие способы (наименее предпочтительно).
В валидаторе file можно задать сценарий при котором он (не) будет отрабатывать, после загрузки - менять сценарий на тот, при котором валидатор не работает. Другой вариант - использовать готовые решения. Еще один вариант - заменить валидатор на кастомный, который будет проверять файл на соответствие форматам и вызывать последующую загрузку.
Ответ написан
Ваш ответ на вопрос

Войдите, чтобы написать ответ

Похожие вопросы