CodeIgniter 4 - Insert and Update Data (CRUD) with Single File Upload

Posted by Ridwan Fadilah on Jul 18, 2020 - 03:25 pm

Insert and Update Data (CRUD) with Single File Upload

Learn about CodeIgniter 4, how to insert and update data (CRUD) with single file upload, and validation for a file uploaded.

I've made a CodeIgniter 4 CRUD example in the previous tutorial. If you've read the tutorial, I use the $_POST (superglobal variable) to catch all of the data input from the form page.

There's no problem, CodeIgniter allows that. However, it may have an impact on the security side.

Now, in this tutorial, I'll show you how to insert and update data (CRUD) correctly as exemplified on the manual guide. I'll also show you how to make a single upload file method in CodeIgniter 4.

CodeIgniter makes working with files uploaded through a form much simpler and more secure than using PHP’s $_FILES array directly.

Configure Your Model

Let's start by configuring the model first. The model has a few configurations. I'll show you how to config your model but, I just to show you a little config. You can find the full config on the CodeIgniter 4 Official user guide.



<?php

namespace App\Models;

use CodeIgniter\Model;

class ArticleModel extends Model
{
    // your table
    protected $table = 'blog_article';
    // primary key
    protected $primaryKey = 'id';
    // table fields
    protected $allowedFields = ['author', 'title', 'slug', 'description', 'status', 'date_create', 'publish_date', 'content', 'last_update', 'preview_image'];

}


The $table defines and specifies the primary database table that will work with this model. You can also use more than one table for your queries.

The $primaryKey is the name of the column that used as uniquely identifies in this table.

The $allowedFields specify the fields that allowed to use. This array should be the same as the field names on your database table.

Here's the table structure:

Table Structure

Preparing The Form

Before we work with the controller, we should prepare the form for inserting data first. In this example, I had added the form validation.

If you haven't learned about the form validation, you can read the article of CodeIgniter 4 - Form Validation Example.

Important to note: Here, I have an 'article' controller with the 'create' method, so, I set the form action attribute to action="/article/create". Be sure you have an exact controller and method.

The Form:



<form action="/article/create" method="post" enctype="multipart/form-data">
  <?= csrf_field(); ?>
  <div class="form-group my-3">
    <label for="author">Author</label>
    <input type="text" class="form-control <?= ($validation->hasError('author')) ? 'is-invalid' : ''; ?>" name="author" value="<?= old('author'); ?>">
    <div class="invalid-feedback">
      <?= $validation->getError('author'); ?>
    </div>
  </div>

  <div class="form-group my-3">
    <label for="title">Title</label>
    <input type="text" class="form-control <?= ($validation->hasError('title')) ? 'is-invalid' : ''; ?>" name="title" value="<?= old('title'); ?>">
    <div class="invalid-feedback">
      <?= $validation->getError('title'); ?>
    </div>
  </div>

  <div class="form-group my-3">
    <label for="description">Description</label>
    <input type="text" class="form-control <?= ($validation->hasError('description')) ? 'is-invalid' : ''; ?>" name="description" value="<?= old('description'); ?>">
    <div class="invalid-feedback">
      <?= $validation->getError('description'); ?>
    </div>
  </div>

  <div class="form-group my-3">
    <label for="date_create"></label>
    <input type="hidden" class="form-control" name="date_create" value="<?= date('D, d M Y H:i:s'); ?>">
  </div>

  <div class="form-file">
    <input type="file" class="form-file-input <?= ($validation->hasError('preview_image')) ? 'is-invalid' : ''; ?>" id="preview_image" name="preview_image">
      <div class="invalid-feedback">
          <?= $validation->getError('preview_image'); ?>
      </div>
    <label class="form-file-label" for="preview_image">
      <span class="form-file-text">Choose Image...</span>
      <span class="form-file-button">Browse</span>
    </label>
  </div>

  <div class="form-group my-3">
    <label for="content">Content</label>
    <textarea type="text" class="form-control <?= ($validation->hasError('content')) ? 'is-invalid' : ''; ?>" name="content" rows="7"><?= old('content'); ?></textarea>
    <div class="invalid-feedback">
      <?= $validation->getError('content'); ?>
    </div>
  </div>
  <button type="button" class="btn btn-secondary" onclick="window.location.href='<?= base_url('article'); ?>'">Back</button>
  <button type="submit" class="btn btn-primary">Save</button>
