How to Upload file using jQuery AJAX with Validation in CakePHP 4

Asynchronous file upload gives the user a more seamless experience by allowing them to upload files without having to reload the page.

Handle the file on the client side using jQuery and JavaScript, send the file to the server using an AJAX request, and then process the file on the server.

If the file is not validated then you can immediately display the error messages.

In this tutorial, I show how you can upload file using jQuery AJAX with validation and display a preview of it after uploading it in CakePHP 4.

How to upload file using jQuery AJAX with Validation in CakePHP 4


Contents

  1. Include jQuery and CSRF token
  2. Create Controller
  3. Create Template
  4. Output
  5. Conclusion

1. Include jQuery and CSRF token

I am including jQuery library and CSRF token in templates/layout/default.php file.

Completed Code

<?php

$cakeDescription = 'CakePHP: the rapid development php framework';
?>
<!DOCTYPE html>
<html>
<head>
     <?= $this->Html->charset() ?>
     <meta name="viewport" content="width=device-width, initial-scale=1">

     <!-- CSRF Token -->
     <?= $this->Html->meta('csrfToken', $this->request->getAttribute('csrfToken')); ?>
     <title>
          <?= $cakeDescription ?>:
          <?= $this->fetch('title') ?>
     </title>
     <?= $this->Html->meta('icon') ?>

     <link href="https://fonts.googleapis.com/css?family=Raleway:400,700" rel="stylesheet">

     <?= $this->Html->css(['normalize.min', 'milligram.min', 'cake']) ?>

     <!-- jQuery -->
     <?= $this->Html->script('https://code.jquery.com/jquery.min.js'); ?>

     <?= $this->fetch('meta') ?>
     <?= $this->fetch('css') ?>
     <?= $this->fetch('script') ?>
</head>
<body>
     <nav class="top-nav">
         <div class="top-nav-title">
              <a href="<?= $this->Url->build('/') ?>"><span>Cake</span>PHP</a>
         </div>
         <div class="top-nav-links">
              <a target="_blank" rel="noopener" href="https://book.cakephp.org/4/">Documentation</a>
              <a target="_blank" rel="noopener" href="https://api.cakephp.org/">API</a>
         </div>
     </nav>
     <main class="main">
         <div class="container">
             <?= $this->Flash->render() ?>
             <?= $this->fetch('content') ?>
         </div>
     </main>
     <footer>
     </footer>
</body>
</html>

2. Create Controller

  • Create a FileuploadController.php file in src/Controller/ folder.
  • Create FileuploadController Class that extends AppController.
  • Include Cake\Filesystem\Folder to create folder and Cake\Validation\Validator to add validation rules.

Create 2 methods –

  • index()
  • upload() – Using this method handle AJAX file upload request.

Define validation rule for upload_file. Here, upload_file is POST file element name. Customize the validation according to your requirement.

If file is not validated then read the error message and assign it to $response['error']. Also, assign 0 to $response['success']. Return $response Array in JSON format.

If file is validated then read file details and assign them to the variables. Assign upload location to $location variable. I am uploading file to uploads folder. Check if a folder exists or not. Create it if not exists.

Upload the file to the location by calling moveTo(). After upload assign the upload path to $filepath variable.

Store filename, path, success message, and success status in $response Array. Return $response Array in JSON format.

NOTE – You can find the uploaded files in webroot/uploads/ location.

Completed Code

<?php
declare(strict_types=1);

namespace App\Controller;
use Cake\Filesystem\Folder;
use Cake\Validation\Validator;

class FileuploadController extends AppController
{

     public function index(){

     }

