MysqliConnection.php 8.2 KB

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