1
|
|
|
<?php |
2
|
|
|
/* |
3
|
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS |
4
|
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT |
5
|
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR |
6
|
|
|
* A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT |
7
|
|
|
* OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
8
|
|
|
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT |
9
|
|
|
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, |
10
|
|
|
* DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY |
11
|
|
|
* THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT |
12
|
|
|
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE |
13
|
|
|
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
14
|
|
|
* |
15
|
|
|
* This software consists of voluntary contributions made by many individuals |
16
|
|
|
* and is licensed under the MIT license. For more information, see |
17
|
|
|
* <http://www.doctrine-project.org>. |
18
|
|
|
*/ |
19
|
|
|
|
20
|
|
|
namespace Doctrine\DBAL; |
21
|
|
|
|
22
|
|
|
use Doctrine\Common\EventManager; |
23
|
|
|
use function array_keys; |
24
|
|
|
use function array_map; |
25
|
|
|
use function array_merge; |
26
|
|
|
use function class_implements; |
27
|
|
|
use function in_array; |
28
|
|
|
use function is_subclass_of; |
29
|
|
|
use function parse_str; |
30
|
|
|
use function parse_url; |
31
|
|
|
use function preg_replace; |
32
|
|
|
use function str_replace; |
33
|
|
|
use function strpos; |
34
|
|
|
use function substr; |
35
|
|
|
|
36
|
|
|
/** |
37
|
|
|
* Factory for creating Doctrine\DBAL\Connection instances. |
38
|
|
|
* |
39
|
|
|
* @author Roman Borschel <[email protected]> |
40
|
|
|
* @since 2.0 |
41
|
|
|
*/ |
42
|
|
|
final class DriverManager |
43
|
|
|
{ |
44
|
|
|
/** |
45
|
|
|
* List of supported drivers and their mappings to the driver classes. |
46
|
|
|
* |
47
|
|
|
* To add your own driver use the 'driverClass' parameter to |
48
|
|
|
* {@link DriverManager::getConnection()}. |
49
|
|
|
* |
50
|
|
|
* @var array |
51
|
|
|
*/ |
52
|
|
|
private static $_driverMap = [ |
53
|
|
|
'pdo_mysql' => 'Doctrine\DBAL\Driver\PDOMySql\Driver', |
54
|
|
|
'pdo_sqlite' => 'Doctrine\DBAL\Driver\PDOSqlite\Driver', |
55
|
|
|
'pdo_pgsql' => 'Doctrine\DBAL\Driver\PDOPgSql\Driver', |
56
|
|
|
'pdo_oci' => 'Doctrine\DBAL\Driver\PDOOracle\Driver', |
57
|
|
|
'oci8' => 'Doctrine\DBAL\Driver\OCI8\Driver', |
58
|
|
|
'ibm_db2' => 'Doctrine\DBAL\Driver\IBMDB2\DB2Driver', |
59
|
|
|
'pdo_sqlsrv' => 'Doctrine\DBAL\Driver\PDOSqlsrv\Driver', |
60
|
|
|
'mysqli' => 'Doctrine\DBAL\Driver\Mysqli\Driver', |
61
|
|
|
'drizzle_pdo_mysql' => 'Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver', |
62
|
|
|
'sqlanywhere' => 'Doctrine\DBAL\Driver\SQLAnywhere\Driver', |
63
|
|
|
'sqlsrv' => 'Doctrine\DBAL\Driver\SQLSrv\Driver', |
64
|
|
|
]; |
65
|
|
|
|
66
|
|
|
/** |
67
|
|
|
* List of URL schemes from a database URL and their mappings to driver. |
68
|
|
|
*/ |
69
|
|
|
private static $driverSchemeAliases = [ |
70
|
|
|
'db2' => 'ibm_db2', |
71
|
|
|
'mssql' => 'pdo_sqlsrv', |
72
|
|
|
'mysql' => 'pdo_mysql', |
73
|
|
|
'mysql2' => 'pdo_mysql', // Amazon RDS, for some weird reason |
74
|
|
|
'postgres' => 'pdo_pgsql', |
75
|
|
|
'postgresql' => 'pdo_pgsql', |
76
|
|
|
'pgsql' => 'pdo_pgsql', |
77
|
|
|
'sqlite' => 'pdo_sqlite', |
78
|
|
|
'sqlite3' => 'pdo_sqlite', |
79
|
|
|
]; |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Private constructor. This class cannot be instantiated. |
83
|
|
|
*/ |
84
|
|
|
private function __construct() |
85
|
|
|
{ |
86
|
|
|
} |
87
|
|
|
|
88
|
|
|
/** |
89
|
|
|
* Creates a connection object based on the specified parameters. |
90
|
|
|
* This method returns a Doctrine\DBAL\Connection which wraps the underlying |
91
|
|
|
* driver connection. |
92
|
|
|
* |
93
|
|
|
* $params must contain at least one of the following. |
94
|
|
|
* |
95
|
|
|
* Either 'driver' with one of the following values: |
96
|
|
|
* |
97
|
|
|
* pdo_mysql |
98
|
|
|
* pdo_sqlite |
99
|
|
|
* pdo_pgsql |
100
|
|
|
* pdo_oci (unstable) |
101
|
|
|
* pdo_sqlsrv |
102
|
|
|
* pdo_sqlsrv |
103
|
|
|
* mysqli |
104
|
|
|
* sqlanywhere |
105
|
|
|
* sqlsrv |
106
|
|
|
* ibm_db2 (unstable) |
107
|
|
|
* drizzle_pdo_mysql |
108
|
|
|
* |
109
|
|
|
* OR 'driverClass' that contains the full class name (with namespace) of the |
110
|
|
|
* driver class to instantiate. |
111
|
|
|
* |
112
|
|
|
* Other (optional) parameters: |
113
|
|
|
* |
114
|
|
|
* <b>user (string)</b>: |
115
|
|
|
* The username to use when connecting. |
116
|
|
|
* |
117
|
|
|
* <b>password (string)</b>: |
118
|
|
|
* The password to use when connecting. |
119
|
|
|
* |
120
|
|
|
* <b>driverOptions (array)</b>: |
121
|
|
|
* Any additional driver-specific options for the driver. These are just passed |
122
|
|
|
* through to the driver. |
123
|
|
|
* |
124
|
|
|
* <b>pdo</b>: |
125
|
|
|
* You can pass an existing PDO instance through this parameter. The PDO |
126
|
|
|
* instance will be wrapped in a Doctrine\DBAL\Connection. |
127
|
|
|
* |
128
|
|
|
* <b>wrapperClass</b>: |
129
|
|
|
* You may specify a custom wrapper class through the 'wrapperClass' |
130
|
|
|
* parameter but this class MUST inherit from Doctrine\DBAL\Connection. |
131
|
|
|
* |
132
|
|
|
* <b>driverClass</b>: |
133
|
|
|
* The driver class to use. |
134
|
|
|
* |
135
|
|
|
* @param array $params The parameters. |
136
|
|
|
* @param \Doctrine\DBAL\Configuration|null $config The configuration to use. |
137
|
|
|
* @param \Doctrine\Common\EventManager|null $eventManager The event manager to use. |
138
|
|
|
* |
139
|
|
|
* @return \Doctrine\DBAL\Connection |
140
|
|
|
* |
141
|
|
|
* @throws \Doctrine\DBAL\DBALException |
142
|
|
|
*/ |
143
|
2745 |
|
public static function getConnection( |
144
|
|
|
array $params, |
145
|
|
|
Configuration $config = null, |
146
|
|
|
EventManager $eventManager = null): Connection |
147
|
|
|
{ |
148
|
|
|
// create default config and event manager, if not set |
149
|
2745 |
|
if ( ! $config) { |
150
|
2602 |
|
$config = new Configuration(); |
151
|
|
|
} |
152
|
2745 |
|
if ( ! $eventManager) { |
153
|
2602 |
|
$eventManager = new EventManager(); |
154
|
|
|
} |
155
|
|
|
|
156
|
2745 |
|
$params = self::parseDatabaseUrl($params); |
157
|
|
|
|
158
|
|
|
// URL support for MasterSlaveConnection |
159
|
2688 |
|
if (isset($params['master'])) { |
160
|
75 |
|
$params['master'] = self::parseDatabaseUrl($params['master']); |
161
|
|
|
} |
162
|
|
|
|
163
|
2688 |
|
if (isset($params['slaves'])) { |
164
|
75 |
|
foreach ($params['slaves'] as $key => $slaveParams) { |
165
|
75 |
|
$params['slaves'][$key] = self::parseDatabaseUrl($slaveParams); |
166
|
|
|
} |
167
|
|
|
} |
168
|
|
|
|
169
|
|
|
// URL support for PoolingShardConnection |
170
|
2688 |
|
if (isset($params['global'])) { |
171
|
285 |
|
$params['global'] = self::parseDatabaseUrl($params['global']); |
172
|
|
|
} |
173
|
|
|
|
174
|
2688 |
|
if (isset($params['shards'])) { |
175
|
285 |
|
foreach ($params['shards'] as $key => $shardParams) { |
176
|
285 |
|
$params['shards'][$key] = self::parseDatabaseUrl($shardParams); |
177
|
|
|
} |
178
|
|
|
} |
179
|
|
|
|
180
|
|
|
// check for existing pdo object |
181
|
2688 |
|
if (isset($params['pdo']) && ! $params['pdo'] instanceof \PDO) { |
182
|
19 |
|
throw DBALException::invalidPdoInstance(); |
183
|
2669 |
|
} elseif (isset($params['pdo'])) { |
184
|
95 |
|
$params['pdo']->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); |
185
|
95 |
|
$params['driver'] = 'pdo_' . $params['pdo']->getAttribute(\PDO::ATTR_DRIVER_NAME); |
186
|
|
|
} else { |
187
|
2574 |
|
self::_checkParams($params); |
188
|
|
|
} |
189
|
|
|
|
190
|
2612 |
|
$className = $params['driverClass'] ?? self::$_driverMap[$params['driver']]; |
191
|
|
|
|
192
|
2612 |
|
$driver = new $className(); |
193
|
|
|
|
194
|
2612 |
|
$wrapperClass = 'Doctrine\DBAL\Connection'; |
195
|
2612 |
|
if (isset($params['wrapperClass'])) { |
196
|
512 |
|
if (is_subclass_of($params['wrapperClass'], $wrapperClass)) { |
197
|
493 |
|
$wrapperClass = $params['wrapperClass']; |
198
|
|
|
} else { |
199
|
19 |
|
throw DBALException::invalidWrapperClass($params['wrapperClass']); |
200
|
|
|
} |
201
|
|
|
} |
202
|
|
|
|
203
|
2593 |
|
return new $wrapperClass($params, $driver, $config, $eventManager); |
204
|
|
|
} |
205
|
|
|
|
206
|
|
|
/** |
207
|
|
|
* Returns the list of supported drivers. |
208
|
|
|
* |
209
|
|
|
* @return array |
210
|
|
|
*/ |
211
|
|
|
public static function getAvailableDrivers(): array |
212
|
|
|
{ |
213
|
|
|
return array_keys(self::$_driverMap); |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
/** |
217
|
|
|
* Checks the list of parameters. |
218
|
|
|
* |
219
|
|
|
* @param array $params The list of parameters. |
220
|
|
|
* |
221
|
|
|
* @return void |
222
|
|
|
* |
223
|
|
|
* @throws \Doctrine\DBAL\DBALException |
224
|
|
|
*/ |
225
|
2574 |
|
private static function _checkParams(array $params): void |
226
|
|
|
{ |
227
|
|
|
// check existence of mandatory parameters |
228
|
|
|
|
229
|
|
|
// driver |
230
|
2574 |
|
if ( ! isset($params['driver']) && ! isset($params['driverClass'])) { |
231
|
19 |
|
throw DBALException::driverRequired(); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
// check validity of parameters |
235
|
|
|
|
236
|
|
|
// driver |
237
|
2555 |
|
if (isset($params['driver']) && ! isset(self::$_driverMap[$params['driver']])) { |
238
|
19 |
|
throw DBALException::unknownDriver($params['driver'], array_keys(self::$_driverMap)); |
239
|
|
|
} |
240
|
|
|
|
241
|
2536 |
|
if (isset($params['driverClass']) && ! in_array('Doctrine\DBAL\Driver', class_implements($params['driverClass'], true))) { |
242
|
19 |
|
throw DBALException::invalidDriverClass($params['driverClass']); |
243
|
|
|
} |
244
|
2517 |
|
} |
245
|
|
|
|
246
|
|
|
/** |
247
|
|
|
* Normalizes the given connection URL path. |
248
|
|
|
* |
249
|
|
|
* @param string $urlPath |
250
|
|
|
* |
251
|
|
|
* @return string The normalized connection URL path |
252
|
|
|
*/ |
253
|
513 |
|
private static function normalizeDatabaseUrlPath(string $urlPath): string |
254
|
|
|
{ |
255
|
|
|
// Trim leading slash from URL path. |
256
|
513 |
|
return substr($urlPath, 1); |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
/** |
260
|
|
|
* Extracts parts from a database URL, if present, and returns an |
261
|
|
|
* updated list of parameters. |
262
|
|
|
* |
263
|
|
|
* @param array $params The list of parameters. |
264
|
|
|
* |
265
|
|
|
* @return array A modified list of parameters with info from a database |
266
|
|
|
* URL extracted into indidivual parameter parts. |
267
|
|
|
* |
268
|
|
|
* @throws DBALException |
269
|
|
|
*/ |
270
|
2745 |
|
private static function parseDatabaseUrl(array $params): array |
271
|
|
|
{ |
272
|
2745 |
|
if (!isset($params['url'])) { |
273
|
2213 |
|
return $params; |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
// (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid |
|
|
|
|
277
|
570 |
|
$url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $params['url']); |
278
|
570 |
|
$url = parse_url($url); |
279
|
|
|
|
280
|
570 |
|
if ($url === false) { |
281
|
|
|
throw new DBALException('Malformed parameter "url".'); |
282
|
|
|
} |
283
|
|
|
|
284
|
570 |
|
$url = array_map('rawurldecode', $url); |
285
|
|
|
|
286
|
|
|
// If we have a connection URL, we have to unset the default PDO instance connection parameter (if any) |
287
|
|
|
// as we cannot merge connection details from the URL into the PDO instance (URL takes precedence). |
288
|
570 |
|
unset($params['pdo']); |
289
|
|
|
|
290
|
570 |
|
$params = self::parseDatabaseUrlScheme($url, $params); |
291
|
|
|
|
292
|
513 |
|
if (isset($url['host'])) { |
293
|
513 |
|
$params['host'] = $url['host']; |
294
|
|
|
} |
295
|
513 |
|
if (isset($url['port'])) { |
296
|
57 |
|
$params['port'] = $url['port']; |
297
|
|
|
} |
298
|
513 |
|
if (isset($url['user'])) { |
299
|
399 |
|
$params['user'] = $url['user']; |
300
|
|
|
} |
301
|
513 |
|
if (isset($url['pass'])) { |
302
|
399 |
|
$params['password'] = $url['pass']; |
303
|
|
|
} |
304
|
|
|
|
305
|
513 |
|
$params = self::parseDatabaseUrlPath($url, $params); |
306
|
513 |
|
$params = self::parseDatabaseUrlQuery($url, $params); |
307
|
|
|
|
308
|
513 |
|
return $params; |
309
|
|
|
} |
310
|
|
|
|
311
|
|
|
/** |
312
|
|
|
* Parses the given connection URL and resolves the given connection parameters. |
313
|
|
|
* |
314
|
|
|
* Assumes that the connection URL scheme is already parsed and resolved into the given connection parameters |
315
|
|
|
* via {@link parseDatabaseUrlScheme}. |
316
|
|
|
* |
317
|
|
|
* @param array $url The URL parts to evaluate. |
318
|
|
|
* @param array $params The connection parameters to resolve. |
319
|
|
|
* |
320
|
|
|
* @return array The resolved connection parameters. |
321
|
|
|
* |
322
|
|
|
* @see parseDatabaseUrlScheme |
323
|
|
|
*/ |
324
|
513 |
|
private static function parseDatabaseUrlPath(array $url, array $params): array |
325
|
|
|
{ |
326
|
513 |
|
if (! isset($url['path'])) { |
327
|
|
|
return $params; |
328
|
|
|
} |
329
|
|
|
|
330
|
513 |
|
$url['path'] = self::normalizeDatabaseUrlPath($url['path']); |
331
|
|
|
|
332
|
|
|
// If we do not have a known DBAL driver, we do not know any connection URL path semantics to evaluate |
333
|
|
|
// and therefore treat the path as regular DBAL connection URL path. |
334
|
513 |
|
if (! isset($params['driver'])) { |
335
|
19 |
|
return self::parseRegularDatabaseUrlPath($url, $params); |
336
|
|
|
} |
337
|
|
|
|
338
|
494 |
|
if (strpos($params['driver'], 'sqlite') !== false) { |
339
|
114 |
|
return self::parseSqliteDatabaseUrlPath($url, $params); |
340
|
|
|
} |
341
|
|
|
|
342
|
380 |
|
return self::parseRegularDatabaseUrlPath($url, $params); |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
/** |
346
|
|
|
* Parses the query part of the given connection URL and resolves the given connection parameters. |
347
|
|
|
* |
348
|
|
|
* @param array $url The connection URL parts to evaluate. |
349
|
|
|
* @param array $params The connection parameters to resolve. |
350
|
|
|
* |
351
|
|
|
* @return array The resolved connection parameters. |
352
|
|
|
*/ |
353
|
513 |
|
private static function parseDatabaseUrlQuery(array $url, array $params): array |
354
|
|
|
{ |
355
|
513 |
|
if (! isset($url['query'])) { |
356
|
494 |
|
return $params; |
357
|
|
|
} |
358
|
|
|
|
359
|
19 |
|
$query = []; |
360
|
|
|
|
361
|
19 |
|
parse_str($url['query'], $query); // simply ingest query as extra params, e.g. charset or sslmode |
362
|
|
|
|
363
|
19 |
|
return array_merge($params, $query); // parse_str wipes existing array elements |
364
|
|
|
} |
365
|
|
|
|
366
|
|
|
/** |
367
|
|
|
* Parses the given regular connection URL and resolves the given connection parameters. |
368
|
|
|
* |
369
|
|
|
* Assumes that the "path" URL part is already normalized via {@link normalizeDatabaseUrlPath}. |
370
|
|
|
* |
371
|
|
|
* @param array $url The regular connection URL parts to evaluate. |
372
|
|
|
* @param array $params The connection parameters to resolve. |
373
|
|
|
* |
374
|
|
|
* @return array The resolved connection parameters. |
375
|
|
|
* |
376
|
|
|
* @see normalizeDatabaseUrlPath |
377
|
|
|
*/ |
378
|
399 |
|
private static function parseRegularDatabaseUrlPath(array $url, array $params): array |
379
|
|
|
{ |
380
|
399 |
|
$params['dbname'] = $url['path']; |
381
|
|
|
|
382
|
399 |
|
return $params; |
383
|
|
|
} |
384
|
|
|
|
385
|
|
|
/** |
386
|
|
|
* Parses the given SQLite connection URL and resolves the given connection parameters. |
387
|
|
|
* |
388
|
|
|
* Assumes that the "path" URL part is already normalized via {@link normalizeDatabaseUrlPath}. |
389
|
|
|
* |
390
|
|
|
* @param array $url The SQLite connection URL parts to evaluate. |
391
|
|
|
* @param array $params The connection parameters to resolve. |
392
|
|
|
* |
393
|
|
|
* @return array The resolved connection parameters. |
394
|
|
|
* |
395
|
|
|
* @see normalizeDatabaseUrlPath |
396
|
|
|
*/ |
397
|
114 |
|
private static function parseSqliteDatabaseUrlPath(array $url, array $params): array |
398
|
|
|
{ |
399
|
114 |
|
if ($url['path'] === ':memory:') { |
400
|
38 |
|
$params['memory'] = true; |
401
|
|
|
|
402
|
38 |
|
return $params; |
403
|
|
|
} |
404
|
|
|
|
405
|
76 |
|
$params['path'] = $url['path']; // pdo_sqlite driver uses 'path' instead of 'dbname' key |
406
|
|
|
|
407
|
76 |
|
return $params; |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
/** |
411
|
|
|
* Parses the scheme part from given connection URL and resolves the given connection parameters. |
412
|
|
|
* |
413
|
|
|
* @param array $url The connection URL parts to evaluate. |
414
|
|
|
* @param array $params The connection parameters to resolve. |
415
|
|
|
* |
416
|
|
|
* @return array The resolved connection parameters. |
417
|
|
|
* |
418
|
|
|
* @throws DBALException if parsing failed or resolution is not possible. |
419
|
|
|
*/ |
420
|
570 |
|
private static function parseDatabaseUrlScheme(array $url, array $params): array |
421
|
|
|
{ |
422
|
570 |
|
if (isset($url['scheme'])) { |
423
|
|
|
// The requested driver from the URL scheme takes precedence |
424
|
|
|
// over the default custom driver from the connection parameters (if any). |
425
|
437 |
|
unset($params['driverClass']); |
426
|
|
|
|
427
|
|
|
// URL schemes must not contain underscores, but dashes are ok |
428
|
437 |
|
$driver = str_replace('-', '_', $url['scheme']); |
429
|
|
|
|
430
|
|
|
// The requested driver from the URL scheme takes precedence over the |
431
|
|
|
// default driver from the connection parameters. If the driver is |
432
|
|
|
// an alias (e.g. "postgres"), map it to the actual name ("pdo-pgsql"). |
433
|
|
|
// Otherwise, let checkParams decide later if the driver exists. |
434
|
437 |
|
$params['driver'] = self::$driverSchemeAliases[$driver] ?? $driver; |
435
|
|
|
|
436
|
437 |
|
return $params; |
437
|
|
|
} |
438
|
|
|
|
439
|
|
|
// If a schemeless connection URL is given, we require a default driver or default custom driver |
440
|
|
|
// as connection parameter. |
441
|
133 |
|
if (! isset($params['driverClass']) && ! isset($params['driver'])) { |
442
|
57 |
|
throw DBALException::driverRequired($params['url']); |
443
|
|
|
} |
444
|
|
|
|
445
|
76 |
|
return $params; |
446
|
|
|
} |
447
|
|
|
} |
448
|
|
|
|
Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.
The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.
This check looks for comments that seem to be mostly valid code and reports them.