</form>


Be sure the value of the 'for' attribute is the same as the 'name' attribute.

The csrf_field(); will return a string with the HTML for hidden input with all required CSRF information.

The old() will keep the value of the input field when the form is not valid.

The date('D, d M Y H:i:s'); is the date and time.

The <?= ($validation->hasError()) ? 'is-invalid' : ''; ?> is the ternary operator ('if' and 'else' conditional statement in one line). The code can be described like this:

If the validation for (field name) has an error, add the is-invalid class. Else, don't add anything!

The is-invalid is a Bootstrap class for an invalid field.

The <?= $validation->getError('description'); ?> will display the error message.

The Controller:



public function newPost()
{

    session();

    $data = [
        'pageTitle' => 'Create a New Post',
        'validation' => \Config\Services::validation()
    ];


    return view('article/newpost', $data);
}


The session() is used to load the session. I use the session to send the error message when the validation has an error.

The code above will display by the browser like this:

The Form

Insert Data

Insert data (create) is the one of the CRUD operation.

In the previous tutorial, I use the $_POST (superglobal variable) to make it easier to catch the data from the input field. Now, I'll not use that on this CodeIgniter 4 CRUD and upload tutorial.

You can see the article of CodeIgniter 4 - Basic CRUD and Form Validation Example (Part 1)

If you already had the form for update data, now, we'll work with the controller. All the data inserted are processed by the controller.

Controller:



public function create()
{
    session();

    // load model
    $article = new ArticleModel();

    // get image
    $previewImage = $this->request->getFile('preview_image');
    // name
    $imageName = $previewImage->getRandomName();
    // moving
    $previewImage->move('assets/img', $imageName);

    $slug = url_title($this->request->getPost('title'), '-', true);
    $article->save([
        'author' => $this->request->getPost('author'),
        'title' => $this->request->getPost('title'),
        'slug' => $slug,
        'description' => $this->request->getPost('description'),
        'date_create' => $this->request->getPost('date_create'),
        'preview_image' => $imageName,
        'content' => $this->request->getPost('content')
    ]);

    return $this->response->redirect(site_url('article'));
}


Use getFile() to catch the image. Please be sure your image field name is correct.

The getRandomName() will generate a random name for the image.

The move() is used to store the image into the specific folder. The first parameter is the specific directory for the uploaded file. If the directory is not set, the file will be stored in the 'uploads' folder by default. The second parameter is the file name.

The $slug is the URL title. I use the 'title' that will use for the URL as the first parameter. The second parameter is the separator. In this case, I use '-' (dash) as the separator. The dash is the default, you can use another sign like '+' or anything you want. The third parameter is the text lowercase. By default, the value is false (lowercase: false), set it to 'true' if you want to use lowercase.

The $article is the model, and the save() is the CodeIgniter 4 built-in method.

I use getPost() for post method. You can also use the getVar(), that allows you to use both method (post and get). Or, use getGet() for get method.

Form Validation

Now is make the form validation. I'll use the custom error message for the author's name and the article preview image.

Place the validation below the $article = new ArticleModel; and before the $previewImage = $this->request->getFile('preview_image'); line.

Validation:



// validation

if (!$this->validate([
    'author' => [
        'rules' => 'required',
        'errors' => [
            'required' => 'Please input the author name. The author name is required!'
        ]
    ],
    'title' => 'required|min_length[8]|is_unique[blog_article.title]',
    'description' => 'required|min_length[100]',
    'preview_image' => [
        'rules' => 'uploaded[preview_image]|max_size[preview_image,1024]|mime_in[preview_image,image/jpg,image/jpeg,image/svg,image/webp,image/png]|is_image[preview_image]',
        'errors' => [
            'uploaded' => 'Preview image is required! Please choose an image first!',
            'max_size' => 'The size of this image is too large. The image must have less than 1MB size',
            'mime_in' => 'Your upload does not have a valid image format',
            'is_image' => 'Your file is not allowed! Please use an image!'
        ]
    ],
    'content' => 'required'
])) {

    return redirect()->to('/article/newpost')->withInput();
}


