Skip to content

Add setInstance() support for runtime database switching #9812

Open

Description

@brillsense-shailesh

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

Activity

  1. added
    bugVerified issues on the current code behavior or pull requests that will fix them
    on Nov 28, 2025
  2. lonnieezell commented on Nov 28, 2025

    @lonnieezell
    Member

    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....

  3. neznaika0 commented on Nov 28, 2025

    @neznaika0
    Contributor

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

  4. changed the title [-]Bug: . 🚀 Feature Request: Add setInstance() support for runtime database switching in CodeIgniter 4[/-] [+]Add setInstance() support for runtime database switching[/+] on Nov 28, 2025
  5. added
    enhancementPRs that improve existing functionalities
    and removed
    bugVerified issues on the current code behavior or pull requests that will fix them
    on Nov 28, 2025
  6. michalsn commented on Nov 28, 2025

    @michalsn
    Member

    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.

  7. brillsense-shailesh commented on Nov 29, 2025

    @brillsense-shailesh
    Author

    🔍 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.

  8. michalsn commented on Nov 29, 2025

    @michalsn
    Member

    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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementPRs that improve existing functionalities

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions