TOPICS

FIND YOUR TOPIC QUICKLY

CakePHP 1.2 File Upload

Keith Medlin

Jan 29 2008

I was unable to locate a simple file upload tutorial for CakePHP 1.2. Once I figured it out with the help of Aditya Mooley’s filePost.tar.gz from August 9th 2005. Unfortunately his tutorial and demo are no longer active, so I’ve resurrected the code, updated it for CakePHP 1.2 and will walk you through it.

Getting Started

Set up a new 1.2 project. If you need help with that there are plenty of great tutorials out there including mine (if you’re an Apple OS X Leopard user).

Create your database with the following SQL:

CREATE TABLE `attachments` (
               `id` int(10) NOT NULL AUTO_INCREMENT,
               `name` varchar(100) NOT NULL,
               `description` text NOT NULL,
               `filename` varchar(255) NOT NULL,
               `date_added` datetime NOT NULL,
               PRIMARY KEY  (`id`)
             )

Now that we’ve got our database in place and configured our database.php (you did remember to do that during your project setup right?) we can begin using the great cake console commands that come with CakePHP 1.2.

cake bake

Select to set up the model (m) and just let it configure everything.

We won’t worry about setting up the data validations for the purposes of this tutorial since there are other excellent data validation tutorials available.

Continue by setting up the controller and then the view. Make sure you do them in that order as the controller is needed by the console prior to setting up the views. You do not need to set up any of the admin routing and views.

Setting up the view

Cake will treat your filename column as a regular input field like name in this example. You need to tell the form that you want it to process multipart form data and you need to change the form field to a file field type.

Change views/add.ctp :

<!--p echo $for-->create('Attachment');?>

You’ll want to make it look like this:

<!--p echo $for-->create('Attachment', array('type' => 'file')); ?>

This sets the form option type to file, which is the HTML equivalent of:

<form id="AttachmentAddForm" action="/attachments/add" accept-charset="UNKNOWN" enctype="multipart/form-data" method="post">
</form>

When you make these changes the way CakePHP handles your upload has changed. You’re filename field is no longer:

$this->data['filename']

It now is an array as described by the php manual.

We will be accessing the filename field in the following ways:

$this->data['Attachment']['filename']['tmp_name']
$this->data['Attachment']['filename']['name']
...

Setting up the model

We need to accomplish 4 things in the model (for this basic tutorial):

  • Create some validation for the file we are uploading
  • Ensure we are uploading a unique filename
  • Process the upload
  • Delete the temporary file created on the server if the file is not valid

Creating the validation rule

Put the following at top of your Attachments_Controller.php file:

var $validateFile = array(
                          'size' => 204800,
                          'type' => 'jpg,jpeg,png,gif,'
                          );

We’ve set the maximum size of the file in bytes and we’ve set the mime types that will be accepted. In this case we’re only accepting images, but these can be customized to fit whatever use you may need. You can change to suit your needs. Remember though that these are the mime types so you don’t want to add extensions like .doc. You’ll want to add msword to accept .doc and .docx.

Now let’s set up a method to ensure that we only write unique filenames to the application.

  function generateUniqueFilename($fileName, $path='')
  {
    $path = empty($path) ? WWW_ROOT.'/files/' : $path;
    $no = 1;
    $newFileName = $fileName;
    while (file_exists("$path/".$newFileName)) {
      $no++;
      $newFileName = substr_replace($fileName, "_$no.", strrpos($fileName, "."), 1);
    }
    return $newFileName;
  }

So what we’ve got going on in this method is just a simple check in the directory we’re writing to (../webroot/files/), if the filename already exists. If the file does exist we’re going to add an underscore and a unique number to the end of it.

We should now create the method that will handle our file upload.

  function handleFileUpload($fileData, $fileName)
  {
    $error = false;
 
    //Get file type
    $typeArr = explode('/', $fileData['type']);
 
    //If size is provided for validation check with that size. Else compare the size with INI file
    if (($this->validateFile['size'] &amp;&amp; $fileData['size'] > $this->validateFile['size']) || $fileData['error'] == UPLOAD_ERR_INI_SIZE)
    {
      $error = 'File is too large to upload';
    }
    elseif ($this->validateFile['type'] &amp;&amp; (strpos($this->validateFile['type'], strtolower($typeArr[1])) === false))
    {
      //File type is not the one we are going to accept. Error!!
      $error = 'Invalid file type';
    }
    else
    {
      //Data looks OK at this stage. Let's proceed.
      if ($fileData['error'] == UPLOAD_ERR_OK)
      {
        //Oops!! File size is zero. Error!
        if ($fileData['size'] == 0)
        {
          $error = 'Zero size file found.';
        }
        else
        {
          if (is_uploaded_file($fileData['tmp_name']))
          {
            //Finally we can upload file now. Let's do it and return without errors if success in moving.
            if (!move_uploaded_file($fileData['tmp_name'], WWW_ROOT.'/files/'.$fileName))
            {
              $error = true;
            }
          }
          else
          {
            $error = true;
          }
        }
      }
    }
    return $error;
  }

Here are a few highlights of this method:

  1. We evalute the size and isolate the mime type to evaluate it as well.
  2. Once validation has completed successfully the temp file is moved to the directory we configured

Please note that you can customize where you upload the file by changing:

