-
-
Notifications
You must be signed in to change notification settings - Fork 0
Multiple Connections
Models bind to the shared DB facade by default. For models that need a separate connection — a reports database, a sharded tenant store, a read replica — set $credentials on the subclass.
use InitORM\Database\Facade\DB;
DB::createImmutable([
'dsn' => 'mysql:host=primary;dbname=app;charset=utf8mb4',
'username' => 'app',
'password' => 'secret',
]);
class Posts extends \InitORM\ORM\Model
{
protected string $schema = 'posts';
}
(new Posts())->getDatabase() === DB::getDatabase(); // trueEvery model with $credentials = null (the default) shares the same Database instance — which means they all share the same underlying PDO connection and query builder pool.
The shared model is the right default for the common case: a single application connected to a single primary database.
Set $credentials to give a model its own connection:
class ReportsEvents extends \InitORM\ORM\Model
{
protected string $schema = 'events';
protected ?array $credentials = [
'driver' => 'pgsql',
'host' => 'reports.internal',
'database' => 'reports',
'username' => 'reports_ro',
'password' => '…',
];
}Internally, the constructor calls DB::connect($credentials), which builds a fresh Database (and underlying Connection) without touching the shared facade slot.
The $credentials array is passed verbatim to the DBAL Connection constructor. See the Database wiki — Configuration for every supported key (dsn, host, port, database, username, password, charset, collation, driver, options, queryOptions, log, debug, queryLogs).
Each subclass with its own $credentials gets its own Database instance. Two models with identical $credentials arrays still produce two separate connections — there is no de-duplication at the model layer.
class TenantA extends \InitORM\ORM\Model
{
protected ?array $credentials = ['driver' => 'mysql', 'host' => 'tenant-a', /* ... */];
}
class TenantB extends \InitORM\ORM\Model
{
protected ?array $credentials = ['driver' => 'mysql', 'host' => 'tenant-b', /* ... */];
}
// Each (new TenantA) and (new TenantB) builds a fresh connection per instantiation.If you instantiate the same model many times in a request, you'll open many connections. For long-running processes or hot loops, instantiate the model once and reuse it.
If you need shared, named connections (e.g. an injected reports DB used by many models), build the Database objects yourself once and inject them. The cleanest pattern is to override the constructor:
class Reports extends \InitORM\ORM\Model
{
protected string $schema = 'events';
public function __construct(\InitORM\Database\Interfaces\DatabaseInterface $db)
{
// Skip the credentials/facade path by setting $db directly.
// We still call parent::__construct() to run schema-derivation and
// the soft-delete invariant check — but we need to clear the parent's
// $db wire-up. The simplest approach is to set a sentinel after super:
parent::__construct(); // would call DB::getDatabase() — pre-set DB first
// Trick: assign the protected property after parent ran.
$reflection = new \ReflectionProperty(\InitORM\ORM\Model::class, 'db');
$reflection->setValue($this, $db);
}
}This is unusual. The two conventional paths are:
-
One shared facade —
DB::createImmutable()once at boot; every model uses it. -
Per-model
$credentials— subclasses with their own credentials; accept that each instantiation opens a fresh PDO handle.
If your application genuinely needs a small fleet of named connections shared across many models, consider building a tiny "connection registry" on top of the Database layer and skipping Model for those tables.
The two patterns coexist freely. DB::createImmutable() populates the shared slot; DB::connect() (and any model with $credentials) does not touch it:
// Main app:
DB::createImmutable(['dsn' => 'mysql:host=primary;…', /* … */]);
class Posts extends \InitORM\ORM\Model { protected string $schema = 'posts'; }
class ReportsEvents extends \InitORM\ORM\Model
{
protected string $schema = 'events';
protected ?array $credentials = ['driver' => 'pgsql', 'host' => 'reports.internal', /* … */];
}
(new Posts())->read()->rows(); // hits primary
(new ReportsEvents())->read()->rows(); // hits reports.internalDB::createImmutable() deliberately throws if called twice — silent reconfiguration of the application-wide connection is a footgun. To explicitly swap, use DB::replaceImmutable():
DB::replaceImmutable($newDatabase); // pass null to clear the slotYou'll see this in test bases — tearDown() clears the slot so the next test starts clean. See Testing.
Reads go to a replica, writes go to the primary:
class PostsReader extends \InitORM\ORM\Model
{
protected string $schema = 'posts';
protected ?array $credentials = [
'dsn' => 'mysql:host=replica.internal;dbname=app',
'username' => 'app_ro',
'password' => '…',
];
protected bool $writable = false;
protected bool $updatable = false;
protected bool $deletable = false;
}
class PostsWriter extends \InitORM\ORM\Model
{
protected string $schema = 'posts';
// Uses the shared facade (the primary).
protected bool $readable = false;
}
// Reads:
foreach ((new PostsReader())->read()->rows() as $row) { /* … */ }
// Writes:
(new PostsWriter())->create(['title' => 'Hello']);The permission gates ensure callers can't accidentally write through the reader (or read through the writer) — a typo turns into a typed exception instead of a misrouted query.
DB::connect() does not cache. Every call returns a fresh Database, which lazily opens a fresh Connection on its first query. For hot loops, hold the Database (or the Model) in a variable:
// ❌ opens a connection per iteration
foreach ($jobs as $job) {
(new ReportsEvents())->create(['payload' => $job]);
}
// ✅ one connection for the whole loop
$reporter = new ReportsEvents();
foreach ($jobs as $job) {
$reporter->create(['payload' => $job]);
}For persistent connections, pass options[\PDO::ATTR_PERSISTENT] in $credentials. PHP will pool them across requests on the same FPM worker.
-
Defining Models — the
$credentialsproperty reference. - Testing — facade replacement in test setup.
- Database wiki — Multiple Connections — the lower-level pattern this builds on.
InitORM ORM · MIT · maintained by Muhammet ŞAFAK · part of the InitORM stack
Getting Started
Models
Entities
Cross-Cutting
Reference
Upgrading
Project