Skip to main content

Drivers

A Driver does two things: it opens a PDO instance for a config, and it names the transaction grammar its database understands.

interface Driver
{
public function name(): DriverName;

public function transactionGrammar(): TransactionGrammar;

public function connect(ConnectionConfig $config): PDO;
}

Each driver is constructed with its grammar and registered with the factory:

use Dirthara\Database\Connection\Driver\MySqlDriver;
use Dirthara\Database\Connection\Driver\PostgresSqlDriver;
use Dirthara\Database\Connection\Driver\SQLiteDriver;
use Dirthara\Database\Connection\Driver\SqlServerDriver;
use Dirthara\Database\Connection\Transaction\SqlServerTransactionGrammar;
use Dirthara\Database\Connection\Transaction\StandardTransactionGrammar;
use Dirthara\Database\Connection\ValueObjects\SavepointPrefix;

$prefix = new SavepointPrefix();
$standard = new StandardTransactionGrammar($prefix);

$drivers = [
new MySqlDriver($standard),
new PostgresSqlDriver($standard),
new SQLiteDriver($standard),
new SqlServerDriver(new SqlServerTransactionGrammar($prefix)),
];

Register only the drivers you use. A driver you never register costs you a ConnectionException when a config asks for it, not a silent fallback.

DriverName

The enum is backed by PDO's own driver strings, so its values match what PDO::getAvailableDrivers() reports.

CaseValueClass
DriverName::MySqlmysqlMySqlDriver
DriverName::PostgresSqlpgsqlPostgresSqlDriver
DriverName::SqlServersqlsrvSqlServerDriver
DriverName::SQLitesqliteSQLiteDriver

What each driver requires

Driverhostport defaultdatabasecharsetdsn
MySQLRequired3306OptionalDSN, defaults to utf8mb4Appended
PostgreSQLRequired5432OptionalSET client_encodingAppended
SQL ServerRequiredServer defaultOptionalRejectedAppended
SQLiteIgnoredRequiredRejectedRejected

An empty or whitespace-only host counts as missing. A host or database containing a semicolon is refused, because a semicolon separates DSN fields and a value containing one could smuggle in another field.

MySQL

Builds mysql:host=…;port=…;charset=…, appending ;dbname=… when a database is configured. The charset goes into the DSN, which is the only place MySQL applies it to the handshake. Without one, the DSN says utf8mb4.

PostgreSQL

Builds pgsql:host=…;port=…, appending ;dbname=… when a database is configured. A charset is applied after connecting, with SET client_encoding TO '…', because the pgsql DSN has no charset field.

SQL Server

Builds sqlsrv:Server=…, appending ,port to the server when a port is configured and ;Database=… when a database is. SQL Server uses a comma for the port rather than a separate DSN field. It has no default port here; leave port null to use the server's own.

SQLite

Builds sqlite: followed by the database value verbatim — a file path, or :memory: for a throwaway in-memory database. No host, port, username, or password is passed. database is required and must be explicit, so a missing path never quietly becomes an in-memory database whose writes disappear.

Driver-specific options

Three options are not uniform across drivers.

charset is applied where the database actually accepts it: through the DSN on MySQL, through client_encoding on PostgreSQL. SQLite and SQL Server throw a ConnectionException instead of accepting a value they would ignore — SQLite stores text as UTF-8 regardless, and the SQL Server DSN has no equivalent field. A rejected charset is a configuration mistake worth hearing about.

options are PDO attributes and reach PDO's constructor unchanged. They override the defaults each driver sets. See the options array.

dsn parameters are appended to the connection string of every driver that has one. SQLite's DSN is a bare path, so it refuses them. See driver-specific DSN parameters.

Writing a driver

Extend PdoDriver rather than implementing Driver directly. It handles the work that is the same everywhere: wrapping PDOException in a ConnectionException with context, merging default PDO options, and validating DSN fragments. Implement name() and createConnection().

final class MariaDbDriver extends PdoDriver
{
public function name(): DriverName
{
return DriverName::MySql;
}

protected function createConnection(ConnectionConfig $config): PDO
{
$dsn = 'mysql:host=' . $this->requireHost($config)->value;

return new PDO($dsn, $config->username, $config->password, $this->options($config));
}
}

The protected helpers available to a subclass:

HelperPurpose
options($config)Default PDO attributes merged with the config's, config winning. Override to add your own defaults.
requireHost($config)The host as a validated DsnValue, throwing when it is empty or contains a semicolon.
optionalDatabase($config)The database as a validated DsnValue, or null when it is absent.
charset($config)The charset as a validated Charset, or null when it is absent.
rejectCharset($config)Throws when a charset is configured. For databases that have no place to put one.
dsnParameters($config)The configured driver-specific parameters as a ;Name=Value string, with both halves validated. Append it last.
rejectDsnParameters($config)Throws when any is configured. For a DSN with no Key=Value syntax.
context($config, $operation, $cause)Exception context: config diagnostics, the operation, and the SQLSTATE and driver code of a PDOException.

Only createConnection() needs to run; connect() already catches PDOException around it and rethrows it as a ConnectionException carrying the message, SQLSTATE, and driver error code.