Есть стандартная задача - таблица с полями, одно из полей содержит файл. Вернее, оно будет содержать имя файла, а сам файл лежать в специальной папки. Это решение тоже вполне стандартное.
Итак, есть у меня модель наследник 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, есть примеры.