The withInput(); is used to send the validation. We can't to send the validation using the $data variable with response->redirect.

The 'errors' is a custom error message for the specific rules.

Example:

'rules' => 'required',
'errors' => ['required' => 'your custom message here!']

You can read the CodeIgniter 4 manual guide for more rules explained.

If you use Bootstrap like this example, the code above will display by the browser like this:

Form Errors



Update Data

The update data is also one of the CRUD operation.

The update method typically should have parameter. Parameter consist of two parameters. The first parameter is the $primaryKey of the record to update. The second parameter is an associative array of data. The array elements should match the name of the $allowedFields column in the $table set like the example above.

Preparing the model:



<?php

namespace App\Models;

use CodeIgniter\Model;

class ArticleModel extends Model
{
    // your table
    protected $table = 'blog_article';
    // primary key
    protected $primaryKey = 'id';
    // table fields
    protected $allowedFields = ['author', 'title', 'slug', 'description', 'status', 'date_create', 'publish_date', 'content', 'last_update', 'preview_image'];

    public function getForEdit($id)
    {
        return $this->find($id);
    }
}


The getForEdit($id) is used to find the data by $id.

Preparing the form:



<form action="/article/update" method="post" enctype="multipart/form-data">
  <?= csrf_field(); ?>
  <div class="form-group my-3">
    <label for="id"></label>
    <input type="hidden" class="form-control " name="id" value="<?= $forEdit['id']; ?>">
  </div>

  <div class="form-group my-3">
    <label for="oldImage"></label>
    <input type="hidden" class="form-control" name="oldImage" value="<?= $forEdit['preview_image']; ?>">
  </div>

  <div class="form-group my-3">
    <label for="author">Author</label>
    <input type="text" class="form-control <?= ($validation->hasError('author')) ? 'is-invalid' : ''; ?>" name="author" value="<?= $forEdit['author']; ?>">
    <div class="invalid-feedback">
      <?= $validation->getError('author'); ?>
    </div>
  </div>

  <div class="form-group my-3">
    <label for="title">Title</label>
    <input type="text" class="form-control <?= ($validation->hasError('title')) ? 'is-invalid' : ''; ?>" name="title" value="<?= $forEdit['title']; ?>">
    <div class="invalid-feedback">
      <?= $validation->getError('title'); ?>
    </div>
  </div>

  <div class="form-group my-3">
    <label for="description">Description</label>
    <input type="text" class="form-control <?= ($validation->hasError('description')) ? 'is-invalid' : ''; ?>" name="description" value="<?= $forEdit['description']; ?>">
    <div class="invalid-feedback">
      <?= $validation->getError('description'); ?>
    </div>
  </div>

  <div class="form-group my-3">
    <label for="last_update"></label>
    <input type="hidden" class="form-control" name="last_update" value="<?= date('D, d M Y H:i:s'); ?>">
  </div>

  <div class="form-file">
    <input type="file" class="form-file-input <?= ($validation->hasError('preview_image')) ? 'is-invalid' : ''; ?>" id="preview_image" name="preview_image">
    <div class="invalid-feedback">
      <?= $validation->getError('preview_image'); ?>
    </div>
    <label class="form-file-label" for="preview_image">
      <span class="form-file-text"><?= $forEdit['preview_image']; ?></span>
      <span class="form-file-button">Browse</span>
    </label>
  </div>

  <div class="form-group my-3">
    <label for="content">Content</label>
    <textarea type="text" class="form-control <?= ($validation->hasError('content')) ? 'is-invalid' : ''; ?>" name="content" rows="7"><?= $forEdit['content']; ?></textarea>
    <div class="invalid-feedback">
      <?= $validation->getError('content'); ?>
    </div>
  </div>

  <button type="button" class="btn btn-secondary" onclick="window.location.href='<?= base_url('article'); ?>'">Back</button>
  <button type="submit" class="btn btn-primary">Update</button>
</form>


Displaying the Form (Controller):



public function edit($id)
{
    $article = new articleModel();

    $data = [
        'forEdit' => $article->getForEdit($id),
        'pageTitle' => 'Edit Post',
        'validation' => \Config\Services::validation()
    ];

    return view('article/edit', $data);
}


Don't forget to add hidden input type for the id and image (especially if you use the same structure with this example.)

