-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
A walk through the package — what classes exist, why they exist, and
how a single $db->query(...) call flows through them.
┌────────────────────────────────────────────────────────────┐
│ your code │
└──────────────┬─────────────────────────────────────────────┘
│
▼
┌────────────────────────────────────────────────────────────┐
│ Connection ←─ logging, credentials, │
│ ├── DsnBuilder DSN building, lifecycle│
│ ├── QueryLogger │
│ └── Logger (PSR-3 / callable / file / duck) │
└──────────────┬─────────────────────────────────────────────┘
│ prepare($sql, $options)
▼
┌────────────────────────────────────────────────────────────┐
│ PDO │
└──────────────┬─────────────────────────────────────────────┘
│ returns PDOStatement
▼
┌────────────────────────────────────────────────────────────┐
│ DataMapperFactory ──► DataMapper (wraps PDOStatement) │
│ ├── bind / bindValue / bindValues │
│ └── asAssoc / asObject / asClass /│
│ asLazy / asArray / row / rows │
└────────────────────────────────────────────────────────────┘
src/
├── Connection/
│ ├── Connection.php
│ ├── ConnectionFactory.php
│ ├── Exceptions/
│ │ ├── ConnectionException.php
│ │ ├── ConnectionInvalidArgumentException.php
│ │ ├── SQLExecuteException.php
│ │ ├── ConnectionAlreadyEstablishedException.php
│ │ └── ValidConnectionAvailableException.php [deprecated alias]
│ ├── Interfaces/
│ │ ├── ConnectionInterface.php [aggregate]
│ │ ├── ConfigurableConnectionInterface.php
│ │ ├── LoggableConnectionInterface.php
│ │ └── ConnectionFactoryInterface.php
│ └── Support/
│ ├── DsnBuilder.php ← driver-aware DSN composer
│ ├── QueryLogger.php ← in-memory query log buffer
│ └── Logger.php ← critical-log dispatcher
│
└── DataMapper/
├── DataMapper.php
├── DataMapperFactory.php
├── Exceptions/
│ ├── DataMapperException.php
│ └── DataMapperInvalidArgumentException.php
└── Interfaces/
├── DataMapperInterface.php
└── DataMapperFactoryInterface.php
ConnectionInterface is the public face, but it deliberately extends
two narrower contracts so callers can depend on only what they use:
| Interface | Responsibility |
|---|---|
ConfigurableConnectionInterface |
Credentials accessors (setHost, setDatabase, …) |
LoggableConnectionInterface |
Query log + critical log surface |
ConnectionInterface |
Lifecycle (getPDO, connect, disconnect) + query()
|
A consumer that only needs to run queries can type-hint
ConnectionInterface. A consumer that only configures connections
can type-hint ConfigurableConnectionInterface and never see the
query surface.
Both Connection and DataMapper use __call to forward unknown
methods. The rule is the same in both:
public function __call(string $name, array $arguments)
{
$result = $this->target->{$name}(...$arguments);
return $result instanceof TargetClass ? $this : $result;
}Where TargetClass is PDO for Connection, and PDOStatement for
DataMapper. This rewrite is important — it keeps fluent chains
working across the wrapper boundary:
$db->beginTransaction() // PDO returns $this (PDO) → we re-wrap to Connection
->lastInsertId(); // OK to call on ConnectionWithout the rewrite, the second call would land on the bare PDO instance and break the wrapper's logging / lifecycle hooks.
$db->query('SELECT id FROM users WHERE id = :id', ['id' => 1]);
├─ microtime(true) ← startTime
├─ $options + $queryOptions ← merge with +, not array_merge
├─ getPDO() ← connects lazily on first call
│ ├─ new PDO($dsn, $u, $p, $opts)
│ ├─ applyCharsetAndCollation() ← MySQL only
│ └─ fill missing 'driver' from PDO
│
├─ $pdo->prepare($sql, $options) ← may throw PDOException
├─ dataMapperFactory->createDataMapper($stmt)
├─ $mapper->bindValues($params) ← per-value type via bind()
├─ $mapper->execute() ← may throw PDOException
│
├─ queryLogger->add($sql, $params, startTime)
└─ return $mapper
catch Throwable:
queryLogger->add(...)
logger->write(failure message, $params)
throw original exception
Key points:
-
Lazy connect. No PDO is created until the first call that needs
it. Constructing a
Connectionis essentially free. - Logging on both paths. A successful query and a failing query both produce a log entry (when enabled).
-
Exception passthrough. The library does not transform PDO
exceptions on the success path. They reach your
catchblock in their original form.
| Concern | Default | Where |
|---|---|---|
| Driver | mysql |
Connection::$credentials |
| Host / port |
127.0.0.1 / 3306
|
Connection::$credentials |
| Charset | utf8mb4 |
Connection::$credentials |
ATTR_EMULATE_PREPARES |
false |
Connection::getOptions() |
ATTR_PERSISTENT |
false |
Connection::getOptions() |
ATTR_ERRMODE |
PDO::ERRMODE_EXCEPTION |
Connection::getOptions() |
ATTR_DEFAULT_FETCH_MODE |
PDO::FETCH_ASSOC |
Connection::getOptions() |
-
No query builder. That is a separate package
(
initorm/query-builder). DBAL only runs strings. - No connection pool. PHP's lifecycle is per-request; pooling belongs to a proxy (ProxySQL, PgBouncer) or a persistent runtime (Swoole) — not to a library.
-
No reflection-driven hydration.
asClass()uses nativePDO::FETCH_CLASS. If you need richer hydration, drop down togetStatement()and use any mapper you like. -
Driver-aware but driver-agnostic on the surface.
DsnBuilderknows MySQL, PostgreSQL, and SQLite; the public API does not change per driver.
-
Entry point —
src/Connection/Connection.php -
query()itself — search forpublic function query( -
__callforwarding — search forpublic function __call( -
DSN building —
src/Connection/Support/DsnBuilder.php -
Critical log dispatch —
src/Connection/Support/Logger.php -
Result extraction —
src/DataMapper/DataMapper.php
- Factories and DI for replacing internal collaborators.
- Testing Your Application for patterns that exercise this architecture from the outside.
InitORM DBAL · MIT · maintained by Muhammet ŞAFAK · part of the InitORM stack
Getting Started
Core
Cross-Cutting
Reference
Upgrading
Project