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 | |||
24 | /** |
||
25 | * Factory for creating Doctrine\DBAL\Connection instances. |
||
26 | * |
||
27 | * @author Roman Borschel <[email protected]> |
||
28 | * @since 2.0 |
||
29 | */ |
||
30 | final class DriverManager |
||
31 | { |
||
32 | /** |
||
33 | * List of supported drivers and their mappings to the driver classes. |
||
34 | * |
||
35 | * To add your own driver use the 'driverClass' parameter to |
||
36 | * {@link DriverManager::getConnection()}. |
||
37 | * |
||
38 | * @var array |
||
39 | */ |
||
40 | private static $_driverMap = [ |
||
41 | 'pdo_mysql' => 'Doctrine\DBAL\Driver\PDOMySql\Driver', |
||
42 | 'pdo_sqlite' => 'Doctrine\DBAL\Driver\PDOSqlite\Driver', |
||
43 | 'pdo_pgsql' => 'Doctrine\DBAL\Driver\PDOPgSql\Driver', |
||
44 | 'pdo_oci' => 'Doctrine\DBAL\Driver\PDOOracle\Driver', |
||
45 | 'oci8' => 'Doctrine\DBAL\Driver\OCI8\Driver', |
||
46 | 'ibm_db2' => 'Doctrine\DBAL\Driver\IBMDB2\DB2Driver', |
||
47 | 'pdo_sqlsrv' => 'Doctrine\DBAL\Driver\PDOSqlsrv\Driver', |
||
48 | 'mysqli' => 'Doctrine\DBAL\Driver\Mysqli\Driver', |
||
49 | 'drizzle_pdo_mysql' => 'Doctrine\DBAL\Driver\DrizzlePDOMySql\Driver', |
||
50 | 'sqlanywhere' => 'Doctrine\DBAL\Driver\SQLAnywhere\Driver', |
||
51 | 'sqlsrv' => 'Doctrine\DBAL\Driver\SQLSrv\Driver', |
||
52 | ]; |
||
53 | |||
54 | /** |
||
55 | * List of URL schemes from a database URL and their mappings to driver. |
||
56 | */ |
||
57 | private static $driverSchemeAliases = [ |
||
58 | 'db2' => 'ibm_db2', |
||
59 | 'mssql' => 'pdo_sqlsrv', |
||
60 | 'mysql' => 'pdo_mysql', |
||
61 | 'mysql2' => 'pdo_mysql', // Amazon RDS, for some weird reason |
||
62 | 'postgres' => 'pdo_pgsql', |
||
63 | 'postgresql' => 'pdo_pgsql', |
||
64 | 'pgsql' => 'pdo_pgsql', |
||
65 | 'sqlite' => 'pdo_sqlite', |
||
66 | 'sqlite3' => 'pdo_sqlite', |
||
67 | ]; |
||
68 | |||
69 | /** |
||
70 | * Private constructor. This class cannot be instantiated. |
||
71 | */ |
||
72 | private function __construct() |
||
73 | { |
||
74 | } |
||
75 | |||
76 | /** |
||
77 | * Creates a connection object based on the specified parameters. |
||
78 | * This method returns a Doctrine\DBAL\Connection which wraps the underlying |
||
79 | * driver connection. |
||
80 | * |
||
81 | * $params must contain at least one of the following. |
||
82 | * |
||
83 | * Either 'driver' with one of the following values: |
||
84 | * |
||
85 | * pdo_mysql |
||
86 | * pdo_sqlite |
||
87 | * pdo_pgsql |
||
88 | * pdo_oci (unstable) |
||
89 | * pdo_sqlsrv |
||
90 | * pdo_sqlsrv |
||
91 | * mysqli |
||
92 | * sqlanywhere |
||
93 | * sqlsrv |
||
94 | * ibm_db2 (unstable) |
||
95 | * drizzle_pdo_mysql |
||
96 | * |
||
97 | * OR 'driverClass' that contains the full class name (with namespace) of the |
||
98 | * driver class to instantiate. |
||
99 | * |
||
100 | * Other (optional) parameters: |
||
101 | * |
||
102 | * <b>user (string)</b>: |
||
103 | * The username to use when connecting. |
||
104 | * |
||
105 | * <b>password (string)</b>: |
||
106 | * The password to use when connecting. |
||
107 | * |
||
108 | * <b>driverOptions (array)</b>: |
||
109 | * Any additional driver-specific options for the driver. These are just passed |
||
110 | * through to the driver. |
||
111 | * |
||
112 | * <b>pdo</b>: |
||
113 | * You can pass an existing PDO instance through this parameter. The PDO |
||
114 | * instance will be wrapped in a Doctrine\DBAL\Connection. |
||
115 | * |
||
116 | * <b>wrapperClass</b>: |
||
117 | * You may specify a custom wrapper class through the 'wrapperClass' |
||
118 | * parameter but this class MUST inherit from Doctrine\DBAL\Connection. |
||
119 | * |
||
120 | * <b>driverClass</b>: |
||
121 | * The driver class to use. |
||
122 | * |
||
123 | * @param array $params The parameters. |
||
124 | * @param \Doctrine\DBAL\Configuration|null $config The configuration to use. |
||
125 | * @param \Doctrine\Common\EventManager|null $eventManager The event manager to use. |
||
126 | * |
||
127 | * @return \Doctrine\DBAL\Connection |
||
128 | * |
||
129 | * @throws \Doctrine\DBAL\DBALException |
||
130 | */ |
||
131 | 138 | public static function getConnection( |
|
132 | array $params, |
||
133 | Configuration $config = null, |
||
134 | EventManager $eventManager = null): Connection |
||
135 | { |
||
136 | // create default config and event manager, if not set |
||
137 | 138 | if ( ! $config) { |
|
138 | 133 | $config = new Configuration(); |
|
139 | } |
||
140 | 138 | if ( ! $eventManager) { |
|
141 | 133 | $eventManager = new EventManager(); |
|
142 | } |
||
143 | |||
144 | 138 | $params = self::parseDatabaseUrl($params); |
|
145 | |||
146 | // check for existing pdo object |
||
147 | 135 | if (isset($params['pdo']) && ! $params['pdo'] instanceof \PDO) { |
|
148 | 1 | throw DBALException::invalidPdoInstance(); |
|
149 | 134 | } elseif (isset($params['pdo'])) { |
|
150 | 5 | $params['pdo']->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION); |
|
151 | 5 | $params['driver'] = 'pdo_' . $params['pdo']->getAttribute(\PDO::ATTR_DRIVER_NAME); |
|
152 | } else { |
||
153 | 129 | self::_checkParams($params); |
|
154 | } |
||
155 | 131 | if (isset($params['driverClass'])) { |
|
156 | 3 | $className = $params['driverClass']; |
|
157 | } else { |
||
158 | 128 | $className = self::$_driverMap[$params['driver']]; |
|
159 | } |
||
160 | |||
161 | 131 | $driver = new $className(); |
|
162 | |||
163 | 131 | $wrapperClass = 'Doctrine\DBAL\Connection'; |
|
164 | 131 | if (isset($params['wrapperClass'])) { |
|
165 | 22 | if (is_subclass_of($params['wrapperClass'], $wrapperClass)) { |
|
166 | 21 | $wrapperClass = $params['wrapperClass']; |
|
167 | } else { |
||
168 | 1 | throw DBALException::invalidWrapperClass($params['wrapperClass']); |
|
169 | } |
||
170 | } |
||
171 | |||
172 | 130 | return new $wrapperClass($params, $driver, $config, $eventManager); |
|
173 | } |
||
174 | |||
175 | /** |
||
176 | * Returns the list of supported drivers. |
||
177 | * |
||
178 | * @return array |
||
179 | */ |
||
180 | public static function getAvailableDrivers(): array |
||
181 | { |
||
182 | return array_keys(self::$_driverMap); |
||
183 | } |
||
184 | |||
185 | /** |
||
186 | * Checks the list of parameters. |
||
187 | * |
||
188 | * @param array $params The list of parameters. |
||
189 | * |
||
190 | * @return void |
||
191 | * |
||
192 | * @throws \Doctrine\DBAL\DBALException |
||
193 | */ |
||
194 | 129 | private static function _checkParams(array $params): void |
|
195 | { |
||
196 | // check existence of mandatory parameters |
||
197 | |||
198 | // driver |
||
199 | 129 | if ( ! isset($params['driver']) && ! isset($params['driverClass'])) { |
|
200 | 1 | throw DBALException::driverRequired(); |
|
201 | } |
||
202 | |||
203 | // check validity of parameters |
||
204 | |||
205 | // driver |
||
206 | 128 | if (isset($params['driver']) && ! isset(self::$_driverMap[$params['driver']])) { |
|
207 | 1 | throw DBALException::unknownDriver($params['driver'], array_keys(self::$_driverMap)); |
|
208 | } |
||
209 | |||
210 | 127 | if (isset($params['driverClass']) && ! in_array('Doctrine\DBAL\Driver', class_implements($params['driverClass'], true))) { |
|
211 | 1 | throw DBALException::invalidDriverClass($params['driverClass']); |
|
212 | } |
||
213 | 126 | } |
|
214 | |||
215 | /** |
||
216 | * Normalizes the given connection URL path. |
||
217 | * |
||
218 | * @param string $urlPath |
||
219 | * |
||
220 | * @return string The normalized connection URL path |
||
221 | */ |
||
222 | 25 | private static function normalizeDatabaseUrlPath(string $urlPath): string |
|
223 | { |
||
224 | // Trim leading slash from URL path. |
||
225 | 25 | return substr($urlPath, 1); |
|
0 ignored issues
–
show
Bug
Best Practice
introduced
by
Loading history...
|
|||
226 | } |
||
227 | |||
228 | /** |
||
229 | * Extracts parts from a database URL, if present, and returns an |
||
230 | * updated list of parameters. |
||
231 | * |
||
232 | * @param array $params The list of parameters. |
||
233 | * |
||
234 | * @return array A modified list of parameters with info from a database |
||
235 | * URL extracted into indidivual parameter parts. |
||
236 | * |
||
237 | * @throws DBALException |
||
238 | */ |
||
239 | 138 | private static function parseDatabaseUrl(array $params): array |
|
240 | { |
||
241 | 138 | if (!isset($params['url'])) { |
|
242 | 110 | return $params; |
|
243 | } |
||
244 | |||
245 | // (pdo_)?sqlite3?:///... => (pdo_)?sqlite3?://localhost/... or else the URL will be invalid |
||
246 | 28 | $url = preg_replace('#^((?:pdo_)?sqlite3?):///#', '$1://localhost/', $params['url']); |
|
247 | |||
248 | // PHP < 5.4.8 doesn't parse schemeless urls properly. |
||
249 | // See: https://php.net/parse-url#refsect1-function.parse-url-changelog |
||
250 | 28 | if (PHP_VERSION_ID < 50408 && strpos($url, '//') === 0) { |
|
251 | $url = parse_url('fake:' . $url); |
||
252 | |||
253 | unset($url['scheme']); |
||
254 | } else { |
||
255 | 28 | $url = parse_url($url); |
|
256 | } |
||
257 | |||
258 | 28 | if ($url === false) { |
|
259 | throw new DBALException('Malformed parameter "url".'); |
||
260 | } |
||
261 | |||
262 | 28 | $url = array_map('rawurldecode', $url); |
|
263 | |||
264 | // If we have a connection URL, we have to unset the default PDO instance connection parameter (if any) |
||
265 | // as we cannot merge connection details from the URL into the PDO instance (URL takes precedence). |
||
266 | 28 | unset($params['pdo']); |
|
267 | |||
268 | 28 | $params = self::parseDatabaseUrlScheme($url, $params); |
|
269 | |||
270 | 25 | if (isset($url['host'])) { |
|
271 | 25 | $params['host'] = $url['host']; |
|
272 | } |
||
273 | 25 | if (isset($url['port'])) { |
|
274 | 1 | $params['port'] = $url['port']; |
|
275 | } |
||
276 | 25 | if (isset($url['user'])) { |
|
277 | 19 | $params['user'] = $url['user']; |
|
278 | } |
||
279 | 25 | if (isset($url['pass'])) { |
|
280 | 19 | $params['password'] = $url['pass']; |
|
281 | } |
||
282 | |||
283 | 25 | $params = self::parseDatabaseUrlPath($url, $params); |
|
284 | 25 | $params = self::parseDatabaseUrlQuery($url, $params); |
|
285 | |||
286 | 25 | return $params; |
|
287 | } |
||
288 | |||
289 | /** |
||
290 | * Parses the given connection URL and resolves the given connection parameters. |
||
291 | * |
||
292 | * Assumes that the connection URL scheme is already parsed and resolved into the given connection parameters |
||
293 | * via {@link parseDatabaseUrlScheme}. |
||
294 | * |
||
295 | * @param array $url The URL parts to evaluate. |
||
296 | * @param array $params The connection parameters to resolve. |
||
297 | * |
||
298 | * @return array The resolved connection parameters. |
||
299 | * |
||
300 | * @see parseDatabaseUrlScheme |
||
301 | */ |
||
302 | 25 | private static function parseDatabaseUrlPath(array $url, array $params): array |
|
303 | { |
||
304 | 25 | if (! isset($url['path'])) { |
|
305 | return $params; |
||
306 | } |
||
307 | |||
308 | 25 | $url['path'] = self::normalizeDatabaseUrlPath($url['path']); |
|
309 | |||
310 | // If we do not have a known DBAL driver, we do not know any connection URL path semantics to evaluate |
||
311 | // and therefore treat the path as regular DBAL connection URL path. |
||
312 | 25 | if (! isset($params['driver'])) { |
|
313 | 1 | return self::parseRegularDatabaseUrlPath($url, $params); |
|
314 | } |
||
315 | |||
316 | 24 | if (strpos($params['driver'], 'sqlite') !== false) { |
|
317 | 6 | return self::parseSqliteDatabaseUrlPath($url, $params); |
|
318 | } |
||
319 | |||
320 | 18 | return self::parseRegularDatabaseUrlPath($url, $params); |
|
321 | } |
||
322 | |||
323 | /** |
||
324 | * Parses the query part of the given connection URL and resolves the given connection parameters. |
||
325 | * |
||
326 | * @param array $url The connection URL parts to evaluate. |
||
327 | * @param array $params The connection parameters to resolve. |
||
328 | * |
||
329 | * @return array The resolved connection parameters. |
||
330 | */ |
||
331 | 25 | private static function parseDatabaseUrlQuery(array $url, array $params): array |
|
332 | { |
||
333 | 25 | if (! isset($url['query'])) { |
|
334 | 24 | return $params; |
|
335 | } |
||
336 | |||
337 | 1 | $query = []; |
|
338 | |||
339 | 1 | parse_str($url['query'], $query); // simply ingest query as extra params, e.g. charset or sslmode |
|
340 | |||
341 | 1 | return array_merge($params, $query); // parse_str wipes existing array elements |
|
342 | } |
||
343 | |||
344 | /** |
||
345 | * Parses the given regular connection URL and resolves the given connection parameters. |
||
346 | * |
||
347 | * Assumes that the "path" URL part is already normalized via {@link normalizeDatabaseUrlPath}. |
||
348 | * |
||
349 | * @param array $url The regular connection URL parts to evaluate. |
||
350 | * @param array $params The connection parameters to resolve. |
||
351 | * |
||
352 | * @return array The resolved connection parameters. |
||
353 | * |
||
354 | * @see normalizeDatabaseUrlPath |
||
355 | */ |
||
356 | 19 | private static function parseRegularDatabaseUrlPath(array $url, array $params): array |
|
357 | { |
||
358 | 19 | $params['dbname'] = $url['path']; |
|
359 | |||
360 | 19 | return $params; |
|
361 | } |
||
362 | |||
363 | /** |
||
364 | * Parses the given SQLite connection URL and resolves the given connection parameters. |
||
365 | * |
||
366 | * Assumes that the "path" URL part is already normalized via {@link normalizeDatabaseUrlPath}. |
||
367 | * |
||
368 | * @param array $url The SQLite connection URL parts to evaluate. |
||
369 | * @param array $params The connection parameters to resolve. |
||
370 | * |
||
371 | * @return array The resolved connection parameters. |
||
372 | * |
||
373 | * @see normalizeDatabaseUrlPath |
||
374 | */ |
||
375 | 6 | private static function parseSqliteDatabaseUrlPath(array $url, array $params): array |
|
376 | { |
||
377 | 6 | if ($url['path'] === ':memory:') { |
|
378 | 2 | $params['memory'] = true; |
|
379 | |||
380 | 2 | return $params; |
|
381 | } |
||
382 | |||
383 | 4 | $params['path'] = $url['path']; // pdo_sqlite driver uses 'path' instead of 'dbname' key |
|
384 | |||
385 | 4 | return $params; |
|
386 | } |
||
387 | |||
388 | /** |
||
389 | * Parses the scheme part from given connection URL and resolves the given connection parameters. |
||
390 | * |
||
391 | * @param array $url The connection URL parts to evaluate. |
||
392 | * @param array $params The connection parameters to resolve. |
||
393 | * |
||
394 | * @return array The resolved connection parameters. |
||
395 | * |
||
396 | * @throws DBALException if parsing failed or resolution is not possible. |
||
397 | */ |
||
398 | 28 | private static function parseDatabaseUrlScheme(array $url, array $params): array |
|
399 | { |
||
400 | 28 | if (isset($url['scheme'])) { |
|
401 | // The requested driver from the URL scheme takes precedence |
||
402 | // over the default custom driver from the connection parameters (if any). |
||
403 | 21 | unset($params['driverClass']); |
|
404 | |||
405 | // URL schemes must not contain underscores, but dashes are ok |
||
406 | 21 | $driver = str_replace('-', '_', $url['scheme']); |
|
407 | |||
408 | // The requested driver from the URL scheme takes precedence |
||
409 | // over the default driver from the connection parameters (if any). |
||
410 | 21 | $params['driver'] = isset(self::$driverSchemeAliases[$driver]) |
|
411 | // use alias like "postgres", else we just let checkParams decide later |
||
412 | // if the driver exists (for literal "pdo-pgsql" etc) |
||
413 | 19 | ? self::$driverSchemeAliases[$driver] |
|
414 | 2 | : $driver; |
|
415 | |||
416 | 21 | return $params; |
|
417 | } |
||
418 | |||
419 | // If a schemeless connection URL is given, we require a default driver or default custom driver |
||
420 | // as connection parameter. |
||
421 | 7 | if (! isset($params['driverClass']) && ! isset($params['driver'])) { |
|
422 | 3 | throw DBALException::driverRequired($params['url']); |
|
423 | } |
||
424 | |||
425 | 4 | return $params; |
|
426 | } |
||
427 | } |
||
428 |