Code Igniter
Code Igniter is a simple and easy-to-learn PHP framework following the Model-View-Controller (MVC) architectural pattern.
- Documentation (βͺ)
- GitHub (4.3k β)
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 thepublic
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
istrue
- 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()