     // Upload file
     public function upload(){

          ## Validate File
          $validator = new Validator();

          $validator
              ->notEmptyFile('upload_file')
              ->add('upload_file', [
                   'mimeType' => [
                         'rule' => ['mimeType',['image/jpg','image/png','image/jpeg','application/pdf']],
                         'message' => 'File type must be .jpg,.jpeg,.png,.pdf',
                   ],
                   'fileSize' => [
                         'rule' => ['fileSize','<', '2MB'],
                         'message' => 'File size must be less than 2MB',
                   ]
              ]);
          $errors = $validator->validate($this->request->getData());

          $response = array();
          if (!empty($errors)) { // Not validated

               ## Return response with error message
               $fileerrors = $errors['upload_file'];
               $errormsg = "";
               foreach($fileerrors as $error){
                   $errormsg = $error;
               }
               $response['error'] = $errormsg;
               $response['success'] = 0;
               echo json_encode($response);
               die;

          }

          ## Upload file
          $fileEl = $this->request->getData('upload_file');

          // File details
          $filename = $fileEl->getClientFilename();
          $type = $fileEl->getClientMediaType();
          $size = $fileEl->getSize();
          $extension = strtolower(pathinfo($filename, PATHINFO_EXTENSION));
          $tmpName = $fileEl->getStream()->getMetadata('uri');
          $error = $fileEl->getError();

          if($error == 0){

               ## Upload location
               $location = WWW_ROOT . 'uploads' . DS;// webroot/uploads/

               ## Check upload location exists or not
               $folder = new Folder();
               $checkfolder = $folder->inPath($location, true);
               if(!$checkfolder){ // Not exists
                    if (!$folder->create($location)) {

                          $response['error'] = 'Folder is not created.';
                          $response['success'] = 0;

                          echo json_encode($response);
                          die;

                    }
               }

               ## Upload file
               $targetPath = $location.$filename;
               $fileEl->moveTo($targetPath);

               ## Uploaded file path
               $filepath = "/uploads/".$filename;

               ## Return file details with response for preview
               $imageext_arr = array('jpg','jpeg','png');
               $isImage = 0;
               if(in_array($extension,$imageext_arr)){
                     $isImage = 1;
               }

               $response['filename'] = $filename;
               $response['path'] = $filepath;
               $response['isImage'] = $isImage;

               $response['msg'] = 'File uploaded successfully.';
               $response['success'] = 1;

               echo json_encode($response);
               die;

          }else{

               $response['error'] = 'File is not uploaded.';
               $response['success'] = 0;

               echo json_encode($response);
               die;
          }

     }
}

3. Create Template

Create Fileupload folder in templates/ location. In the Fileupload folder create index.php file – templates/Fileupload/index.php.

Create a file element and a button. To display file preview after upload create <div id="div_filepreview">.


Script

Read CSRF token from <meta > tag and assign it to csrfToken variable.

Define click event on #btnupload. On button click read selected file from the file element and assign it to files variable. If files.length is not > 0 means file is not selected and display an alert message.

If files.length>0 then create FormData object. Append selected file in fd object.

Send AJAX POST request to <?= $this->Url->build(['controller' => 'fileupload','action' => 'upload']) ?>. Pass FormData object as data, set contentType and processData to false.

Using header to set csrfToken'X-CSRF-Token': csrfToken.

On successful callback check if response.success value. If response.success == 0 means file is not uploaded and alert the error message.

Completed Code

<div class="row">
    <div class="col-6">

        <?php 

        // 
        echo $this->Form->control('file',['label' => 'Select file','type' => 'file','class' => 'form-control','id' => 'upload_file']);
        echo $this->Form->button('Upload',['id'=>'btnupload']);

        ?>

        <!-- Display file preview -->
        <div id="div_filepreview">

        </div>

    </div>
</div>
<!-- Script -->
<script type="text/javascript">

// Read CSRF Token
var csrfToken = $('meta[name="csrfToken"]').attr('content');

$(document).ready(function(){

    $('#btnupload').click(function(){
         
         var files = $('#upload_file')[0].files;

         // Check file selected or not
         if(files.length > 0 ){
              var fd = new FormData();
              fd.append('upload_file',files[0]);

              $.ajax({
                   url: "<?= $this->Url->build(['controller' => 'fileupload','action' => 'upload']) ?>",
                   type: 'post',
                   data: fd,
                   contentType: false,
                   processData: false,
                   headers:{
                       'X-CSRF-Token': csrfToken
                   },
                   dataType: 'json',
                   success: function(response){

                        if(response.success == 0){ // Not validated
                             alert(response.error);
                        }else{

                             // Display preview
                             var filename = response.filename;
                             var path = response.path;
                             var isImage = response.isImage;

                             if(isImage == 1){
                                  $('#div_filepreview').html("<img src='"+path+"' width='200px' >");
                             }else{
                                  $('#div_filepreview').html("<a href='"+path+"' target='_blank' >"+ filename +"</a>");
                             }
                        }

                   },
              });
         }else{
              alert("Please select a file.");
         }
    });

});
</script>

4. Output

View Output


5. Conclusion

Use FormData object to pass the selected file for upload. You can also pass additional data by appending it to the FormData object – fd.append('filename','new-filename');.

If you want to upload a file without using AJAX in CakePHP 4, check out this tutorial.

If you found this tutorial helpful then don't forget to share.

Leave a Comment