The web tip on uploading file in Yii 2 using FileInput widget has seen a lot of web hits. As a result, this in turn has generated more feedback and queries by many users (both offline and online). As a request by many folks, I am creating a follow-up web tip to the earlier web tip. In the former web tip, you learnt how to create a new record and upload a file using the FileInput widget by Krajee. This web tip expands the scenario for advanced usage and include the following additional functionalities:
- optimize and enhance the model
- update the record and replace the saved image.
- delete the saved image
The following parameters are useful to setup for global access:
Yii::$app->params['uploadPath']
as suggested in the earlier web tip. This will store the server path for all your file uploads. You can copy and store a default placeholder image in this folder, to display an image, whenever no file is uploaded e.g.default_user.jpg
.Yii::$app->params['uploadUrl']
. This will store the Url pointing to server location for your file uploads. Note your web server must be configured to allow theuploadPath
access.
For example, your configuration can be:
Yii::$app->params['uploadPath'] = Yii::$app->basePath . '/uploads/';
Yii::$app->params['uploadUrl'] = Yii::$app->urlManager->baseUrl . '/uploads/';
About FileInput
The FileInput widget is based on the bootstrap-fileinput plugin by Krajee. Here are the advanced steps for you to configure the widget for upload:
Model
It is recommended to optimize code for your model and controller. Instead of overloading the controller, we will now enhance our model code from the previous web tip. We will now include these new additional methods:
getImageFile
: Will return the uploaded/stored image file name with complete pathgetImageUrl
: Will return the uploaded/stored image url accessible via browser front end.uploadImage
: Will process upload of the image and return ayii\web\UploadedFile
instance. If no image was uploaded this method will returnfalse
.deleteImage
: Will process deletion of already existing image in database and returntrue
if image file was found – else returnfalse
.
Accordingly set any additional model scenarios or validation rules for your attributes as needed.
namespace common\models;
use yii\db\ActiveRecord;
/**
* Class Person
* @package common\models
* @property int $id unique person identifier
* @property string $name person / user name
* @property array $avatar generated filename on server
* @property string $filename source filename from client
*/
class Person extends ActiveRecord
{
/**
* @var mixed image the attribute for rendering the file input
* widget for upload on the form
*/
use yii\web\UploadedFile;
public $image;
public function rules()
{
return [
[['name', 'avatar', 'filename', 'image'], 'safe'],
[['image'], 'file', 'extensions'=>'jpg, gif, png'],
];
}
/**
* fetch stored image file name with complete path
* @return string
*/
public function getImageFile()
{
return isset($this->avatar) ? Yii::$app->params['uploadPath'] . $this->avatar : null;
}
/**
* fetch stored image url
* @return string
*/
public function getImageUrl()
{
// return a default image placeholder if your source avatar is not found
$avatar = isset($this->avatar) ? $this->avatar : 'default_user.jpg';
return Yii::$app->params['uploadUrl'] . $avatar;
}
/**
* Process upload of image
*
* @return mixed the uploaded image instance
*/
public function uploadImage() {
// get the uploaded file instance. for multiple file uploads
// the following data will return an array (you may need to use
// getInstances method)
$image = UploadedFile::getInstance($this, 'image');
// if no image was uploaded abort the upload
if (empty($image)) {
return false;
}
// store the source file name
$this->filename = $image->name;
$ext = end((explode(".", $image->name)));
// generate a unique file name
$this->avatar = Yii::$app->security->generateRandomString().".{$ext}";
// the uploaded image instance
return $image;
}
/**
* Process deletion of image
*
* @return boolean the status of deletion
*/
public function deleteImage() {
$file = $this->getImageFile();
// check if file exists on server
if (empty($file) || !file_exists($file)) {
return false;
}
// check if uploaded file can be deleted on server
if (!unlink($file)) {
return false;
}
// if deletion successful, reset your file attributes
$this->avatar = null;
$this->filename = null;
return true;
}
}
Controller
Great. With the model now ready, your controller code can be leaner. Setup your controller action for create, update, and delete.
use Yii;
use common\models\Person;
use yii\web\UploadedFile;
class PersonController extends \yii\web\Controller
{
/**
* Creates a new Person model.
* If creation is successful, the browser will be redirected to the 'view' page.
* @return mixed
*/
public function actionCreate()
{
$model = new Person;
if ($model->load(Yii::$app->request->post())) {
// process uploaded image file instance
$image = $model->uploadImage();
if ($model->save()) {
// upload only if valid uploaded file instance found
if ($image !== false) {
$path = $model->getImageFile();
$image->saveAs($path);
}
return $this->redirect(['view', 'id'=>$model->id]);
} else {
// error in saving model
}
}
return $this->render('create', [
'model'=>$model,
]);
}
/**
* Updates an existing Person model.
* If update is successful, the browser will be redirected to the 'view' page.
* @param integer $id
* @return mixed
*/
public function actionUpdate($id)
{
$model = $this->findModel($id);
$oldFile = $model->getImageFile();
$oldAvatar = $model->avatar;
$oldFileName = $model->filename;
if ($model->load(Yii::$app->request->post())) {
// process uploaded image file instance
$image = $model->uploadImage();
// revert back if no valid file instance uploaded
if ($image === false) {
$model->avatar = $oldAvatar;
$model->filename = $oldFileName;
}
if ($model->save()) {
// upload only if valid uploaded file instance found
if ($image !== false && unlink($oldFile)) { // delete old and overwrite
$path = $model->getImageFile();
$image->saveAs($path);
}
return $this->redirect(['view', 'id'=>$model->_id]);
} else {
// error in saving model
}
}
return $this->render('update', [
'model'=>$model,
]);
}
/**
* Deletes an existing Person model.
* If deletion is successful, the browser will be redirected to the 'index' page.
* @param integer $id
* @return mixed
*/
public function actionDelete($id)
{
$model = $this->findModel($id);
// validate deletion and on failure process any exception
// e.g. display an error message
if ($model->delete()) {
if (!$model->deleteImage()) {
Yii::$app->session->setFlash('error', 'Error deleting image');
}
}
return $this->redirect(['index']);
}
/**
* Finds the Person model based on its primary key value.
* If the model is not found, a 404 HTTP exception will be thrown.
* @param integer $id
* @return Person the loaded model
* @throws NotFoundHttpException if the model cannot be found
*/
protected function findModel($id)
{
if (($model = Person::findOne($id)) !== null) {
return $model;
} else {
throw new NotFoundHttpException('The requested page does not exist.');
}
}
}
View
Your view pretty much remains the same as earlier except that you now add functionality for preview, update, and delete. You may also wish to add any error block to display any error messages.
Edit your _form.php or your other view file where you are rendering the create and image upload form.
use kartik\helpers\Html; // or yii\helpers\Html
use kartik\widgets\ActiveForm; // or yii\widgets\ActiveForm
use kartik\widgets\FileInput;
$form = ActiveForm::begin([
'options'=>['enctype'=>'multipart/form-data'] // important
]);
echo $form->field($model, 'filename');
// display the image uploaded or show a placeholder
// you can also use this code below in your `view.php` file
$title = isset($model->filename) && !empty($model->filename) ? $model->filename : 'Avatar';
echo Html::img($model->getImageUrl(), [
'class'=>'img-thumbnail',
'alt'=>$title,
'title'=>$title
]);
// your fileinput widget for single file upload
echo $form->field($model, 'image')->widget(FileInput::classname(), [
'options'=>['accept'=>'image/*'],
'pluginOptions'=>['allowedFileExtensions'=>['jpg','gif','png']
]);
/**
* uncomment for multiple file upload
echo $form->field($model, 'image[]')->widget(FileInput::classname(), [
'options'=>['accept'=>'image/*', 'multiple'=>true],
'pluginOptions'=>['allowedFileExtensions'=>['jpg','gif','png']
]);
*/
// render the submit button
echo Html::submitButton($model->isNewRecord ? 'Upload' : 'Update', [
'class'=>$model->isNewRecord ? 'btn btn-success' : 'btn btn-primary']
);
// render a delete image button
if (!$model->isNewRecord) {
echo Html::a('Delete', ['/person/delete', 'id'=>$model->id], ['class'=>'btn btn-danger']);
}
ActiveForm::end();
Thus, you should be able to use the FileInput for advanced cases of upload. Basically, completely manage your files for insert, update, and delete. As mentioned earlier, in case you are using multiple file input, the $image
variable in controller will return an array of files, which you must loop through. Its important for multiple file uploads to follow an array naming convention as mentioned in this web tip.
The post Advanced upload using Yii2 FileInput widget appeared first on Krajee Web Tips.