DriverManager.php 15 KB

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