CodeIgniter4 icon indicating copy to clipboard operation
CodeIgniter4 copied to clipboard

Add setInstance() support for runtime database switching

Open brillsense-shailesh opened this issue 1 month ago • 5 comments

PHP Version

8.3

CodeIgniter4 Version

4.6.1

CodeIgniter4 Installation Method

Composer (using codeigniter4/appstarter)

Which operating systems have you tested for this bug?

Windows

Which server did you use?

apache

Database

MySQL

What happened?

I would like to request a new method in Config\Database that allows developers to set or override a database connection instance at runtime without modifying the framework core.

This is especially useful when a project needs dynamic database switching, such as in:

Multi-tenant SaaS applications

ERP systems with client-specific databases

“Switch DB per request” API requirements

Dynamic “connect with custom credentials” scenarios

Currently, CodeIgniter 4 allows Database::connect($group) but there is no official method to replace or inject a database instance for a given group.

Proposed Method

Add this method in system/Database/Config.php:

public static function setInstance(string $group, array $config) { static::ensureFactory();

$connection = static::$factory->load($config, $group);

static::$instances[$group] = $connection;

return $connection;

}

Why this is needed

Today, Database::connect() always loads config from app/Config/Database.php.

To use a custom connection (hostname, username, password, database), we must create a fake group name or override config objects — both are hacky.

Dynamic DB switching is common in modern SaaS, ERP, and enterprise applications.

This addition would provide a clean, supported API for runtime DB instance control.

Example Use Case $dbConfig = [ 'DSN' => '', 'hostname' => $tenantHost, 'username' => $tenantUser, 'password' => $tenantPass, 'database' => $tenantDb, 'DBDriver' => 'MySQLi', 'DBPrefix' => '', ];

$connection = \Config\Database::setInstance('tenant', $dbConfig);

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

This gives developers the power to switch databases safely and officially.

Backward Compatibility

This method does not change any existing behavior.

It only adds a new optional utility method.

No BC breaks.

Conclusion

This feature will make CodeIgniter 4 significantly more flexible for advanced and enterprise applications that require dynamic DB switching.

Thank you for considering this enhancement! 🙏

Steps to Reproduce

I would like to request a new method in Config\Database that allows developers to set or override a database connection instance at runtime without modifying the framework core.

This is especially useful when a project needs dynamic database switching, such as in:

Multi-tenant SaaS applications

ERP systems with client-specific databases

“Switch DB per request” API requirements

Dynamic “connect with custom credentials” scenarios

Currently, CodeIgniter 4 allows Database::connect($group) but there is no official method to replace or inject a database instance for a given group.

Proposed Method

Add this method in system/Database/Config.php:

public static function setInstance(string $group, array $config) { static::ensureFactory();

$connection = static::$factory->load($config, $group);

static::$instances[$group] = $connection;

return $connection;

}

Why this is needed

Today, Database::connect() always loads config from app/Config/Database.php.

To use a custom connection (hostname, username, password, database), we must create a fake group name or override config objects — both are hacky.

Dynamic DB switching is common in modern SaaS, ERP, and enterprise applications.

This addition would provide a clean, supported API for runtime DB instance control.

Example Use Case $dbConfig = [ 'DSN' => '', 'hostname' => $tenantHost, 'username' => $tenantUser, 'password' => $tenantPass, 'database' => $tenantDb, 'DBDriver' => 'MySQLi', 'DBPrefix' => '', ];

$connection = \Config\Database::setInstance('tenant', $dbConfig);

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

This gives developers the power to switch databases safely and officially.

Backward Compatibility

This method does not change any existing behavior.

It only adds a new optional utility method.

No BC breaks.

Conclusion

This feature will make CodeIgniter 4 significantly more flexible for advanced and enterprise applications that require dynamic DB switching.

Thank you for considering this enhancement! 🙏

Expected Output

I would like to request a new method in Config\Database that allows developers to set or override a database connection instance at runtime without modifying the framework core.

This is especially useful when a project needs dynamic database switching, such as in:

Multi-tenant SaaS applications

ERP systems with client-specific databases

“Switch DB per request” API requirements

Dynamic “connect with custom credentials” scenarios

Currently, CodeIgniter 4 allows Database::connect($group) but there is no official method to replace or inject a database instance for a given group.

Proposed Method

Add this method in system/Database/Config.php:

public static function setInstance(string $group, array $config) { static::ensureFactory();

$connection = static::$factory->load($config, $group);

static::$instances[$group] = $connection;

return $connection;

}

Why this is needed

Today, Database::connect() always loads config from app/Config/Database.php.

To use a custom connection (hostname, username, password, database), we must create a fake group name or override config objects — both are hacky.

Dynamic DB switching is common in modern SaaS, ERP, and enterprise applications.

This addition would provide a clean, supported API for runtime DB instance control.

Example Use Case $dbConfig = [ 'DSN' => '', 'hostname' => $tenantHost, 'username' => $tenantUser, 'password' => $tenantPass, 'database' => $tenantDb, 'DBDriver' => 'MySQLi', 'DBPrefix' => '', ];

$connection = \Config\Database::setInstance('tenant', $dbConfig);

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

This gives developers the power to switch databases safely and officially.

Backward Compatibility

This method does not change any existing behavior.

It only adds a new optional utility method.

No BC breaks.

Conclusion

This feature will make CodeIgniter 4 significantly more flexible for advanced and enterprise applications that require dynamic DB switching.

Thank you for considering this enhancement! 🙏

Anything else?

No response

brillsense-shailesh avatar Nov 28 '25 13:11 brillsense-shailesh

Among the several possible ways to connect to the database you can already specify custom config values when you create a connection, see Connecting with Custom Settings.

The group specifies the connection values already so adding custom values to completely replace all of the values in the configuration array doesn't make sense. I can see it making sense to allow setting override values when specifying the group name, since the most common use case that I can think of is a very large app with multiple databases of the same type. However, I don't know how common that is and if it needs to be in core since it's simple enough for an app handle that override with 2 lines of code, like:

$config = config('database.default');
$db = Database::connect([...config, 'database' => 'app2']);

I'd be interested to see what others have to say, though.

P.S. - maybe take the time to clean up your request - you seem to have multiple versions of the same request back to back....

lonnieezell avatar Nov 28 '25 13:11 lonnieezell

I don't work with frequent DB switching. I don't understand why not use groups. It looks the same

neznaika0 avatar Nov 28 '25 17:11 neznaika0

I have never used a separate database connection per tenant, but are you sure you need to change the entire connection?

I would expect that tenants would share the same server, and just the database name would be changing. In that case, we already have setDatabase() method in the Connection class. Although I can imagine the need for a separate connection due to different credentials per tenant.

Honestly, since you have to load database credentials from somewhere - probably the master database? Then I would consider using Registrars to seed the tenant group array, which would be defined in the config (as an empty one). Then, in your code, you could just use the tenant connection group.

michalsn avatar Nov 28 '25 17:11 michalsn

🔍 Clarification of My Use Case (Why setInstance() Is Needed)

Thank you for the feedback. Let me clarify my scenario, because my requirement cannot be solved by:

  • Using predefined groups
  • Using Database::connect(configArray)
  • Using setDatabase()
  • Using Registrars

My project is a Large-Scale Multi-Tenant SaaS CRM built in CodeIgniter 4 + HMVC with the following structure:

1. Each Application (Module) is a Separate Product

In my CRM, I can create multiple applications/products, and each application has:

  • Its own controllers, models, and routes
  • Its own migration files
  • Its own DB group
  • Its own module structure

Example:

Projects/BrillsenseCRM/City/CityModel.php  
Projects/BrillsenseCRM/Country/CountryModel.php  
Projects/BrillsenseCRM/Customer/CustomerModel.php  
...etc

