Skip to content

Commit aab2ae9

Browse files
committed
feat(database): Add write/read database configuration
1 parent 53e20d0 commit aab2ae9

7 files changed

Lines changed: 502 additions & 131 deletions

File tree

src/Database/Connection/AbstractConnection.php

Lines changed: 138 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,37 +31,157 @@ abstract class AbstractConnection
3131
protected int $fetch = PDO::FETCH_OBJ;
3232

3333
/**
34-
* The PDO instance
34+
* The write (primary) PDO instance
3535
*
36-
* @var PDO
36+
* @var ?PDO
3737
*/
38-
protected PDO $pdo;
38+
protected ?PDO $write_pdo = null;
3939

4040
/**
41-
* Create an instance of the PDO
41+
* The read (replica) PDO instance
42+
*
43+
* @var ?PDO
44+
*/
45+
protected ?PDO $read_pdo = null;
46+
47+
/**
48+
* The configuration used to build the write connection
49+
*
50+
* @var array
51+
*/
52+
protected array $write_config = [];
53+
54+
/**
55+
* The configuration used to build the read connection,
56+
* or null when the connection is not split (reads use write).
57+
*
58+
* @var ?array
59+
*/
60+
protected ?array $read_config = null;
61+
62+
/**
63+
* AbstractConnection constructor.
64+
*
65+
* Splits the connection configuration into a write (primary)
66+
* configuration and an optional read (replica) configuration.
67+
*
68+
* @param array $config
69+
*/
70+
public function __construct(array $config)
71+
{
72+
$this->config = $config;
73+
74+
$this->write_config = $config;
75+
unset($this->write_config['read']);
76+
77+
if (isset($config['read']) && is_array($config['read'])) {
78+
$this->read_config = array_merge($this->write_config, $config['read']);
79+
} else {
80+
$this->read_config = null;
81+
}
82+
83+
// Validate eagerly so misconfiguration fails fast, while the
84+
// connection itself is still established lazily on first use.
85+
$this->validateConfig($this->write_config);
86+
87+
if ($this->read_config !== null) {
88+
$this->validateConfig($this->read_config);
89+
}
90+
}
91+
92+
/**
93+
* Validate the connection configuration.
94+
*
95+
* @param array $config
96+
* @return void
97+
*/
98+
abstract protected function validateConfig(array $config): void;
99+
100+
/**
101+
* Build a PDO instance from the given configuration.
102+
*
103+
* @param array $config
104+
* @return PDO
105+
*/
106+
abstract protected function makePdo(array $config): PDO;
107+
108+
/**
109+
* Build (eagerly) the write connection.
110+
*
111+
* Kept for backward compatibility with callers that expect to
112+
* (re)establish the connection explicitly.
42113
*
43114
* @return void
44115
*/
45-
abstract public function connection(): void;
116+
public function connection(): void
117+
{
118+
$this->write_pdo = $this->makePdo($this->write_config);
119+
}
46120

47121
/**
48-
* Retrieves the connection
122+
* Retrieves the connection (the write/primary connection)
49123
*
50124
* @return PDO
51125
*/
52126
public function getConnection(): PDO
53127
{
54-
return $this->pdo;
128+
return $this->getWriteConnection();
129+
}
130+
131+
/**
132+
* Retrieves the write (primary) connection, building it lazily
133+
*
134+
* @return PDO
135+
*/
136+
public function getWriteConnection(): PDO
137+
{
138+
if ($this->write_pdo === null) {
139+
$this->write_pdo = $this->makePdo($this->write_config);
140+
}
141+
142+
return $this->write_pdo;
55143
}
56144

57145
/**
58-
* Set the connection
146+
* Retrieves the read (replica) connection, building it lazily.
147+
*
148+
* Falls back to the write connection when the connection is not split.
149+
*
150+
* @return PDO
151+
*/
152+
public function getReadConnection(): PDO
153+
{
154+
if ($this->read_config === null) {
155+
return $this->getWriteConnection();
156+
}
157+
158+
if ($this->read_pdo === null) {
159+
$this->read_pdo = $this->makePdo($this->read_config);
160+
}
161+
162+
return $this->read_pdo;
163+
}
164+
165+
/**
166+
* Whether the write connection has already been established.
167+
*
168+
* Used to inspect transaction state without forcing a connection open.
169+
*
170+
* @return bool
171+
*/
172+
public function hasWriteConnection(): bool
173+
{
174+
return $this->write_pdo instanceof PDO;
175+
}
176+
177+
/**
178+
* Set the connection (the write/primary connection)
59179
*
60180
* @param PDO $pdo
61181
*/
62182
public function setConnection(PDO $pdo): void
63183
{
64-
$this->pdo = $pdo;
184+
$this->write_pdo = $pdo;
65185
}
66186

