ConnectionFactory.php 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  1. <?php
  2. /**
  3. * @copyright Copyright (c) 2016, ownCloud, Inc.
  4. *
  5. * @author Andreas Fischer <bantu@owncloud.com>
  6. * @author Joas Schilling <coding@schilljs.com>
  7. * @author Jörn Friedrich Dreyer <jfd@butonic.de>
  8. * @author Morris Jobke <hey@morrisjobke.de>
  9. * @author Robin Appelman <robin@icewind.nl>
  10. * @author Thomas Müller <thomas.mueller@tmit.eu>
  11. *
  12. * @license AGPL-3.0
  13. *
  14. * This code is free software: you can redistribute it and/or modify
  15. * it under the terms of the GNU Affero General Public License, version 3,
  16. * as published by the Free Software Foundation.
  17. *
  18. * This program is distributed in the hope that it will be useful,
  19. * but WITHOUT ANY WARRANTY; without even the implied warranty of
  20. * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  21. * GNU Affero General Public License for more details.
  22. *
  23. * You should have received a copy of the GNU Affero General Public License, version 3,
  24. * along with this program. If not, see <http://www.gnu.org/licenses/>
  25. *
  26. */
  27. namespace OC\DB;
  28. use Doctrine\Common\EventManager;
  29. use Doctrine\DBAL\Configuration;
  30. use Doctrine\DBAL\DriverManager;
  31. use Doctrine\DBAL\Event\Listeners\OracleSessionInit;
  32. use Doctrine\DBAL\Event\Listeners\SQLSessionInit;
  33. use OC\SystemConfig;
  34. /**
  35. * Takes care of creating and configuring Doctrine connections.
  36. */
  37. class ConnectionFactory {
  38. /** @var string default database name */
  39. const DEFAULT_DBNAME = 'owncloud';
  40. /** @var string default database table prefix */
  41. const DEFAULT_DBTABLEPREFIX = 'oc_';
  42. /**
  43. * @var array
  44. *
  45. * Array mapping DBMS type to default connection parameters passed to
  46. * \Doctrine\DBAL\DriverManager::getConnection().
  47. */
  48. protected $defaultConnectionParams = [
  49. 'mysql' => [
  50. 'adapter' => AdapterMySQL::class,
  51. 'charset' => 'UTF8',
  52. 'driver' => 'pdo_mysql',
  53. 'wrapperClass' => Connection::class,
  54. ],
  55. 'oci' => [
  56. 'adapter' => AdapterOCI8::class,
  57. 'charset' => 'AL32UTF8',
  58. 'driver' => 'oci8',
  59. 'wrapperClass' => OracleConnection::class,
  60. ],
  61. 'pgsql' => [
  62. 'adapter' => AdapterPgSql::class,
  63. 'driver' => 'pdo_pgsql',
  64. 'wrapperClass' => Connection::class,
  65. ],
  66. 'sqlite3' => [
  67. 'adapter' => AdapterSqlite::class,
  68. 'driver' => 'pdo_sqlite',
  69. 'wrapperClass' => Connection::class,
  70. ],
  71. ];
  72. /** @var SystemConfig */
  73. private $config;
  74. /**
  75. * ConnectionFactory constructor.
  76. *
  77. * @param SystemConfig $systemConfig
  78. */
  79. public function __construct(SystemConfig $systemConfig) {
  80. $this->config = $systemConfig;
  81. if ($this->config->getValue('mysql.utf8mb4', false)) {
  82. $this->defaultConnectionParams['mysql']['charset'] = 'utf8mb4';
  83. }
  84. }
  85. /**
  86. * @brief Get default connection parameters for a given DBMS.
  87. * @param string $type DBMS type
  88. * @throws \InvalidArgumentException If $type is invalid
  89. * @return array Default connection parameters.
  90. */
  91. public function getDefaultConnectionParams($type) {
  92. $normalizedType = $this->normalizeType($type);
  93. if (!isset($this->defaultConnectionParams[$normalizedType])) {
  94. throw new \InvalidArgumentException("Unsupported type: $type");
  95. }
  96. $result = $this->defaultConnectionParams[$normalizedType];
  97. // \PDO::MYSQL_ATTR_FOUND_ROWS may not be defined, e.g. when the MySQL
  98. // driver is missing. In this case, we won't be able to connect anyway.
  99. if ($normalizedType === 'mysql' && defined('\PDO::MYSQL_ATTR_FOUND_ROWS')) {
  100. $result['driverOptions'] = array(
  101. \PDO::MYSQL_ATTR_FOUND_ROWS => true,
  102. );
  103. }
  104. return $result;
  105. }
  106. /**
  107. * @brief Get default connection parameters for a given DBMS.
  108. * @param string $type DBMS type
  109. * @param array $additionalConnectionParams Additional connection parameters
  110. * @return \OC\DB\Connection
  111. */
  112. public function getConnection($type, $additionalConnectionParams) {
  113. $normalizedType = $this->normalizeType($type);
  114. $eventManager = new EventManager();
  115. switch ($normalizedType) {
  116. case 'mysql':
  117. $eventManager->addEventSubscriber(
  118. new SQLSessionInit("SET SESSION AUTOCOMMIT=1"));
  119. break;
  120. case 'oci':
  121. $eventManager->addEventSubscriber(new OracleSessionInit);
  122. // the driverOptions are unused in dbal and need to be mapped to the parameters
  123. if (isset($additionalConnectionParams['driverOptions'])) {
  124. $additionalConnectionParams = array_merge($additionalConnectionParams, $additionalConnectionParams['driverOptions']);
  125. }
  126. $host = $additionalConnectionParams['host'];
  127. $port = isset($additionalConnectionParams['port']) ? $additionalConnectionParams['port'] : null;
  128. $dbName = $additionalConnectionParams['dbname'];
  129. // we set the connect string as dbname and unset the host to coerce doctrine into using it as connect string
  130. if ($host === '') {
  131. $additionalConnectionParams['dbname'] = $dbName; // use dbname as easy connect name
  132. } else {
  133. $additionalConnectionParams['dbname'] = '//' . $host . (!empty($port) ? ":{$port}" : "") . '/' . $dbName;
  134. }
  135. unset($additionalConnectionParams['host']);
  136. break;
  137. case 'sqlite3':
  138. $journalMode = $additionalConnectionParams['sqlite.journal_mode'];
  139. $additionalConnectionParams['platform'] = new OCSqlitePlatform();
  140. $eventManager->addEventSubscriber(new SQLiteSessionInit(true, $journalMode));
  141. break;
  142. }
  143. /** @var Connection $connection */
  144. $connection = DriverManager::getConnection(
  145. array_merge($this->getDefaultConnectionParams($type), $additionalConnectionParams),
  146. new Configuration(),
  147. $eventManager
  148. );
  149. return $connection;
  150. }
  151. /**
  152. * @brief Normalize DBMS type
  153. * @param string $type DBMS type
  154. * @return string Normalized DBMS type
  155. */
  156. public function normalizeType($type) {
  157. return $type === 'sqlite' ? 'sqlite3' : $type;
  158. }
  159. /**
  160. * Checks whether the specified DBMS type is valid.
  161. *
  162. * @param string $type
  163. * @return bool
  164. */
  165. public function isValidType($type) {
  166. $normalizedType = $this->normalizeType($type);
  167. return isset($this->defaultConnectionParams[$normalizedType]);
  168. }
  169. /**
  170. * Create the connection parameters for the config
  171. *
  172. * @return array
  173. */
  174. public function createConnectionParams() {
  175. $type = $this->config->getValue('dbtype', 'sqlite');
  176. $connectionParams = [
  177. 'user' => $this->config->getValue('dbuser', ''),
  178. 'password' => $this->config->getValue('dbpassword', ''),
  179. ];
  180. $name = $this->config->getValue('dbname', self::DEFAULT_DBNAME);
  181. if ($this->normalizeType($type) === 'sqlite3') {
  182. $dataDir = $this->config->getValue("datadirectory", \OC::$SERVERROOT . '/data');
  183. $connectionParams['path'] = $dataDir . '/' . $name . '.db';
  184. } else {
  185. $host = $this->config->getValue('dbhost', '');
  186. $connectionParams = array_merge($connectionParams, $this->splitHostFromPortAndSocket($host));
  187. $connectionParams['dbname'] = $name;
  188. }
  189. $connectionParams['tablePrefix'] = $this->config->getValue('dbtableprefix', self::DEFAULT_DBTABLEPREFIX);
  190. $connectionParams['sqlite.journal_mode'] = $this->config->getValue('sqlite.journal_mode', 'WAL');
  191. //additional driver options, eg. for mysql ssl
  192. $driverOptions = $this->config->getValue('dbdriveroptions', null);
  193. if ($driverOptions) {
  194. $connectionParams['driverOptions'] = $driverOptions;
  195. }
  196. // set default table creation options
  197. $connectionParams['defaultTableOptions'] = [
  198. 'collate' => 'utf8_bin',
  199. 'tablePrefix' => $connectionParams['tablePrefix']
  200. ];
  201. if ($this->config->getValue('mysql.utf8mb4', false)) {
  202. $connectionParams['defaultTableOptions'] = [
  203. 'collate' => 'utf8mb4_bin',
  204. 'charset' => 'utf8mb4',
  205. 'row_format' => 'compressed',
  206. 'tablePrefix' => $connectionParams['tablePrefix']
  207. ];
  208. }
  209. return $connectionParams;
  210. }
  211. /**
  212. * @param string $host
  213. * @return array
  214. */
  215. protected function splitHostFromPortAndSocket($host): array {
  216. $params = [
  217. 'host' => $host,
  218. ];
  219. $matches = [];
  220. if (preg_match('/^(.*):([^\]:]+)$/', $host, $matches)) {
  221. // Host variable carries a port or socket.
  222. $params['host'] = $matches[1];
  223. if (is_numeric($matches[2])) {
  224. $params['port'] = (int) $matches[2];
  225. } else {
  226. $params['unix_socket'] = $matches[2];
  227. }
  228. }
  229. return $params;
  230. }
  231. }