2. Each Application Has Multiple Clients (Multi-Tenant Per App)

Every client who buys a subscription for a particular application (project) gets their own separate database, not just a different schema name.

Example:

App1_Client_A_DB
App1_Client_B_DB
App1_Client_C_DB

App2_Client_A_DB
App2_Client_B_DB

➡️ Each client = separate database server OR database credentials.

3. DB Groups Cannot Be Predefined

I cannot define fixed groups in app/Config/Database.php because:

  • I don’t know tenant DB credentials at development time
  • Tenants can be created dynamically from CRM
  • Tenants can update DB credentials later
  • One application may have 500+ tenants

Defining 500 DB groups dynamically is not reasonable.

4. Why Current Solution Doesn't Work

“Use Connect With Config Array”

Yes, I can do:

$config = config('database.default');
$db = Database::connect([...$config, 'database' => 'app2']);

But this does not register the connection inside Database::$instances[$group].

My entire project uses:

model('CityModel', false, 'App1TenantClientA');

When switching tenants, I need:

model('CityModel', false, 'tenantGroup')

⚠️ But group must exist in Database::$instances.

Without setInstance(), CI4 will still try to load from Config\Database.php.

“Use setDatabase()”

This only changes the database name inside the same connection.

But in my case:

  • hostname is different
  • username is different
  • password is different
  • and sometimes DBDriver is different

So new connection is mandatory per tenant.

“Use Registrars to seed groups”

Not possible because tenant DB details are generated:

  • after subscription purchase
  • can be modified
  • can be deleted
  • can be added dynamically anytime

I cannot generate groups on boot.


5. Why setInstance() Is Perfect for My Use Case

I want to do:

$dbConfig = [
    'hostname' => $tenantHost,
    'username' => $tenantUser,
    'password' => $tenantPass,
    'database' => $tenantDb,
    'DBDriver' => 'MySQLi',
];

\Config\Database::setInstance('tenant_123', $dbConfig);

// Now models can naturally use the tenant instance
$db = \Config\Database::connect('tenant_123');

This allows:

  • Clean DB switching
  • No hacks
  • No modifying framework core
  • No defining 500+ groups manually
  • Full compatibility with CI4 model system
  • One-line switching of tenants

This mirrors patterns in other modern frameworks (Laravel, Symfony).


6. Summary of Why I Really Need This Feature

  • I run a SaaS with unlimited applications
  • Each application has unlimited clients
  • Each client has a separate database
  • Tenants are dynamic, not known at boot time
  • I need to load runtime credentials
  • Models depend on $group
  • Dynamic group must be registered to Database::$instances

Without setInstance(), I must hack into protected properties or override core classes — which I want to avoid.


🙏 Final Note

My goal is not to replace or conflict with existing features; I only want a clean, framework-supported way to inject database instances at runtime for multitenant SaaS systems.

Thank you for considering this enhancement. I believe many enterprise-level CI4 applications will benefit from this small but powerful feature.

brillsense-shailesh avatar Nov 29 '25 07:11 brillsense-shailesh

Yes, I can do:

$config = config('database.default');
$db = Database::connect([...$config, 'database' => 'app2']);

But this does not register the connection inside Database::$instances[$group].

That is not true. The entry in the database instances is always created. It just has a different naming convention.

I want to do:

$dbConfig = [
    'hostname' => $tenantHost,
    'username' => $tenantUser,
    'password' => $tenantPass,
    'database' => $tenantDb,
    'DBDriver' => 'MySQLi',
];

\Config\Database::setInstance('tenant_123', $dbConfig);

// Now models can naturally use the tenant instance
$db = \Config\Database::connect('tenant_123');

The main problem I have with this approach is that it will not work as expected when we call for a "none shared" instance.

michalsn avatar Nov 29 '25 09:11 michalsn