67187
/**
@@ -84,10 +204,14 @@ public function setFetchMode(int $fetch): void
84204
{
85205
$this->fetch = $fetch;
86206

87-
$this->pdo->setAttribute(
88-
PDO::ATTR_DEFAULT_FETCH_MODE,
89-
$fetch
90-
);
207+
foreach ([$this->write_pdo, $this->read_pdo] as $pdo) {
208+
if ($pdo instanceof PDO) {
209+
$pdo->setAttribute(
210+
PDO::ATTR_DEFAULT_FETCH_MODE,
211+
$fetch
212+
);
213+
}
214+
}
91215
}
92216

93217
/**
@@ -137,7 +261,7 @@ public function getCollation(): string
137261
*/
138262
public function getPdoDriver(): string
139263
{
140-
return $this->pdo->getAttribute(PDO::ATTR_DRIVER_NAME);
264+
return $this->getConnection()->getAttribute(PDO::ATTR_DRIVER_NAME);
141265
}
142266

143267
/**

src/Database/Connection/Adapters/MysqlAdapter.php

Lines changed: 22 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -25,53 +25,51 @@ class MysqlAdapter extends AbstractConnection
2525
protected ?string $name = 'mysql';
2626

2727
/**
28-
* MysqlAdapter constructor.
28+
* Validate the connection configuration.
2929
*
30-
* @param array $config
30+
* @param array $config
31+
* @return void
3132
*/
32-
public function __construct(array $config)
33+
protected function validateConfig(array $config): void
3334
{
34-
$this->config = $config;
35-
36-
$this->connection();
35+
// Check the existence of database definition
36+
if (!isset($config['database'])) {
37+
throw new InvalidArgumentException("The database is not defined");
38+
}
3739
}
3840

3941
/**
40-
* Make connexion
42+
* Build a PDO instance from the given configuration.
4143
*
42-
* @return void
44+
* @param array $config
45+
* @return PDO
4346
*/
44-
public function connection(): void
47+
protected function makePdo(array $config): PDO
4548
{
4649
// Build of the mysql dsn
47-
if (isset($this->config['socket']) && !empty($this->config['socket'])) {
48-
$hostname = $this->config['socket'];
50+
if (isset($config['socket']) && !empty($config['socket'])) {
51+
$hostname = $config['socket'];
4952
$port = '';
5053
} else {
51-
$hostname = $this->config['hostname'] ?? null;
52-
$port = (string)($this->config['port'] ?? self::PORT);
53-
}
54-
55-
// Check the existence of database definition
56-
if (!isset($this->config['database'])) {
57-
throw new InvalidArgumentException("The database is not defined");
54+
$hostname = $config['hostname'] ?? null;
55+
$port = (string)($config['port'] ?? self::PORT);
5856
}
5957

6058
// Formatting connection parameters
61-
$dsn = sprintf("mysql:host=%s;port=%s;dbname=%s", $hostname, $port, $this->config['database']);
59+
$dsn = sprintf("mysql:host=%s;port=%s;dbname=%s", $hostname, $port, $config['database']);
6260

63-
$username = $this->config["username"];
64-
$password = $this->config["password"];
61+
$username = $config["username"];
62+
$password = $config["password"];
6563

6664
// Configuration the PDO attributes that we want to set
6765
$options = [
68-
PDO::ATTR_DEFAULT_FETCH_MODE => $this->config['fetch'] ?? $this->fetch,
66+
PDO::ATTR_DEFAULT_FETCH_MODE => $config['fetch'] ?? $this->fetch,
6967
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
70-
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES " . Str::upper($this->config["charset"]),
68+
PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES " . Str::upper($config["charset"]),
7169
PDO::ATTR_ORACLE_NULLS => PDO::NULL_EMPTY_STRING
7270
];
7371

7472
// Build the PDO connection
75-
$this->pdo = new PDO($dsn, $username, $password, $options);
73+
return new PDO($dsn, $username, $password, $options);
7674
}
7775
}

src/Database/Connection/Adapters/PostgreSQLAdapter.php

Lines changed: 38 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -24,79 +24,79 @@ class PostgreSQLAdapter extends AbstractConnection
2424
protected ?string $name = 'pgsql';
2525

