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.
Contents
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 insrc/Controller/
folder. - Create
FileuploadController
Class that extendsAppController
. - Include
Cake\Filesystem\Folder
to create folder andCake\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
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.