PageRenderTime 46ms CodeModel.GetById 17ms RepoModel.GetById 0ms app.codeStats 0ms

/src/ARC2/Store/Adapter/PDOAdapter.php

http://github.com/semsol/arc2
PHP | 357 lines | 278 code | 22 blank | 57 comment | 9 complexity | cbac7416faec8583acdc8f6dd23fee06 MD5 | raw file
Possible License(s): GPL-3.0
  1. <?php
  2. /**
  3. * Adapter to enable usage of PDO functions.
  4. *
  5. * @author Benjamin Nowack <bnowack@semsol.com>
  6. * @author Konrad Abicht <konrad.abicht@pier-and-peer.com>
  7. * @license W3C Software License and GPL
  8. * @homepage <https://github.com/semsol/arc2>
  9. */
  10. namespace ARC2\Store\Adapter;
  11. use Exception;
  12. /**
  13. * PDO Adapter - Handles database operations using PDO.
  14. *
  15. * This adapter doesn't support SQLite, please use PDOSQLiteAdapter instead.
  16. */
  17. class PDOAdapter extends AbstractAdapter
  18. {
  19. public function checkRequirements()
  20. {
  21. if (false == \extension_loaded('pdo_mysql')) {
  22. throw new Exception('Extension pdo_mysql is not loaded.');
  23. }
  24. if ('mysql' != $this->configuration['db_pdo_protocol']) {
  25. throw new Exception('Only "mysql" protocol is supported at the moment.');
  26. }
  27. }
  28. public function getAdapterName()
  29. {
  30. return 'pdo';
  31. }
  32. public function getAffectedRows(): int
  33. {
  34. return $this->lastRowCount;
  35. }
  36. /**
  37. * Connect to server or storing a given connection.
  38. *
  39. * @param EasyDB $existingConnection default is null
  40. */
  41. public function connect($existingConnection = null)
  42. {
  43. // reuse a given existing connection.
  44. // it assumes that $existingConnection is a PDO connection object
  45. if (null !== $existingConnection) {
  46. $this->db = $existingConnection;
  47. // create your own connection
  48. } elseif (false === $this->db instanceof \PDO) {
  49. /*
  50. * build connection string
  51. *
  52. * - db_pdo_protocol: Protocol to determine server, e.g. mysql
  53. */
  54. if (false == isset($this->configuration['db_pdo_protocol'])) {
  55. throw new \Exception('When using PDO the protocol has to be given (e.g. mysql). Please set db_pdo_protocol in database configuration.');
  56. }
  57. $dsn = $this->configuration['db_pdo_protocol'].':host='.$this->configuration['db_host'];
  58. if (isset($this->configuration['db_name'])) {
  59. $dsn .= ';dbname='.$this->configuration['db_name'];
  60. }
  61. // port
  62. $dsn .= ';port=';
  63. $dsn .= isset($this->configuration['db_port']) ? $this->configuration['db_port'] : 3306;
  64. // set charset
  65. $dsn .= ';charset=utf8mb4';
  66. $this->db = new \PDO(
  67. $dsn,
  68. $this->configuration['db_user'],
  69. $this->configuration['db_pwd']
  70. );
  71. $this->db->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
  72. // errors DONT lead to exceptions
  73. // set to false for compatibility reasons with mysqli. ARC2 using mysqli does not throw any
  74. // exceptions, instead collects errors in a hidden array.
  75. $this->db->setAttribute(\PDO::ATTR_ERRMODE, \PDO::ERRMODE_EXCEPTION);
  76. // default fetch mode is associative
  77. $this->db->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
  78. // from source: http://php.net/manual/de/ref.pdo-mysql.php
  79. // If this attribute is set to TRUE on a PDOStatement, the MySQL driver will use
  80. // the buffered versions of the MySQL API. But we wont rely on that, setting it false.
  81. $this->db->setAttribute(\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY, false);
  82. // in MySQL, this setting allows bigger JOINs
  83. $stmt = $this->db->prepare('SET SESSION SQL_BIG_SELECTS=1');
  84. $stmt->execute();
  85. $stmt->closeCursor();
  86. /*
  87. * with MySQL 5.6 we ran into exceptions like:
  88. * PDOException: SQLSTATE[42000]: Syntax error or access violation:
  89. * 1140 In aggregated query without GROUP BY, expression #1 of SELECT list contains
  90. * nonaggregated column 'testdb.T_0_0_0.p'; this is incompatible with
  91. * sql_mode=only_full_group_by
  92. *
  93. * the following query makes this right.
  94. * FYI: https://stackoverflow.com/questions/23921117/disable-only-full-group-by
  95. */
  96. $stmt = $this->db->prepare("SET sql_mode = ''");
  97. $stmt->execute();
  98. $stmt->closeCursor();
  99. }
  100. return $this->db;
  101. }
  102. /**
  103. * @return void
  104. */
  105. public function disconnect()
  106. {
  107. // FYI: https://stackoverflow.com/questions/18277233/pdo-closing-connection
  108. $this->db = null;
  109. }
  110. public function escape($value)
  111. {
  112. $quoted = $this->db->quote($value);
  113. /*
  114. * fixes the case, that we have double quoted strings like:
  115. * ''x1''
  116. *
  117. * remember, this value will be surrounded by quotes later on!
  118. * so we don't send it back with quotes around.
  119. */
  120. if ("'" == substr($quoted, 0, 1)) {
  121. $quoted = substr($quoted, 1, \strlen($quoted) - 2);
  122. }
  123. return $quoted;
  124. }
  125. /**
  126. * @param string $sql
  127. *
  128. * @return array
  129. */
  130. public function fetchList($sql)
  131. {
  132. // save query
  133. $this->queries[] = [
  134. 'query' => $sql,
  135. 'by_function' => 'fetchList',
  136. ];
  137. if (null == $this->db) {
  138. $this->connect();
  139. }
  140. $stmt = $this->db->prepare($sql);
  141. $stmt->execute();
  142. $rows = $stmt->fetchAll();
  143. $stmt->closeCursor();
  144. return $rows;
  145. }
  146. public function fetchRow($sql)
  147. {
  148. // save query
  149. $this->queries[] = [
  150. 'query' => $sql,
  151. 'by_function' => 'fetchRow',
  152. ];
  153. if (null == $this->db) {
  154. $this->connect();
  155. }
  156. $row = false;
  157. $stmt = $this->db->prepare($sql);
  158. $stmt->execute();
  159. $rows = $stmt->fetchAll();
  160. if (0 < \count($rows)) {
  161. $row = array_values($rows)[0];
  162. }
  163. $stmt->closeCursor();
  164. return $row;
  165. }
  166. public function getCollation()
  167. {
  168. $row = $this->fetchRow('SHOW TABLE STATUS LIKE "'.$this->getTablePrefix().'setting"');
  169. if (isset($row['Collation'])) {
  170. return $row['Collation'];
  171. } else {
  172. return '';
  173. }
  174. }
  175. public function getConnection()
  176. {
  177. return $this->db;
  178. }
  179. public function getConnectionId()
  180. {
  181. return $this->db->query('SELECT CONNECTION_ID()')->fetch(\PDO::FETCH_ASSOC);
  182. }
  183. public function getDBSName()
  184. {
  185. if (null == $this->db) {
  186. return;
  187. }
  188. $clientVersion = strtolower($this->db->getAttribute(\PDO::ATTR_CLIENT_VERSION));
  189. $serverVersion = strtolower($this->db->getAttribute(\PDO::ATTR_SERVER_VERSION));
  190. if (false !== strpos($clientVersion, 'mariadb') || false !== strpos($serverVersion, 'mariadb')) {
  191. $return = 'mariadb';
  192. } elseif (false !== strpos($clientVersion, 'mysql') || false !== strpos($serverVersion, 'mysql')) {
  193. $return = 'mysql';
  194. } else {
  195. $return = null;
  196. }
  197. return $return;
  198. }
  199. public function getServerInfo()
  200. {
  201. return $this->db->getAttribute(\constant('PDO::ATTR_CLIENT_VERSION'));
  202. }
  203. /**
  204. * Returns the version of the database server like 05-00-12.
  205. */
  206. public function getServerVersion()
  207. {
  208. $res = preg_match(
  209. "/([0-9]+)\.([0-9]+)\.([0-9]+)/",
  210. $this->getServerInfo(),
  211. $matches
  212. );
  213. return 1 == $res
  214. ? sprintf('%02d-%02d-%02d', $matches[1], $matches[2], $matches[3])
  215. : '00-00-00';
  216. }
  217. public function getErrorCode()
  218. {
  219. return $this->db->errorCode();
  220. }
  221. public function getErrorMessage()
  222. {
  223. return $this->db->errorInfo()[2];
  224. }
  225. public function getLastInsertId()
  226. {
  227. return $this->db->lastInsertId();
  228. }
  229. public function getNumberOfRows($sql)
  230. {
  231. // save query
  232. $this->queries[] = [
  233. 'query' => $sql,
  234. 'by_function' => 'getNumberOfRows',
  235. ];
  236. $stmt = $this->db->prepare($sql);
  237. $stmt->execute();
  238. $rowCount = \count($stmt->fetchAll());
  239. $stmt->closeCursor();
  240. return $rowCount;
  241. }
  242. public function getStoreName()
  243. {
  244. if (isset($this->configuration['store_name'])) {
  245. return $this->configuration['store_name'];
  246. }
  247. return 'arc';
  248. }
  249. public function getTablePrefix()
  250. {
  251. $prefix = '';
  252. if (isset($this->configuration['db_table_prefix'])) {
  253. $prefix = $this->configuration['db_table_prefix'].'_';
  254. }
  255. $prefix .= $this->getStoreName().'_';
  256. return $prefix;
  257. }
  258. /**
  259. * @param string $sql Query
  260. *
  261. * @return bool true if query ran fine, false otherwise
  262. */
  263. public function simpleQuery($sql)
  264. {
  265. // save query
  266. $this->queries[] = [
  267. 'query' => $sql,
  268. 'by_function' => 'simpleQuery',
  269. ];
  270. if (false === $this->db instanceof \PDO) {
  271. $this->connect();
  272. }
  273. $stmt = $this->db->prepare($sql);
  274. $stmt->execute();
  275. $this->lastRowCount = $stmt->rowCount();
  276. $stmt->closeCursor();
  277. return true;
  278. }
  279. /**
  280. * Encapsulates internal PDO::exec call. This allows us to extend it, e.g. with caching functionality.
  281. *
  282. * @param string $sql
  283. *
  284. * @return int number of affected rows
  285. */
  286. public function exec($sql)
  287. {
  288. // save query
  289. $this->queries[] = [
  290. 'query' => $sql,
  291. 'by_function' => 'exec',
  292. ];
  293. if (null == $this->db) {
  294. $this->connect();
  295. }
  296. return $this->db->exec($sql);
  297. }
  298. }