2626
/**
27-
* MysqlAdapter constructor.
27+
* Validate the connection configuration.
2828
*
29-
* @param array $config
29+
* @param array $config
30+
* @return void
3031
*/
31-
public function __construct(array $config)
32+
protected function validateConfig(array $config): void
3233
{
33-
$this->config = $config;
34-
35-
$this->connection();
34+
// Check the existence of database definition
35+
if (!isset($config['database'])) {
36+
throw new InvalidArgumentException("The database is not defined");
37+
}
3638
}
3739

3840
/**
39-
* Make connexion
41+
* Build a PDO instance from the given configuration.
4042
*
41-
* @return void
43+
* @param array $config
44+
* @return PDO
4245
*/
43-
public function connection(): void
46+
protected function makePdo(array $config): PDO
4447
{
45-
// Build of the mysql dsn
46-
if (isset($this->config['socket']) && !is_null($this->config['socket']) && !empty($this->config['socket'])) {
47-
$hostname = $this->config['socket'];
48+
// Build of the pgsql dsn
49+
if (isset($config['socket']) && !is_null($config['socket']) && !empty($config['socket'])) {
50+
$hostname = $config['socket'];
4851
$port = '';
4952
} else {
50-
$hostname = $this->config['hostname'] ?? null;
51-
$port = (string)($this->config['port'] ?? self::PORT);
52-
}
53-
54-
// Check the existence of database definition
55-
if (!isset($this->config['database'])) {
56-
throw new InvalidArgumentException("The database is not defined");
53+
$hostname = $config['hostname'] ?? null;
54+
$port = (string)($config['port'] ?? self::PORT);
5755
}
5856

5957
// Formatting connection parameters
60-
$dsn = sprintf("pgsql:host=%s;port=%s;dbname=%s", $hostname, $port, $this->config['database']);
58+
$dsn = sprintf("pgsql:host=%s;port=%s;dbname=%s", $hostname, $port, $config['database']);
6159

62-
if (isset($this->config['sslmode'])) {
63-
$dsn .= ';sslmode=' . $this->config['sslmode'];
60+
if (isset($config['sslmode'])) {
61+
$dsn .= ';sslmode=' . $config['sslmode'];
6462
}
6563

66-
if (isset($this->config['sslrootcert'])) {
67-
$dsn .= ';sslrootcert=' . $this->config['sslrootcert'];
64+
if (isset($config['sslrootcert'])) {
65+
$dsn .= ';sslrootcert=' . $config['sslrootcert'];
6866
}
6967

70-
if (isset($this->config['sslcert'])) {
71-
$dsn .= ';sslcert=' . $this->config['sslcert'];
68+
if (isset($config['sslcert'])) {
69+
$dsn .= ';sslcert=' . $config['sslcert'];
7270
}
7371

74-
if (isset($this->config['sslkey'])) {
75-
$dsn .= ';sslkey=' . $this->config['sslkey'];
72+
if (isset($config['sslkey'])) {
73+
$dsn .= ';sslkey=' . $config['sslkey'];
7674
}
7775

78-
if (isset($this->config['sslcrl'])) {
79-
$dsn .= ';sslcrl=' . $this->config['sslcrl'];
76+
if (isset($config['sslcrl'])) {
77+
$dsn .= ';sslcrl=' . $config['sslcrl'];
8078
}
8179

82-
if (isset($this->config['application_name'])) {
83-
$dsn .= ';application_name=' . $this->config['application_name'];
80+
if (isset($config['application_name'])) {
81+
$dsn .= ';application_name=' . $config['application_name'];
8482
}
8583

86-
$username = $this->config["username"];
87-
$password = $this->config["password"];
84+
$username = $config["username"];
85+
$password = $config["password"];
8886

8987
// Configuration the PDO attributes that we want to set
9088
$options = [
91-
PDO::ATTR_DEFAULT_FETCH_MODE => $this->config['fetch'] ?? $this->fetch,
89+
PDO::ATTR_DEFAULT_FETCH_MODE => $config['fetch'] ?? $this->fetch,
9290
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
9391
];
9492

9593
// Build the PDO connection
96-
$this->pdo = new PDO($dsn, $username, $password, $options);
94+
$pdo = new PDO($dsn, $username, $password, $options);
9795

98-
if ($this->config["charset"]) {
99-
$this->pdo->query('SET NAMES \'' . $this->config["charset"] . '\'');
96+
if ($config["charset"]) {
97+
$pdo->query('SET NAMES \'' . $config["charset"] . '\'');
10098
}
99+
100+
return $pdo;
101101
}
102102
}

0 commit comments

Comments
 (0)