Versioning Your 100+ Controllers in CI3 Without Changing a Single Route

Introduction
Ever faced this?
"How can I refactor 100+ controllers in a live CI3 app without touching routes or breaking frontend code?"
This was my reality on a legacy CodeIgniter 3 project. I had to add new features and restructure logic without breaking thousands of AJAX/API calls already in production.
Hereβs how I did it using custom router logic and a versioned controller directory, all without touching the routes file.
π§ The Problem
I had to:
β¨ Refactor 100+ controller methods
β Add new features
π Preserve all existing frontend AJAX/API endpoints
But without:
- β Editing 1000+ frontend calls
- β Manually redirecting each old method
- β Renaming existing controller files
I needed a smarter, safer way to version my logic β just like APIs, but at the controller level.
β What I Didnβt Want to Do
Update every route or endpoint manually
Add
redirect()
logic inside hundreds of methodsRename controllers and risk breaking everything
Too risky. Too time-consuming. Too boring.
β The Solution: Router-Level Controller Versioning
I created a localized/
folder inside the controllers/
directory and duplicated all controllers that needed changes into it. Then, I modified the router to check this versioned folder first.
ποΈ Folder Structure
Before:
application/
βββ controllers/
βββ Dashboard.php
βββ Automation.php
βββ ... (100+ controllers)
After:
application/
βββ controllers/
βββ Dashboard.php
βββ Automation.php
βββ localized/
βββ Dashboard.php
βββ Automation.php
βββ ... (100+ controllers)
π οΈ Custom Router (MY_Router.php)
Inside application/core/
, I created MY_Router.php
and added the following:
<?php defined('BASEPATH') OR exit('No direct script access allowed');
class MY_Router extends CI_Router {
protected function _validate_request($segments) {
if (empty($segments)) {
return $segments;
}
// Get controller segments (ignore method name)
$controller_segments = $segments;
if (count($segments) >= 2) {
array_pop($controller_segments);
}
// Build the file path for the versioned controller
$localized_file = APPPATH . 'controllers/localized/' . implode('/', $controller_segments) . '.php';
// If versioned controller exists, load it
if (file_exists($localized_file)) {
array_unshift($segments, 'localized');
log_message('debug', 'Using localized controller: '.$localized_file);
} else {
log_message('debug', 'Localized controller not found: '.$localized_file);
}
return parent::_validate_request($segments);
}
}
π How It Works
π CI3 checks if the controller exists in the `localized/` folder
β If yes, it uses the updated controller
π If no, it falls back to the original
π‘οΈ No route changes required β the frontend still uses the same URLs
π§ͺ Real-World Impact
βοΈ Easy rollout of new features controller-by-controller
π Seamless fallback to old code during testing
π« Zero downtime or broken frontend calls
π Safer QA and debugging in production
β±οΈ No need to touch thousands of routes or AJAX calls
π§° Tech Stack Used
PHP (CodeIgniter 3)
MySQL
jQuery & AJAX frontend
Webhook/Event-based triggers
π Lessons Learned
CI3 may be old, but it's still flexible when used smartly
Versioning isn't just for APIs β it's great for controllers too
A clean router design can help prevent future tech debt
Avoiding unnecessary redirects reduces debugging headaches
π Final Thoughts
If you're working on a large CI3 codebase and need to modernize without disrupting the existing system, this router-based controller versioning approach could save you hours β or even days β of work.
Itβs simple, non-intrusive, and gives you a safe playground for upgrades while keeping production stable.
Happy coding! π
βοΈ Let me know in the comments if youβve faced similar challenges β or if youβve found other clever ways to handle large-scale controller refactoring in CI3!
Subscribe to my newsletter
Read articles from Rutvi Dhameliya directly inside your inbox. Subscribe to the newsletter, and don't miss out.
Written by