If you have a field with the is_unique rule, you need to put the data record into the variable first. After that set the new rule for the input field with a conditional statement.

Example:



// old title
$oldTitle = $article->getForEdit($this->request->getPost('id'));
if ($oldTitle['title'] == $this->request->getPost('title')) {
    $titleRules = 'required';
} else {
    $titleRules = 'required|min_length[8]|is_unique[blog_article.title]';
}


You also need to delete the old image if the image has changed. It's recommended for memory management of your web hosting.

You can use the unlink($filename, $context) to delete the image when the image is changed.

Example:


// delete old image
unlink('assets/img/' . $this->request->getPost('oldImage'));

The getPost('oldImage') is the hidden input. If you see the form, you will find the hidden input type with the name="oldImage" attribute.

Hidden Input

The form will display by the browser like this:

Edit Data

The upload field will be automatically filled by the old image/file.

Use also the conditional statement for this input. When the image is not changed, the database will not update the image and keep using the old image on the database record.

Example:



// get image
$previewImage = $this->request->getFile('preview_image');
// Check image if user have not uploaded image
if ($previewImage->getError() == 4) {
    // name
    $imageName = $this->request->getPost('oldImage');
 } else {
    // generate name
    $imageName = $previewImage->getRandomName();
    // moving
    $previewImage->move('assets/img', $imageName);

    // delete old image
    unlink('assets/img/' . $this->request->getPost('oldImage'));
}


Now, the method in the controller for update data will be like this:



public function update()
{
    $article = new articleModel();

    // old title
    $oldTitle = $article->getForEdit($this->request->getPost('id'));
    if ($oldTitle['title'] == $this->request->getPost('title')) {
        $titleRules = 'required';
    } else {
        $titleRules = 'required|min_length[8]|is_unique[blog_article.title]';
    }

    if (!$this->validate([
        'author' => [
            'rules' => 'required',
            'errors' => [
                'required' => 'Author name is required!'
            ]
        ],
        'title' => $titleRules,
        'description' => 'required|min_length[100]',
        'preview_image' => [
            'rules' => 'max_size[preview_image,1024]|mime_in[preview_image,image/jpg,image/jpeg,image/svg,image/webp,image/png]|is_image[preview_image]',
            'errors' => [
                'max_size' => 'The size of this image is too large. The image must have less than 1MB size',
                'mime_in' => 'Your upload does not have a valid image format',
                'is_image' => 'Your file is not allowed! Please use an image!'
            ]
        ],
        'content' => 'required'
    ])) {

        // load validation
        // $validation = \Config\Services::validation();
        return redirect()->to('/article/edit/' . $this->request->getPost('id'))->withInput();
    }

    // get image
    $previewImage = $this->request->getFile('preview_image');
    // Check image if user have not uploaded image
    if ($previewImage->getError() == 4) {
        // name
        $imageName = $this->request->getPost('oldImage');
    } else {
        // generate name
        $imageName = $previewImage->getRandomName();
        // moving
        $previewImage->move('assets/img', $imageName);

        // delete old image
        unlink('assets/img/' . $this->request->getPost('oldImage'));
    }

    $slug = url_title($this->request->getPost('title'), '-', true);

    $article->update($this->request->getPost('id'), [
        'author' => $this->request->getPost('author'),
        'title' => $this->request->getPost('title'),
        'slug' => $slug,
        'description' => $this->request->getPost('description'),
        'last_update' => $this->request->getPost('last_update'),
        'preview_image' => $imageName,
        'content' => $this->request->getPost('content')
    ]);
    return $this->response->redirect(site_url('article'));
}


Delete Data

Not like the other method, the delete method is simpler.



public function delete($id)
{
    $article = new articleModel();

    // finding image
    $post = $article->find($id);

    // delete image
    if ($post['preview_image'] == true) {
        unlink('assets/img/' . $post['preview_image']);
    }
    $article->delete($id);

    return $this->response->redirect(site_url('article'));
}


Okay, that's the CodeIgniter 4 tutorial about how to insert and update data (CRUD) with a single file upload. You can find another tutorial on our YouTube Channel. You can also find the full source code of these examples on our GitHub.

Thanks for reading this tutorial.


Related Articles