Single file upload in Yii

Site has moved

This site has moved to a new location. Visit the new site at http://programsdream.nl.

By Ronald van Belzen | May 24, 2013

When uploading files in Yii the first choice to be made is to either use an existing model or create a new model. The existing model can be of the type CActiveRecord. The new model is usually of the type CFormModel. The reason for the choice for a class inheriting from CFormModel is that the uploaded file is usually not saved in a database table and therefore no CActiveRecord is required.

That does not mean that a model based upon CActiveRecord cannot be used. When the meta data of an uploaded file is saved in a database table it even makes perfect sense to use that model for the file upload.

In this example an image is uploaded to serve as logo for one of the blogs.The image will be saved in the file system. The name of the image is saved in the database table field "logo". To the existing Blog model the variable "$filename" is added to be used in the upload form:

class Blog extends CActiveRecord { 
  public $filename;

The validation of the uploaded image will also be done by the model. To the function "rules()" the validation rule for "filename" is added:

public function rules() { 
  return array( 
    // other rules 
    array('filename', 'file', 'allowEmpty' => true,'maxSize' => 102400, 'types' => 'jpg, jpeg, png'), 
    // other rules 
  ); 
}

The form that is used to create and update the database table already exists. This CActiveForm is adapted to be able to handle file uploads by adding the following "htmlOptions" property:

<?php $form=$this->beginWidget('CActiveForm', array( 
  'id'=>'blog-form', 
  'enableAjaxValidation'=>false, 
  'htmlOptions'=>array('enctype'=>'multipart/form-data'), 
)); ?>

This adds the mandatory attribute enctype="multipart/form-data" for file upload to the html form.
NOTE: The default method POST must be used and Ajax validation needs to be disabled.

Next the field for the file upload needs to be added to the form:

  <div class="row"> 
    <?php echo $form->labelEx($model,'logo'); ?> 
    <?php if($model->logo !== null) 
      echo CHtml::image(Yii::app()->request->baseUrl . '/protected/logos/' . $model->logo); ?> 
    <br> 
    <?php echo $form->fileField($model, 'filename'); ?> 
    <?php echo $form->error($model,'filename'); ?> 
  </div>

In the above the label belonging to the field variable  "$logo" is shown instead of the label belonging to the variable "$filename" and the image is shown when the "$logo" variable is set. But the essence is found in the line in which the method  fileField() is called. This adds the required input field to the form.

Now that the form contains the file input field we need to handle the file upload. The controller methods actionCreate() and actionUpdate() need to be adapted. For example actionCreate() becomes:

public function actionCreate() { 
  $model=new Blog; 
  if(isset($_POST['Blog'])) { 
    $model->attributes=$_POST['Blog']; 
    $model->filename = CUploadedFile::getInstance($model, 'filename'); 
    if($model->save()) { 
      if ($model->filename !== null) { 
        $dest = Yii::getPathOfAlias('application.logos'); 
        $model->filename->saveAs($dest . '/' . $model->id . '.' . $model->filename->extensionName); 
        $model->logo = $model->id . '.' . $model->filename->extensionName; $model->save();
      } 
      $this->redirect(array('view','id'=>$model->id));
    }
  } 
  $this->render('create', array( 
    'model'=>$model, 
  )); 
}

The method actionCreate() has been changed by first setting the value for $model->filename. For this the class method getInstance() of the class CUploadedFile is used. CUploadedFile represents the information for an uploaded file; comparable to PHP's $_FILES array.

Next, when the information can be saved, because it passed validation, we check whether a file was uploaded. When a file was uploaded it is saved to the file system and the name of the image is saved in the database.

This will work, but we might consider making the file upload more secure. To be sure the uploaded file is actually of the type that you want to allow for upload make use of Yii's CFileHelper class. This can be accomplished by adding the "mimeTypes" property to a file validation rule or replacing the existing rule by:

  array(
    'filename', 'file', 
    'allowEmpty' => true, 
    'maxSize' => 102400, 
    'mimeTypes' => 'image/jpeg, 
    image/pjpeg, image/png' 
  ),

How to handle the upload of multiple files of the same type will be subject of a future post.