Code Coverage
 
Lines
Functions and Methods
Classes and Traits
Total
94.29% covered (success)
94.29%
33 / 35
71.43% covered (warning)
71.43%
5 / 7
CRAP
0.00% covered (danger)
0.00%
0 / 1
ConnectionManager
94.29% covered (success)
94.29%
33 / 35
71.43% covered (warning)
71.43%
5 / 7
25.12
0.00% covered (danger)
0.00%
0 / 1
 __construct
n/a
0 / 0
n/a
0 / 0
1
 __clone
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __sleep
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 __wakeup
100.00% covered (success)
100.00%
1 / 1
100.00% covered (success)
100.00%
1 / 1
1
 getInstance
66.67% covered (warning)
66.67%
2 / 3
0.00% covered (danger)
0.00%
0 / 1
2.15
 getConnection
100.00% covered (success)
100.00%
5 / 5
100.00% covered (success)
100.00%
1 / 1
5
 connect
92.31% covered (success)
92.31%
12 / 13
0.00% covered (danger)
0.00%
0 / 1
4.01
 parseParams
100.00% covered (success)
100.00%
11 / 11
100.00% covered (success)
100.00%
1 / 1
5
 createDsn
n/a
0 / 0
n/a
0 / 0
5
1<?php declare(strict_types=1);
2/**
3 * Query
4 *
5 * SQL Query Builder / Database Abstraction Layer
6 *
7 * PHP version 8.1
8 *
9 * @package     Query
10 * @author      Timothy J. Warren <tim@timshome.page>
11 * @copyright   2012 - 2023 Timothy J. Warren
12 * @license     http://www.opensource.org/licenses/mit-license.html  MIT License
13 * @link        https://git.timshomepage.net/aviat/Query
14 * @version     4.0.0
15 */
16
17namespace Query;
18
19use DomainException;
20use stdClass;
21
22/**
23 * Connection manager class to manage connections for the
24 * Query method
25 */
26final class ConnectionManager
27{
28    /**
29     * Map of named database connections
30     */
31    private array $connections = [];
32
33    /**
34     * Class instance variable
35     */
36    private static ?ConnectionManager $instance = NULL;
37
38    /**
39     * Private constructor to prevent multiple instances
40     * @codeCoverageIgnore
41     */
42    private function __construct()
43    {
44    }
45
46    /**
47     * Private clone method to prevent cloning
48     *
49     * @throws DomainException
50     * @return void
51     */
52    public function __clone()
53    {
54        throw new DomainException("Can't clone singleton");
55    }
56
57    /**
58     * Prevent serialization of this object
59     *
60     * @throws DomainException
61     */
62    public function __sleep()
63    {
64        throw new DomainException('No serializing of singleton');
65    }
66
67    /**
68     * Make sure serialize/deserialize doesn't work
69     *
70     * @throws DomainException
71     */
72    public function __wakeup(): void
73    {
74        throw new DomainException("Can't unserialize singleton");
75    }
76
77    /**
78     * Return  a connection manager instance
79     *
80     * @staticvar null $instance
81     */
82    public static function getInstance(): ConnectionManager
83    {
84        if (self::$instance === NULL)
85        {
86            self::$instance = new self();
87        }
88
89        return self::$instance;
90    }
91
92    /**
93     * Returns the connection specified by the name given
94     *
95     * @throws Exception\NonExistentConnectionException
96     */
97    public function getConnection(string $name = ''): QueryBuilderInterface
98    {
99        // If the parameter is a string, use it as an array index
100        if (is_scalar($name) && isset($this->connections[$name]))
101        {
102            return $this->connections[$name];
103        }
104
105        if (empty($name) && ! empty($this->connections)) // Otherwise, return the last one
106        {
107            return end($this->connections);
108        }
109
110        // You should actually connect before trying to get a connection...
111        throw new Exception\NonExistentConnectionException('The specified connection does not exist');
112    }
113
114    /**
115     * Parse the passed parameters and return a connection
116     */
117    public function connect(array|object $params): QueryBuilderInterface
118    {
119        [$dsn, $dbType, $params, $options] = $this->parseParams($params);
120
121        $dbType = ucfirst($dbType);
122        $driver = "\\Query\\Drivers\\{$dbType}\\Driver";
123
124        // Create the database connection
125        $db =  empty($params->user)
126            ? new $driver($dsn, '', '', $options)
127            : new $driver($dsn, $params->user, $params->pass, $options);
128
129        // Set the table prefix, if it exists
130        if (isset($params->prefix))
131        {
132            $db->setTablePrefix($params->prefix);
133        }
134
135        // Create Query Builder object
136        $conn = new QueryBuilder($db, new QueryParser($db));
137
138        // Save it for later
139        if (isset($params->alias))
140        {
141            $this->connections[$params->alias] = $conn;
142        }
143        else
144        {
145            $this->connections[] = $conn;
146        }
147
148        return $conn;
149    }
150
151    /**
152     * Parses params into a dsn and option array
153     *
154     * @throws Exception\BadDBDriverException
155     */
156    public function parseParams(array|object $rawParams): array
157    {
158        $params = (object) $rawParams;
159        $params->type = strtolower($params->type);
160        $dbType = ($params->type === 'postgresql') ? 'pgsql' : $params->type;
161        $dbType = ucfirst($dbType);
162
163        // Make sure the class exists
164        if ( ! class_exists("\\Query\\Drivers\\{$dbType}\\Driver"))
165        {
166            throw new Exception\BadDBDriverException('Database driver does not exist, or is not supported');
167        }
168
169        // Set additional PDO options
170        $options = [];
171
172        if (isset($params->options))
173        {
174            $options = (array) $params->options;
175        }
176
177        // Create the dsn for the database to connect to
178        $dsn = strtolower($dbType) === 'sqlite' ? $params->file : $this->createDsn($dbType, $params);
179
180        return [$dsn, $dbType, $params, $options];
181    }
182
183    /**
184     * Create the dsn from the db type and params
185     *
186     * @codeCoverageIgnore
187     */
188    private function createDsn(string $dbType, stdClass $params): string
189    {
190        $pairs = [];
191
192        if ( ! empty($params->database))
193        {
194            $pairs[] = implode('=', ['dbname', $params->database]);
195        }
196
197        $skip = [
198            'name' => 'name',
199            'pass' => 'pass',
200            'user' => 'user',
201            'type' => 'type',
202            'prefix' => 'prefix',
203            'options' => 'options',
204            'database' => 'database',
205            'alias' => 'alias',
206        ];
207
208        foreach ($params as $key => $val)
209        {
210            if (( ! array_key_exists($key, $skip)) && ! empty($val))
211            {
212                $pairs[] = implode('=', [$key, $val]);
213            }
214        }
215
216        return strtolower($dbType) . ':' . implode(';', $pairs);
217    }
218}