Code Igniter

Code Igniter is a simple and easy-to-learn PHP framework following the Model-View-Controller (MVC) architectural pattern.

The best way to get started is to use composer:

$ composer create-project codeigniter4/appstarter my_project
$ cd my_project && php spark serve # test that it works

Some features are:

  • πŸ›£οΈ URI Routing
  • πŸ”οΈ Built-in security and input validation helper
  • πŸͺ️ Built-in session management helper
  • πŸš€οΈ Built-in caching helper
  • πŸ”€ Localization

Code Igniter 4 is the current major version.


File structure

Code Igniter files are split into 3 sections: app, public, and writable.

➑️ Don't forget to rename env to .env. Set CI_ENVIRONMENT to development to load debugging features.

public

A modern secure practice is to not expose the website source (app...) to the public. Your website domain name such as example.com should be configured to point to /path/to/my_project/public/. A user won't be able to write example.com/../app/sensitive_file.

☠️ Traditionally, example.com pointed to /path/to/my_project/ that allowed us to do example.com/app/sensitive_file.

You will store in public any file that the user will be allowed to access using a URL. This includes:

  • πŸ–ΌοΈ Images (ex: public/assets/img/)
  • πŸ–ŒοΈ CSS (ex: public/assets/css/)
  • πŸ€– JavaScript (ex: public/assets/js/)
  • ...

The path to this folder is stored in PUBLICPATH from the code.

writable

