P
Recherches récentes

CodeIgniter 4: our choice, our reasons

20 vues

Choosing a framework is a commitment

Choosing a framework isn't an architectural detail: it's a multi-year commitment. You live with its strengths and quirks at every line of code, every version bump, every hire. Best to choose it with your eyes open.

When we laid the foundations of Punky Tools, we chose CodeIgniter 4. Not out of habit, not out of fashion — out of conviction. And three years later, it's still the backbone of everything we build.

Here are our reasons, no spin: readability, restraint, the numbers, comparisons with the PHP heavyweights, concrete code — and, honestly, what it costs too.

Readability over hype

Many PHP frameworks pile up layers of abstraction until you no longer know what's really happening. Injection containers, facades, magic events, dynamic resolution… It's powerful, but when a bug shows up, you sometimes dig ten levels before hitting the ground.

CI4 makes the opposite bet: a light, explicit core you understand just by reading the code. A call looks like a call. A route points to a controller, with no hidden middleman. When a problem arises, you walk the stack without getting lost.

This isn't a comfort detail: it's what makes a codebase maintainable over time, and handed-over. A developer joining the project reads the code and understands — no cathedral of abstractions to decode before being productive.

The explicit ages better than the magic. The magic impresses; the explicit gets fixed at three in the morning.

A lightweight core: the numbers

CI4 is under the MIT license — free, no subscription, no licensing trap. Its initial version 4 dates to February 2020; we're now on the 4.7 branch (released in early 2026), which represents six years of maturing without brutal breakage. Reassuring for a product you maintain over time.

On the runtime side: PHP 8.2+ required (and up to PHP 8.5 supported), which lets you benefit from the JIT, config caching, file locator caching and preloading — all CPU gains in production.

Above all, where a "complete" ecosystem pulls in dozens of transitive Composer packages at install time, the CI4 core makes do with a handful of dependencies. Less third-party code means less attack surface, fewer version conflicts, and a faster startup. The official motto doesn't lie: "the small framework with powerful features".

Concretely, on a classic OVH/Plesk server, this restraint translates into a contained memory footprint and a short bootstrap time — without needing oversized infrastructure.

Routing: clear and explicit

Routing sets the tone. In CI4, you declare your routes by hand, and you read the routing table like a table of contents for the application. No obscure implicit discovery: what's routed is written.

Routes.php
$routes->get('articles', 'ArticleController::index');
$routes->get('articles/(:segment)', 'ArticleController::show/$1');
$routes->post('articles', 'ArticleController::create');

// Group protected by a filter
$routes->group('starshield', ['filter' => 'session'], function ($routes) {
    $routes->resource('posts');
});

Typed placeholders ((:segment), (:num)…) keep URLs clean, groups apply a prefix and a filter in one go, and route order is controlled — specific routes before catch-alls. It's crystal clear, and it debugs at a glance.

This explicitness has a minimal cost when writing, largely offset by clarity when reading back. You always know why a URL leads somewhere.

The lightweight ORM: Model + Query Builder

CI4 doesn't ship a monumental Doctrine-style ORM, nor the omnipresence of an Active Record that hides everything. It offers a simple Model and a Query Builder that stays close to SQL — without losing control of what actually goes to the database.

ArticleModel.php
class ArticleModel extends Model
{
    protected $table         = 'articles';
    protected $primaryKey    = 'id';
    protected $allowedFields = ['title', 'slug', 'body', 'status'];
    protected $useTimestamps = true;
}

// Query Builder: readable, close to SQL
$articles = $model
    ->where('status', 'published')
    ->orderBy('published_at', 'DESC')
    ->findAll(10);

You keep the readability of a chained query, but you can always drop down to raw SQL when it's warranted. No hidden performance surprise behind a "lazy" relation that fires a hundred queries without warning.

For our needs — multilingual, relations, translations — we built our own traits on top (joins, HasTranslations…). CI4's lightweight Model lets itself be extended without fighting it.

Migrations and the schema

The database is versioned like the code. CI4 migrations, driven by the Forge class, describe the schema in PHP: you create, you modify, you roll back, and everyone works on the same structure.

Migration_Articles.php
public function up()
{
    $this->forge->addField([
        'id'           => ['type' => 'INT', 'unsigned' => true, 'auto_increment' => true],
        'title'        => ['type' => 'VARCHAR', 'constraint' => 255],
        'slug'         => ['type' => 'VARCHAR', 'constraint' => 255],
        'published_at' => ['type' => 'DATETIME', 'null' => true],
    ]);
    $this->forge->addKey('id', true);
    $this->forge->createTable('articles');
}

Combined with seeders for the initial data, migrations make a new environment reproducible in a single Spark command. No more "works on my machine": the structure is in the repository, period.

It's an example of the CI4 philosophy: a simple tool that does one thing well, without imposing a rigid workflow around it.

Shield: authentication, without reinventing it

Authentication is exactly the kind of brick you really don't want to write yourself — too sensitive, too many traps. CI4 has its official answer: Shield, maintained by the framework team.

auth.php
// An area reserved for logged-in users
$routes->group('starshield', ['filter' => 'session'], function ($routes) {
    // Punky back-office
});

// Check a permission in a controller
if (! auth()->user()->can('posts.edit')) {
    return redirect()->back()->with('error', 'Access denied');
}

Sessions, API tokens, group and permission management, security guardrails: Shield covers the essentials cleanly. With us, the whole back-office lives behind the starshield prefix, protected by the session filter.

It's exactly the balance we're after: an official, solid brick for what must be — and the freedom to code the rest ourselves.

Tasks & Queue: clean async

Not everything should happen while the visitor waits. Sending emails, generating thumbnails, syncing a catalog, generating a sitemap… that goes to a background task.

