MysqliConnection.php 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303
  1. <?php
  2. namespace Doctrine\DBAL\Driver\Mysqli;
  3. use Doctrine\DBAL\Driver\Connection;
  4. use Doctrine\DBAL\Driver\PingableConnection;
  5. use Doctrine\DBAL\Driver\ServerInfoAwareConnection;
  6. use Doctrine\DBAL\ParameterType;
  7. use mysqli;
  8. use const MYSQLI_INIT_COMMAND;
  9. use const MYSQLI_OPT_CONNECT_TIMEOUT;
  10. use const MYSQLI_OPT_LOCAL_INFILE;
  11. use const MYSQLI_READ_DEFAULT_FILE;
  12. use const MYSQLI_READ_DEFAULT_GROUP;
  13. use const MYSQLI_SERVER_PUBLIC_KEY;
  14. use function defined;
  15. use function floor;
  16. use function func_get_args;
  17. use function in_array;
  18. use function ini_get;
  19. use function mysqli_errno;
  20. use function mysqli_error;
  21. use function mysqli_init;
  22. use function mysqli_options;
  23. use function restore_error_handler;
  24. use function set_error_handler;
  25. use function sprintf;
  26. use function stripos;
  27. class MysqliConnection implements Connection, PingableConnection, ServerInfoAwareConnection
  28. {
  29. /**
  30. * Name of the option to set connection flags
  31. */
  32. public const OPTION_FLAGS = 'flags';
  33. /** @var mysqli */
  34. private $conn;
  35. /**
  36. * @param mixed[] $params
  37. * @param string $username
  38. * @param string $password
  39. * @param mixed[] $driverOptions
  40. *
  41. * @throws MysqliException
  42. */
  43. public function __construct(array $params, $username, $password, array $driverOptions = [])
  44. {
  45. $port = $params['port'] ?? ini_get('mysqli.default_port');
  46. // Fallback to default MySQL port if not given.
  47. if (! $port) {
  48. $port = 3306;
  49. }
  50. $socket = $params['unix_socket'] ?? ini_get('mysqli.default_socket');
  51. $dbname = $params['dbname'] ?? null;
  52. $flags = $driverOptions[static::OPTION_FLAGS] ?? null;
  53. $this->conn = mysqli_init();
  54. $this->setSecureConnection($params);
  55. $this->setDriverOptions($driverOptions);
  56. set_error_handler(static function () {
  57. });
  58. try {
  59. if (! $this->conn->real_connect($params['host'], $username, $password, $dbname, $port, $socket, $flags)) {
  60. throw new MysqliException($this->conn->connect_error, $this->conn->sqlstate ?? 'HY000', $this->conn->connect_errno);
  61. }
  62. } finally {
  63. restore_error_handler();
  64. }
  65. if (! isset($params['charset'])) {
  66. return;
  67. }
  68. $this->conn->set_charset($params['charset']);
  69. }
  70. /**
  71. * Retrieves mysqli native resource handle.
  72. *
  73. * Could be used if part of your application is not using DBAL.
  74. *
  75. * @return mysqli
  76. */
  77. public function getWrappedResourceHandle()
  78. {
  79. return $this->conn;
  80. }
  81. /**
  82. * {@inheritdoc}
  83. *
  84. * The server version detection includes a special case for MariaDB
  85. * to support '5.5.5-' prefixed versions introduced in Maria 10+
  86. *
  87. * @link https://jira.mariadb.org/browse/MDEV-4088
  88. */
  89. public function getServerVersion()
  90. {
  91. $serverInfos = $this->conn->get_server_info();
  92. if (stripos($serverInfos, 'mariadb') !== false) {
  93. return $serverInfos;
  94. }
  95. $majorVersion = floor($this->conn->server_version / 10000);
  96. $minorVersion = floor(($this->conn->server_version - $majorVersion * 10000) / 100);
  97. $patchVersion = floor($this->conn->server_version - $majorVersion * 10000 - $minorVersion * 100);
  98. return $majorVersion . '.' . $minorVersion . '.' . $patchVersion;
  99. }
  100. /**
  101. * {@inheritdoc}
  102. */
  103. public function requiresQueryForServerVersion()
  104. {
  105. return false;
  106. }
  107. /**
  108. * {@inheritdoc}
  109. */
  110. public function prepare($prepareString)
  111. {
  112. return new MysqliStatement($this->conn, $prepareString);
  113. }
  114. /**
  115. * {@inheritdoc}
  116. */
  117. public function query()
  118. {
  119. $args = func_get_args();
  120. $sql = $args[0];
  121. $stmt = $this->prepare($sql);
  122. $stmt->execute();
  123. return $stmt;
  124. }
  125. /**
  126. * {@inheritdoc}
  127. */
  128. public function quote($input, $type = ParameterType::STRING)
  129. {
  130. return "'" . $this->conn->escape_string($input) . "'";
  131. }
  132. /**
  133. * {@inheritdoc}
  134. */
  135. public function exec($statement)
  136. {
  137. if ($this->conn->query($statement) === false) {
  138. throw new MysqliException($this->conn->error, $this->conn->sqlstate, $this->conn->errno);
  139. }
  140. return $this->conn->affected_rows;
  141. }
  142. /**
  143. * {@inheritdoc}
  144. */
  145. public function lastInsertId($name = null)
  146. {
  147. return $this->conn->insert_id;
  148. }
  149. /**
  150. * {@inheritdoc}
  151. */
  152. public function beginTransaction()
  153. {
  154. $this->conn->query('START TRANSACTION');
  155. return true;
  156. }
  157. /**
  158. * {@inheritdoc}
  159. */
  160. public function commit()
  161. {
  162. return $this->conn->commit();
  163. }
  164. /**
  165. * {@inheritdoc}non-PHPdoc)
  166. */
  167. public function rollBack()
  168. {
  169. return $this->conn->rollback();
  170. }
  171. /**
  172. * {@inheritdoc}
  173. */
  174. public function errorCode()
  175. {
  176. return $this->conn->errno;
  177. }
  178. /**
  179. * {@inheritdoc}
  180. */
  181. public function errorInfo()
  182. {
  183. return $this->conn->error;
  184. }
  185. /**
  186. * Apply the driver options to the connection.
  187. *
  188. * @param mixed[] $driverOptions
  189. *
  190. * @throws MysqliException When one of of the options is not supported.
  191. * @throws MysqliException When applying doesn't work - e.g. due to incorrect value.
  192. */
  193. private function setDriverOptions(array $driverOptions = [])
  194. {
  195. $supportedDriverOptions = [
  196. MYSQLI_OPT_CONNECT_TIMEOUT,
  197. MYSQLI_OPT_LOCAL_INFILE,
  198. MYSQLI_INIT_COMMAND,
  199. MYSQLI_READ_DEFAULT_FILE,
  200. MYSQLI_READ_DEFAULT_GROUP,
  201. ];
  202. if (defined('MYSQLI_SERVER_PUBLIC_KEY')) {
  203. $supportedDriverOptions[] = MYSQLI_SERVER_PUBLIC_KEY;
  204. }
  205. $exceptionMsg = "%s option '%s' with value '%s'";
  206. foreach ($driverOptions as $option => $value) {
  207. if ($option === static::OPTION_FLAGS) {
  208. continue;
  209. }
  210. if (! in_array($option, $supportedDriverOptions, true)) {
  211. throw new MysqliException(
  212. sprintf($exceptionMsg, 'Unsupported', $option, $value)
  213. );
  214. }
  215. if (@mysqli_options($this->conn, $option, $value)) {
  216. continue;
  217. }
  218. $msg = sprintf($exceptionMsg, 'Failed to set', $option, $value);
  219. $msg .= sprintf(', error: %s (%d)', mysqli_error($this->conn), mysqli_errno($this->conn));
  220. throw new MysqliException(
  221. $msg,
  222. $this->conn->sqlstate,
  223. $this->conn->errno
  224. );
  225. }
  226. }
  227. /**
  228. * Pings the server and re-connects when `mysqli.reconnect = 1`
  229. *
  230. * @return bool
  231. */
  232. public function ping()
  233. {
  234. return $this->conn->ping();
  235. }
  236. /**
  237. * Establish a secure connection
  238. *
  239. * @param mixed[] $params
  240. *
  241. * @throws MysqliException
  242. */
  243. private function setSecureConnection(array $params)
  244. {
  245. if (! isset($params['ssl_key']) &&
  246. ! isset($params['ssl_cert']) &&
  247. ! isset($params['ssl_ca']) &&
  248. ! isset($params['ssl_capath']) &&
  249. ! isset($params['ssl_cipher'])
  250. ) {
  251. return;
  252. }
  253. $this->conn->ssl_set(
  254. $params['ssl_key'] ?? null,
  255. $params['ssl_cert'] ?? null,
  256. $params['ssl_ca'] ?? null,
  257. $params['ssl_capath'] ?? null,
  258. $params['ssl_cipher'] ?? null
  259. );
  260. }
  261. }