Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
100.00% covered (success)
100.00%
52 / 52
100.00% covered (success)
100.00%
3 / 3
CRAP
100.00% covered (success)
100.00%
1 / 1
MultiDbRouter
100.00% covered (success)
100.00%
51 / 51
100.00% covered (success)
100.00%
3 / 3
9
100.00% covered (success)
100.00%
1 / 1
 __construct
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getConnection
100.00% covered (success)
100.00%
29 / 29
100.00% covered (success)
100.00%
1 / 1
5
 getYiiConnection
100.00% covered (success)
100.00%
21 / 21
100.00% covered (success)
100.00%
1 / 1
3
1<?php
2
3declare(strict_types=1);
4
5/** @license For full copyright and license information, please see the LICENSE.md file. */
6
7namespace App\Core\Database;
8
9defined('AMMONLY_APP') || exit('Direct script access is forbidden.');
10
11use App\Core\Exception\DatabaseConnectionException;
12use PDO;
13use Yiisoft\Cache\ArrayCache;
14use Yiisoft\Db\Cache\SchemaCache;
15use Yiisoft\Db\Connection\ConnectionInterface;
16use Yiisoft\Db\Mysql\Connection as MysqlConnection;
17use Yiisoft\Db\Mysql\Driver as MysqlDriver;
18
19/**
20 * Multi-Database PDO and Yii3 Connection Router.
21 *
22 * Manages multiple database connection instances (default, tenant, replica) dynamically.
23 *
24 * @package App\Core\Database
25 */
26final class MultiDbRouter
27{
28    /**
29     * Active PDO connection instances cache.
30     *
31     * @var array<string, PDO>
32     */
33    private array $connections = [];
34
35    /**
36     * Active Yii3 database connection instances cache.
37     *
38     * @var array<string, ConnectionInterface>
39     */
40    private array $yiiConnections = [];
41
42    /**
43     * MultiDbRouter constructor.
44     *
45     * @param array<string, array<string, mixed>> $configs Connection configurations array indexed by name.
46     */
47    public function __construct(
48        private readonly array $configs = []
49    ) {
50    }
51
52    /**
53     * Obtains or creates a PDO connection by name.
54     *
55     * @param string $name Connection name (e.g. 'default').
56     * @return PDO Initialized PDO instance.
57     * @throws DatabaseConnectionException If connection config is missing or invalid.
58     */
59    public function getConnection(string $name = 'default'): PDO
60    {
61        if (isset($this->connections[$name])) {
62            return $this->connections[$name];
63        }
64
65        if (!isset($this->configs[$name])) {
66            throw new DatabaseConnectionException("Database connection configuration not found for key: '{$name}'");
67        }
68
69        $config = $this->configs[$name];
70        $dsn = (string)($config['dsn'] ?? sprintf(
71            'mysql:host=%s;port=%d;dbname=%s;charset=%s',
72            $config['host'] ?? '127.0.0.1',
73            $config['port'] ?? 3306,
74            $config['dbname'] ?? 'ammonly_admin',
75            $config['charset'] ?? 'utf8mb4'
76        ));
77
78        $options = [
79            PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
80            PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
81            PDO::ATTR_EMULATE_PREPARES => false,
82        ];
83        if (!empty($config['persistent'])) {
84            $options[PDO::ATTR_PERSISTENT] = true;
85        }
86        if (str_starts_with($dsn, 'mysql:')) {
87            $options[PDO::MYSQL_ATTR_INIT_COMMAND] = 'SET NAMES utf8mb4 COLLATE utf8mb4_unicode_ci';
88        }
89
90        $pdo = new PDO(
91            $dsn,
92            (string)($config['username'] ?? 'root'),
93            (string)($config['password'] ?? ''),
94            $options
95        );
96
97        $this->connections[$name] = $pdo;
98        return $pdo;
99    }
100
101    /**
102     * Obtains or creates a Yii3 database connection instance by name.
103     *
104     * @param string $name Connection configuration name (e.g. 'default').
105     * @return ConnectionInterface Initialized Yii3 database connection.
106     * @throws DatabaseConnectionException If connection config is missing.
107     */
108    public function getYiiConnection(string $name = 'default'): ConnectionInterface
109    {
110        if (isset($this->yiiConnections[$name])) {
111            return $this->yiiConnections[$name];
112        }
113
114        if (!isset($this->configs[$name])) {
115            throw new DatabaseConnectionException("Database connection configuration not found for key: '{$name}'");
116        }
117
118        $config = $this->configs[$name];
119        $dsn = (string)($config['dsn'] ?? sprintf(
120            'mysql:host=%s;port=%d;dbname=%s;charset=%s',
121            $config['host'] ?? '127.0.0.1',
122            $config['port'] ?? 3306,
123            $config['dbname'] ?? 'ammonly_admin',
124            $config['charset'] ?? 'utf8mb4'
125        ));
126
127        $driver = new MysqlDriver(
128            $dsn,
129            (string)($config['username'] ?? 'root'),
130            (string)($config['password'] ?? '')
131        );
132
133        $schemaCache = new SchemaCache(new ArrayCache());
134        $connection = new MysqlConnection($driver, $schemaCache);
135        $this->yiiConnections[$name] = $connection;
136
137        return $connection;
138    }
139}