This directory contains files created by the application:

  • πŸ“„ Logs (debug logs, errors in production)
  • πŸš€ Cache files (delete one to clear its page cache)
  • 🧡 Session files (each user created session)
  • πŸ§‘ Uploaded files (any uploaded file that the user shouldn't access)

The path to this folder is stored in WRITEPATH from the code.

app

The application folder is split into more folders.

app/Config

The most important files are:

  • App.php - edit $baseURL to match your domain name; or the path to the public folder during development

  • Routes.php - map a route (ex: /) to a controller (ex: Home.php)

app/Controllers

A controller is a PHP script that can render multiple views of your application. We usually create one controller per set of related views.

<?php // Home.php
namespace App\Controllers;

class Home extends BaseController {
    public function index(): string
    {
        return view('welcome_message');
    }
}

app/Views

This folder contains PHP files that contain or generate the HTML that will be displayed to the user.

<?php // welcome_message.php
echo "Hello, World!";

Controllers πŸ€–

A controller is a PHP class that extends BaseController. Each method corresponds to a page of your website. They should fetch data from the model, parse it, and pass it to the views.

For instance, imagine a page that displays the latest post on a website.

  • we will first fetch the latest post (Model)
  • then we will display it (View)
public function index(): void {
    // Model
    // imagine that we load this from the model
    $latest_post = ['title' => '...', 'content' => '...'];
    
    // View
    echo view('header.php');
    echo view('latest.php', [ 'post' => $latest_post]);
    echo view('footer.php');
}

The view(path) method will execute the PHP in app/Views/path. We can pass variables to these PHP files. When executing latest.php, it will have access to the variable $post which was set to $latest_post.

You may redirect the user:

public function index(): ?\CodeIgniter\HTTP\RedirectResponse
{
    if ($is_connected) {
        // ...
        return null; // no redirect
    }
    // see also: redirect()->to("/xxx")
    return redirect('login'); // redirect
}

You may raise a throw PageNotFoundException::forPageNotFound("message"); exception if


Views

Views are simply PHP files with the HTML to display.

<?php
if (!isset($post)) die('Missing required argument: $post');
?>

<h1>Latest Post</h1>

<p>Title: <?=$post['title']?></p>
<p>Content: <?=$post['content']?></p>

You will most likely use:

  • site_url('XXX'): https://example.com/index.php/XXX, for links
  • base_url('XXX'): https://example.com/XXX, for images

πŸ›£οΈ Routing πŸ›£οΈ

By default, with file routing, you'll have URLs such as https://example.com/toto.php and one PHP per route (/toto.php).

Code Igniter uses automatic routing. Every request is redirected to public/index.php using the .htaccess file.

Then, using app/Config/Routes.php and app/Config/Routing.php, it will call a method from a Controller according to the routes we defined.


Auto Controller Routing

Before, code igniter was enabling auto-routing, meaning that https://example.com/Controller/method would call method from Controller. This is insecure, don't use it.

// app/Config/Routing.php
public bool $autoRoute = false;

Default routing

You can edit these two to change the default controller/method used when there is none specified in the routes defined later.

// app/Config/Routing.php
public string $defaultNamespace = 'App\Controllers';
public string $defaultController = 'Home';
public string $defaultMethod = 'index';

Specify routing patterns

Routes are declared in app/Config/Routes.php:

$routes->get('/', 'XXXController::index');

➑️ It means $base_url/ will call XXXController#index().

You can pass arguments to your method:

$routes->get('/', 'XXXController::index/arg1/arg2');

You can create dynamic routes:

// the first argument will be the number matching (:num)
$routes->get('/products/:num', 'Products::show_product/$1');
// the first argument (:any) is optional
$routes->get('/xxx/(:any)?', 'XXX::xxx/$1');

➑️ You can replace get(...) with post, put, delete...

404 page

If a route is not found, this controller/method will be called if set.

// app/Config/Routing.php
public ?string $override404 = 'App\Controllers\XXX::yyy';

⚠️ Don't forget to set the HTTP response code to 404 in yyy...


Database

Define the database configuration in apps/Config/Database.php. To avoid writing SQL, Code Igniter has what we call Models πŸš€.

$db = \Config\Database::connect();

To create a non-prepared statement ❌ (vulnerable):

$user_id = "5"; 
$r $db->query("SELECT * from user where id = ".$user_id." LIMIT 0,1;");

To create a prepared statement βœ…:

$user_id = "";
$stmt = $db->prepare(function($db) {
    $sql = "SELECT * from user where id = ? LIMIT 0,1;";
    return (new \CodeIgniter\Database\Query($db))->setQuery($sql);
});
$r = $stmt->execute($user_id);

To get the results as an array:

$results = $r->getResultArray();

While it's not a good practice, you can use the driver-specific execute and write code specific to mysqli/...

$res = $stmt->_execute([$user_id]);
// use the driver-specific functions

Models

Code Igniter implements a sort of Object-Relational Mapping (ORM) using Models. Each model corresponds to a table in the database.

<?php
namespace App\Models;

use CodeIgniter\Model;

class PostModel extends Model
{
    protected $table = 'posts';
    protected $primaryKey = 'id';
    protected $allowedFields = ['id', 'title', 'content'];
    protected $validationRules = [
        'title' => 'required|min_length[3]',
        'content' => 'required'
    ];
}
// crΓ©ate
$postModel = new \App\Models\PostModel();         // normal
$postModel = model(\App\Models\PostModel::class); // shared

// Select id,title,content from posts;
$posts = $postModel->findAll();

// Select [...] from posts where id = some_id;
$post = $postModel->find('some_id');
// Select [...] where id in ['id_1', 'id_2', 'id_3']
$posts = $postModel->find(['id_1', 'id_2', 'id_3']);

// Select [...] from posts where key = value;
$posts = $postModel->where('key', 'value')->findAll();

// Update every record
$postModel->update(['key' => 'value']);
// you can also use "set"
$postModel->whereIn('id', [1, 2, 3])
    ->set(['banned' => 1])
    ->update();

Request/Response

From any file, you can access the request (the data about the one requesting the page), and the response (the response to the request).

// REQUEST
service('request')->getPath()
service('request')->getIPAddress()
// RESPONSE
service('response')->setStatusCode(ResponseInterface::HTTP_NOT_FOUND);

From a Controller, you can use:

$this->request->xxx();
$this->response->xxx();

Request: file upload

You can use Code Igniter file utilities to handle uploads.

$f = $this->request->getFile("fileID"); // null?
$file_name = $f->getName(); // empty?
$size = $f->getSize(); // 1000000=1mb
$ext = $f->guessExtension(); // ex: 'png'
$mtype = $f->getMimeType(); // ex: 'image/png'
$f->store("folderName", "fileName");

Localization

To create apps supporting multiple languages, you need to create folders per locale (en, fr...) in app/Language.

Edit app/Config/App.php to set available locales and the default one.

public string $defaultLocale = 'en';
public bool $negotiateLocale = false;
public array $supportedLocales = ['en'];
// don't fallback to default if the locale is not found
$routes->useSupportedLocalesOnly(true);

For instance, let's say you have the file en/Test.php.

<?php
return [
    // key => value
    'greeting' => 'Hello'
];

Inside the code, instead of a hard-coded text, use:

-<p>Hello</p>
+<p><?=lang('Test.greeting')?></p>

You'll create the same file in another language (ex: fr/Test.php):

<?php
return [
    'greeting' => 'Bonjour'
];

Now, according to the locale loaded, the text will change.

  • Use {locale} in routes (en/home, fr/home)
  • Auto-detection if negotiateLocale is true
  • From the code
// Get the locale
$locale = service('request')->getLocale();
// Set the locale
service('request')->setLocale('fr');

➑️ See localization for more details.


Utilities

Services/email

You can define the default mail configuration in apps/Config/Email.php. Here is a list of methods you can use:

$email = \Config\Services::email();;
$email->setFrom('xxx@xxx.xxx', 'XXX xxx');
$email->setReplyTo('no-reply@xxx.xxx');
$email->setTo('yyy@xxx.xxx'); // one
$email->setTo('yyy@xxx.xxx, zzz@xxx.xxx'); // multiple
$email->setPriority(1);
$email->setSubject('[XXX] XXX');
$email->setMessage("<head>
    <meta http-equiv='Content-Type' content='text/html; charset=utf-8' />
    <title>send</title>
</head>
<body></body>");
$email->send();

Services/throttler

Using the throttler, you can enforce a limit/quota on webpages, such as only allowing an IP to access a page every X hours.

$throttler = Services::throttler();
// 10 times per day
if ($throttler->check("key", 10, DAY) === false) {
    // more than 10 this day
    return ...;
}

Each time check is called with key, the counter increases by one. You could create a key made of an IP to enforce a quota per client.

Logging

To log a message with a log level:

log_message('info', 'message');
log_message('error', 'message');

πŸ‘» To-do πŸ‘»

Stuff that I found, but never read/used yet.

  • .env
  • Autoload
  • Helpers
  • Session
  • Cache pages. Annoying in debug.
$this->cachePage(360000)
// you can use Controller#attribute
service('router')->methodName()