queue.php
// Enqueue an async job
service('queue')->push('emails', SendNewsletter::class, [
    'campaignId' => $id,
]);

// The worker runs via Spark:
// php spark queue:work emails

CI4 provides Tasks (cron-style scheduling) and a Queue for async jobs. You enqueue, a worker dequeues: the visitor's request stays fast, and the heavy work happens elsewhere.

We just learned one subtlety: running a Spark command from an HTTP/FPM context takes a bit of care (PHP doesn't define the CLI streams there). Once the pattern is in place, it's all good.

Performance and restraint

A light framework means a fast startup and a contained memory footprint. On classic OVH/Plesk servers — not sprawling clusters — that makes all the difference between a snappy page and one that drags.

cache.php
$menu = cache('menu:main');

if ($menu === null) {
    $menu = $this->navModel->tree();
    cache()->save('menu:main', $menu, 3600); // 1 h
}

return $menu;

Paired with Redis and real cache discipline, CI4 handles the load without oversized infrastructure. You memoize what's expensive but stable, you invalidate finely, and the database breathes.

On top of that come PHP 8.2+'s native optimizations (JIT, preloading) and the framework's internal caches (config, file locator). Restraint isn't just a stance: it's measured in milliseconds and server bills.

CI4 vs Laravel and Symfony

Let's be clear: Laravel is excellent. Its ecosystem is huge, its productivity immediate, its community enormous. Symfony is an engineering benchmark for complex enterprise applications. The "best" framework doesn't exist in the absolute — only the one best suited to a context.

CriterionCodeIgniter 4LaravelSymfony
Approachminimalist, explicitfull-stack, conventionsmodular (bundles)
ORMQuery Builder + lightweight ModelEloquent (Active Record)Doctrine (Data Mapper)
Container / injectionexplicit services (service())IoC container + facadesDI container + autowiring
Composer dependenciesa handfuldozens (transitive)dozens of components
Template enginePHP views / BladeOneBladeTwig
Built-in scoperouting, migrations, validationqueue, mail, broadcasting, cache…kernel + bundles on demand
Learning curvefastmediumsteep
Footprint / bootstrapfaibleheaviervaries by config
Ideal for…control, long maintenanceproductivity, big ecosystemcomplex enterprise apps

Laravel brings a huge functional scope right away — at the price of deeper abstraction (facades, IoC container) and dozens of transitive dependencies. Symfony offers incomparable modularity and engineering rigor (DI container, autowiring, bundles) — at the price of a steep learning curve. CI4 aims at another balance point: a readable Query Builder rather than a heavy ORM, explicit services rather than an omnipresent container, enough structure not to reinvent the wheel, enough lightness to stay master of the code.

For a product whose every line we control and that we keep alive for years, this balance point is exactly the one we wanted.

And against micro-frameworks?

At the other extreme are the micro-frameworks (Slim-style) or "bare PHP". Even lighter, even freer — but then you have to build everything: structured routing, validation, migrations, security, conventions.

CI4 sits right in the sweet spot: it gives a backbone (structure, conventions, essential tools) without the weight of a full ecosystem. You don't start from scratch on every project, and you don't drag a gas factory around either.

It's that "happy medium" that clinched the decision: the freedom of a small framework, with the guardrails of a big one.

How we extend it: our Punky modules

CI4's lightness pays off most when you extend it. All of Punky Tools is split into self-contained modules, on the Punky\{Module} namespace, that plug into the core without modifying it.

Module.php
namespace Punky\Blog;

class Module extends PunkyBaseModule
{
    // A module declares its routes, its admin menu
    // and its providers (sitemap, search…)
    public function getSitemapProviders(): array
    {
        return [new PostSitemapProvider()];
    }
}

Each module declares its routes, its admin menu, its providers (sitemap, search…) via auto-discovery. Adding a feature means adding a module — not patching the core. The model faithfully follows CI4's module logic.

The result: we reuse the same architecture from one client site to another, and we capitalize on it. A new need becomes a new brick, which will serve again elsewhere.

The flip side

Because there is one, and we'd rather say so. CI4 requires you to build more yourself: fewer ready-made "all-in-one" packages than in the Laravel ecosystem, a smaller community, fewer tutorials for every twisted case.

For us, that's an advantage: we master what we write, we don't inherit behaviors we didn't choose, and we know exactly what runs in production. Control has a cost, and we gladly pay it.

But let's be fair: for a team that wants immediate turnkey, shipping fast with as many ready-made bricks as possible, this "build it yourself" will be a cost, not a pleasure. The right tool depends on what you value: speed of departure, or control over time.

There's also a team factor: the pool of Laravel developers is larger. We own that — we prefer people who like to understand the code over people looking for a ready-made recipe.

A foundation that hasn't let us down

In three years, we've gone through several version upgrades (up to 4.7) with no painful rewrite. Behavior changes are documented, deprecations announced, and backward compatibility taken seriously — which, for a product you maintain, is worth gold.

We even ran into occasional regressions (a CSRF filter that encoded JSON bodies, for instance) — but because the core is readable, we diagnosed and worked around them fast. That's the whole point of code you can read: even its bugs are understandable.

Security and maintenance follow PHP's pace: the framework drops end-of-life PHP versions and pushes you to stay up to date. A healthy constraint, which forces good hygiene.

Three years later

We made our choice with full knowledge: a readable, lean framework, with no black magic, that we extend by modules and master end to end. We know its strengths, and we accept the downside.

And three years later, with Punky Tools in production on client sites and the agency itself, we'd do it again without hesitation. The backbone holds.

Light, readable, no black magic. The best framework is the one whose every line you understand.

Partager