Uploading file in Yii using CUploadedFile

This article explains how to implement file uploading in Yii framework. Let’s start with basics. First, we need to have the form with multipart/form-data encoding and input field of file type.

$form = $this->beginWidget('GxActiveForm', array(
	'id' => 'form-id',
	'enableAjaxValidation' => false,
	'htmlOptions' => array(
		'enctype' => 'multipart/form-data',
	),
));
...
<?php echo $form->fileField($model, 'filename', array('size' => 48)); ?>	

Note! I’m using GxActiveForm instead of CActiveForm because to generate CRUD I’m using giix module which is enhancement of standard gii generator.

Secondly, in the model we need to add file validator which can check the file size or extension.

	public function rules()
	{
		return array_merge(parent::rules(), array(
				array('filename', 
						'file',
						'allowEmpty' => true,
						// 'types'=>'jpg, gif, png',
						'maxSize'=>1024 * 1024 * 50, // 50MB
						'tooLarge'=>'The file was larger than 50MB. Please upload a smaller file.',
				),
			)
		);
	}	

Again, I’m using giix extension which generates base model classes and classes which extends them. Therefore to change default behavior of base class I’m just changing only some values returned by parent class. Please also note that setting ‘maxSize’ may not be sufficient. You also need to set configuration variables in php.ini (upload_max_filesize, post_max_size, memory_limit, etc.)

At the end we also need to add some code to controller (create and update actions). Let’s consider actionUpdate().

$oldfilename = $model->filename; // let's store original filename (if it was defined)
$model->setAttributes($_POST['SomeModel']);

// Now check if there was anything uploaded and store new name if so
$file = CUploadedFile::getInstance($model, 'filename');
if (is_object($file) && get_class($file)==='CUploadedFile') {			
    $model->filename = $file;
} else {
    $model->filename = $oldfilename;
}	

if ($model->save()) {
    if (is_object($file) && get_class($file)==='CUploadedFile') {
        // again, if anything was uploaded and if we have db done then move the file from tmp to the right place
        $model->filename->saveAs(Yii::app()->basePath . '/files/' . $file->filename->name);
        if ($oldfilename != $model->filename->name) {
            unlink($model->fileWithPath(Yii::app()->basePath . '/files/' . $oldfilename));
        }
    }					
    $this->redirect(array('view', 'id' => $model->id));
}

Ok, but still there are some problems. First and foremost, we don’t have a way to remove already added file (we can only replace it with new one). Secondly, if we need to have some access restrictions to uploaded files then we need to randomize their names and have them available via some dispatching php script.

To delete file we will add checkbox to indicate that the file should be deleted, so in the view add following code under fileField:

<?php if ($this->getAction()->getId() == 'update' && strlen($model->filename) > 0) { ?>
    <br /><?php echo CHtml::checkBox('delfile'); ?> check to delete file <?php echo $model->fileLink(); ?>
<?php  } ?>

At the same time new method fileLink() is added to the model to provide link to the action which will provide file itself (and where we will be able to add some access restrictions) and we are adding file deletion methods:

    public function fileLink() {
    	return CHtml::link($this->filename, CHtml::normalizeUrl(array('file', 'id' => $this->id)));
    }

    public function deleteFile() {
    	if (strlen($this->filename) > 0 && file_exists($this->fileWithPath()) && !is_dir($this->fileWithPath())) {
    		unlink($this->fileWithPath());
    	}
    }
    
    protected function afterDelete() {
    	parent::afterDelete();
    	$this->deleteFile();
    }

Now, in the controller we need to add support for file deletion (on update action only, because when calling $model->delete() the above method afterDelete() will be called and this will call file deletion). So, to simplify:

if (isset($_POST['delfile'])) {
    $model->deleteFile();
}

and at the end the action which will provide the file itself:

	public function actionFile($id) {
		$model = $this->loadModel($id, 'So');	
		if (file_exists($model->fileWithPath())) {
			header("Pragma: no-cache");
			header("Expires: 0");
			header('Content-Description: File Transfer');
			header('Content-Type: ' . CFileHelper::getMimeType($model->fileWithPath()));
			header('Content-Disposition: attachment; filename="'.$model->filename.'"');
			header('Content-Transfer-Encoding: binary');
			header('Expires: 0');
			header('Cache-Control: must-revalidate');
			header('Pragma: public');
			header('Content-Length: ' . filesize($model->fileWithPath()));		
			readfile($model->fileWithPath());			
			Yii::app()->end();
		} else {
			throw new CHttpException(404, 'Not found');
		}
	}
  1. #1 by Don Booth on January 25, 2013 - 17:46

    Nice.

    I’m new to PHP and to Yii. Wrestling with a form that has a file upload along with other form data. I like the way you did this and the explanation is clear.

    Thank you.

    Don Booth
    Toronto

  2. #2 by yasrul on March 14, 2014 - 18:20

    good artickel, but i not found content the function fileWithPath(), can you explanation it, please

  3. #3 by gifrancohe on May 19, 2015 - 23:59

    Regards.

    excellent article , you help me , I need to check that do not go I repeated files.

  4. #4 by web design on June 12, 2015 - 01:33

    As a web developer, it is essential for you to know the basics of
    HTML aat the back of your hand. Absolutely, an individual need not want too pay their hard-earned cash with none purpose.
    Youur website gives aan impression to your customers that, youu are bigger and already successful in your business.

  1. [ KOLEKSI ] Tutorial-Tutorial File Upload | phpsabila

Leave a comment