if (!move_uploaded_file($fileData['tmp_name'], WWW_ROOT.'/files/'."$fileName"))

Finally, let’s create a method to clean up the temporary file.

  function deleteMovedFile($fileName)
  {
    if (!$fileName || !is_file($fileName))
    {
      return true;
    }
    if(unlink($fileName))
    {
      return true;
    }
    return false;
  }

Putting it all together in the controller

Now that we’ve got the majority of the worker methods completed in our model we can tie them together in the add method of the controller.

Our new add method should look like this:

	function add()
	{
	  if (empty($this->data))
	  {
	    $this->Document->create();
	  }
	  else
	  {
	    $err = false;
	    if (!empty($this->data['Attachment']['filename']['tmp_name'])) {
	      $fileName = $this->generateUniqueFilename($this->data['Attachment']['filename']['name']);
	      echo 'The filename is:'. $fileName;
	      $error = $this->handleFileUpload($this->data['Attachment']['filename'], $fileName);
	      echo 'The error is: '. $error;
	}  else {
		print_r($this->data);
	}
	    if (!$error)
	    {
	      $this->data['Attachment']['filename'] = $fileName;
	      if ($this->Attachment->save($this->data))
	      {
	        $this->Session->setFlash(__('The Attachment has been saved', true));
	        $this->redirect(array('action'=>'index'));
	      } else {
	        $err = true;
	      }
	    } else {
	      $this->Attachment->set($this->data);
	    }
 
	    if ($error || $err)
	    {
	      //Something failed. Remove the image uploaded if any.
	      $this->deleteMovedFile(WWW_ROOT.IMAGES_URL.$fileName);
	      $this->set('error', $error);
	      $this->set('data', $this->data);
	      $this->validateErrors($this->Attachment);
	      $this->render();
	    }
	  }
}

This is a pretty standard add method. We verify that data was filled in on the form and then set up the file on the server. Once the upload has been completed we can proceed by entering all the other information.

There is, however, one problem. In a standard save the model just looks for the fields and saves them. In this case, cakePHP will be looking for:

$this->data['Attachment']['filename']

This object does not exist. It is an array. So we’ll need to set it… That’s why we’ve got the following line.

$this->data['Attachment']['filename'] = $fileName;

Once that is taken care of, you’ll be all set!

Go ahead and try it out!

Tying up some loose ends

There are still 2 things that would be nice to have.

  1. A link to download the file in the index view
  2. Protecting the directory we uploaded to so people won’t have access to all the files

Adding the download link is as easy as changing the following line in our view/index.ctp:

<!--p echo $attachment['Attachment']['description'];-->

You’ll want to change it to:

<!--p echo $htm-->link($attachment['Attachment']['filename'], '/files/'.$attachment['Attachment']['filename']); ?>

Voila, one problem solved…. Now on the more pressing security concern for our little demo.

Create an .htaccess file that looks like this:

Deny from All

Save this file to the directory in which you chose to upload your files.

Congratulations. You should now have a working simple upload in cakePHP 1.2! Once again, I’d like to extend my thanks to Aditya Mooley’s original code that formed the basis for this tutorial. I hope that you’ll find this lowers the barrier for creating simple uploads within your next cakePHP 1.2 application.

8 Responses to “CakePHP 1.2 File Upload”

  1. Paul


    Great example. Saved me a lot of time.

    One error in the tutorial:
    if (!move_uploaded_file($fileData['tmp_name'], WWW_ROOT.’/files/’.”$fileName”))
    Should be:
    if (!move_uploaded_file($fileData['tmp_name'], WWW_ROOT.’/files/’.$fileName))

  2. Keith Medlin


    You are absolutely correct. Good catch and I’m glad it helped!

  3. yashvit


    hey mate… thanks a lot for this code….
    was wondering how i’d be able to delete the file i upload?

  4. Keith Medlin


    This tutorial has been updated to fix Paul’s issue and to work with CakePHP 1.2 RC3.

  5. asaakius


    Great tutorial!

    Still pretty new to Cakephp although I have plenty of experience in php and webdev. I’m using this for uploading resumes, basically I’ve done the same thing, just changed the word “attachment” to resume.

    According to the error I get, apparently “Document” isn’t recognized by cake?
    I get the following error in the add file:

    Notice: Undefined property: ResumesController::$Document in C:\wamp\www\cakephp\app\controllers\resumes_controller.php on line 22

  6. Jose Sanchis


    Thanks a lot for the example.

    Just another typo/error in the tutorial :

    var $validateFile = array(
    ’size’ = 204800,

    should be:

    var $validateFile = array(
    ’size’ => 204800,

    Anyway, thanks again.

  7. Keith Medlin


    Fixed it! Thanks for the heads up Jose.

  8. Keith Medlin


    The ASCII character issue has been resolved as well…

Leave a Reply





You can use basic HTML styling tags such as bold or italics. Other markup will be removed when you submit your post.

RECENT COMMENTS




  • ON...Installing CakePHP on OS X Leopard 10.5.1
    "Thanks for this great tutorial. Even tho after editing httpd.conf to enable vhosts Web Sharing won’t work (Mac OS X 10.5.6), by letting the vhosts option commented I got it..."
  • Cristian Gradisteanu
  • ON...CakePHP 1.2 File Upload
    "The ASCII character issue has been resolved as well…"
  • Keith Medlin