PageRenderTime 50ms CodeModel.GetById 17ms RepoModel.GetById 1ms app.codeStats 0ms

/lib/Database/ez_mysqli.php

http://github.com/jv2222/ezSQL
PHP | 551 lines | 311 code | 87 blank | 153 comment | 52 complexity | 08421196fce65148e1ba49bf0f7d4dbf MD5 | raw file
Possible License(s): LGPL-3.0
  1. <?php
  2. declare(strict_types=1);
  3. namespace ezsql\Database;
  4. use Exception;
  5. use ezsql\ezsqlModel;
  6. use ezsql\ConfigInterface;
  7. use ezsql\DatabaseInterface;
  8. use function ezsql\functions\setInstance;
  9. class ez_mysqli extends ezsqlModel implements DatabaseInterface
  10. {
  11. private $return_val = 0;
  12. private $is_insert = false;
  13. private $isTransactional = false;
  14. /**
  15. * Database connection handle
  16. * @var \mysqli
  17. */
  18. private $dbh;
  19. /**
  20. * Query result
  21. * @var mixed
  22. */
  23. private $result;
  24. /**
  25. * Database configuration setting
  26. * @var ConfigInterface
  27. */
  28. private $database;
  29. protected $shortcutUsed = false;
  30. public function __construct(ConfigInterface $settings = null)
  31. {
  32. if (empty($settings)) {
  33. throw new Exception(\MISSING_CONFIGURATION);
  34. }
  35. parent::__construct();
  36. $this->database = $settings;
  37. if (empty($GLOBALS['ez' . \MYSQLI]))
  38. $GLOBALS['ez' . \MYSQLI] = $this;
  39. setInstance($this);
  40. } // __construct
  41. public function settings()
  42. {
  43. return $this->database;
  44. }
  45. /**
  46. * Short hand way to connect to mysql database server and select a mysql
  47. * database at the same time
  48. *
  49. * @param string $user The database user name
  50. * @param string $password The database users password
  51. * @param string $name The name of the database
  52. * @param string $host The host name or IP address of the database server.
  53. * Default is localhost
  54. * @param string $charset Encoding of the database
  55. * @return boolean
  56. */
  57. public function quick_connect(
  58. string $user = '',
  59. string $password = '',
  60. string $name = '',
  61. string $host = '',
  62. $port = '',
  63. string $charset = ''
  64. ) {
  65. $user = empty($user) ? $this->database->getUser() : $user;
  66. $password = empty($password) ? $this->database->getPassword() : $password;
  67. $name = empty($name) ? $this->database->getName() : $name;
  68. $host = empty($host) ? $this->database->getHost() : $host;
  69. $port = empty($port) ? $this->database->getPort() : $port;
  70. $charset = empty($charset) ? $this->database->getCharset() : $charset;
  71. if (!$this->connect($user, $password, $host, (int) $port, $charset));
  72. else if (!$this->dbSelect($name, $charset));
  73. return $this->_connected;
  74. } // quick_connect
  75. /**
  76. * Try to connect to mySQLi database server
  77. *
  78. * @param string $user The database user name
  79. * @param string $password The database users password
  80. * @param string $host The host name or IP address of the database server.
  81. * Default is localhost
  82. * @param string $charset The database charset
  83. * Default is empty string
  84. * @return boolean
  85. */
  86. public function connect(
  87. string $user = '',
  88. string $password = '',
  89. string $host = '',
  90. $port = '',
  91. string $charset = ''
  92. ) {
  93. $this->_connected = false;
  94. $user = empty($user) ? $this->database->getUser() : $user;
  95. $password = empty($password) ? $this->database->getPassword() : $password;
  96. $host = empty($host) ? $this->database->getHost() : $host;
  97. $port = empty($port) ? $this->database->getPort() : $port;
  98. $charset = empty($charset) ? $this->database->getCharset() : $charset;
  99. // Try to establish the server database handle
  100. if (!$this->dbh = \mysqli_connect($host, $user, $password, $this->database->getName(), (int) $port)) {
  101. $this->register_error(\FAILED_CONNECTION . ' in ' . __FILE__ . ' on line ' . __LINE__);
  102. } else {
  103. \mysqli_set_charset($this->dbh, $charset);
  104. $this->_connected = true;
  105. }
  106. return $this->_connected;
  107. } // connect
  108. /**
  109. * Try to select the default database for mySQL
  110. *
  111. * @param string $name The name of the database
  112. * @param string $charset Encoding of the database
  113. * @return boolean
  114. */
  115. public function dbSelect($name = '', $charset = '')
  116. {
  117. $name = empty($name) ? $this->database->getName() : $name;
  118. try {
  119. // Try to connect to the database
  120. if (($this->dbh === null) || ($this->_connected === false) || !\mysqli_select_db($this->dbh, $name)) {
  121. throw new Exception("Error Processing Request", 1);
  122. }
  123. $this->database->setName($name);
  124. if ($charset == '') {
  125. $charset = $this->database->getCharset();
  126. }
  127. if ($charset != '') {
  128. $encoding = \strtolower(\str_replace('-', '', $charset));
  129. $charsetArray = array();
  130. $recordSet = \mysqli_query($this->dbh, 'SHOW CHARACTER SET');
  131. while ($row = \mysqli_fetch_array($recordSet, \MYSQLI_ASSOC)) {
  132. $charsetArray[] = $row['Charset'];
  133. }
  134. if (\in_array($charset, $charsetArray)) {
  135. \mysqli_query($this->dbh, 'SET NAMES \'' . $encoding . '\'');
  136. }
  137. }
  138. return true;
  139. } catch (\Throwable $e) {
  140. $str = \FAILED_CONNECTION;
  141. // Must have an active database connection
  142. if ($this->dbh && $this->_connected) {
  143. // Try to get error supplied by mysql if not use our own
  144. if (!$str = \mysqli_error($this->dbh)) {
  145. $str = 'Unexpected error while trying to select database';
  146. }
  147. }
  148. $this->register_error($str . ' in ' . __FILE__ . ' on line ' . __LINE__);
  149. return false;
  150. }
  151. } // select
  152. /**
  153. * Format a mySQL string correctly for safe mySQL insert
  154. * (no matter if magic quotes are on or not)
  155. *
  156. * @param string $str
  157. * @return string
  158. */
  159. public function escape(string $str)
  160. {
  161. return \mysqli_real_escape_string($this->dbh, \stripslashes($str));
  162. } // escape
  163. /**
  164. * Return mySQLi specific system date syntax
  165. * i.e. Oracle: SYSDATE Mysql: NOW()
  166. *
  167. * @return string
  168. */
  169. public function sysDate()
  170. {
  171. return 'NOW()';
  172. }
  173. /**
  174. * Helper fetches rows from a prepared result set
  175. * @param \mysqli_stmt $stmt
  176. * @param string $query
  177. * @return bool|\mysqli_result
  178. */
  179. private function fetch_prepared_result(&$stmt, $query)
  180. {
  181. if ($stmt instanceof \mysqli_stmt) {
  182. $stmt->store_result();
  183. $variables = array();
  184. $is_insert = false;
  185. $col_info = array();
  186. if (\preg_match("/^(insert|delete|update|replace)\s+/i", $query)) {
  187. $this->_affectedRows = \mysqli_stmt_affected_rows($stmt);
  188. // Take note of the insert id
  189. if (\preg_match("/^(insert|replace)\s+/i", $query)) {
  190. $this->insertId = $stmt->insert_id;
  191. }
  192. } else {
  193. $this->_affectedRows = $stmt->num_rows;
  194. $meta = $stmt->result_metadata();
  195. $x = 0;
  196. // Take note of column info
  197. while ($field = $meta->fetch_field()) {
  198. $col_info[$field->name] = "";
  199. $variables[$field->name] = &$col_info[$field->name];
  200. $this->colInfo[$x] = $field;
  201. $x++;
  202. }
  203. // Binds variables to a prepared statement for result storage
  204. \call_user_func_array([$stmt, 'bind_result'], \array_values($variables));
  205. $i = 0;
  206. // Store Query Results
  207. while ($stmt->fetch()) {
  208. // Store results as an objects within main array
  209. $resultObject = new \stdClass();
  210. foreach ($variables as $key => $value) {
  211. $resultObject->$key = $value;
  212. }
  213. $this->lastResult[$i] = $resultObject;
  214. $i++;
  215. }
  216. }
  217. // If there is an error then take note of it..
  218. if ($str = $stmt->error) {
  219. $is_insert = true;
  220. $this->register_error($str);
  221. // If debug ALL queries
  222. $this->trace || $this->debugAll ? $this->debug() : null;
  223. return false;
  224. }
  225. // Return number of rows affected
  226. $return_val = $this->_affectedRows;
  227. // disk caching of queries
  228. $this->store_cache($query, $is_insert);
  229. // If debug ALL queries
  230. $this->trace || $this->debugAll ? $this->debug() : null;
  231. return $return_val;
  232. }
  233. return false;
  234. }
  235. /**
  236. * Creates a prepared query, binds the given parameters and returns the result of the executed
  237. * {@link mysqli_stmt}.
  238. * @param string $query
  239. * @param array $param
  240. * @return bool|\mysqli_result
  241. */
  242. public function query_prepared(string $query, array $param = null)
  243. {
  244. $stmt = $this->dbh->prepare($query);
  245. if (!$stmt instanceof \mysqli_stmt) {
  246. if ($this->isTransactional)
  247. throw new \Exception($this->lastError);
  248. return false;
  249. }
  250. $params = [];
  251. $types = \array_reduce(
  252. $param,
  253. function ($string, &$arg) use (&$params) {
  254. $params[] = &$arg;
  255. if (\is_float($arg))
  256. $string .= 'd';
  257. elseif (\is_integer($arg))
  258. $string .= 'i';
  259. elseif (\is_string($arg))
  260. $string .= 's';
  261. else
  262. $string .= 'b';
  263. return $string;
  264. },
  265. ''
  266. );
  267. \array_unshift($params, $types);
  268. \call_user_func_array([$stmt, 'bind_param'], $params);
  269. $result = ($stmt->execute()) ? $this->fetch_prepared_result($stmt, $query) : false;
  270. // free and closes a prepared statement
  271. $stmt->free_result();
  272. $stmt->close();
  273. return $result;
  274. }
  275. /**
  276. * Perform post processing on SQL query call
  277. *
  278. * @param string $query
  279. * @param mixed $result
  280. * @return bool|void
  281. */
  282. private function processQueryResult(string $query, $result = null)
  283. {
  284. $this->shortcutUsed = false;
  285. if (!empty($result))
  286. $this->result = $result;
  287. // If there is an error then take note of it..
  288. if ($str = \mysqli_error($this->dbh)) {
  289. $this->register_error($str);
  290. // If debug ALL queries
  291. $this->trace || $this->debugAll ? $this->debug() : null;
  292. return false;
  293. }
  294. // Query was an insert, delete, update, replace
  295. $this->is_insert = false;
  296. if (\preg_match("/^(insert|delete|update|replace)\s+/i", $query)) {
  297. $this->is_insert = true;
  298. $this->_affectedRows = \mysqli_affected_rows($this->dbh);
  299. // Take note of the insert_id
  300. if (\preg_match("/^(insert|replace)\s+/i", $query)) {
  301. $this->insertId = \mysqli_insert_id($this->dbh);
  302. }
  303. // Return number of rows affected
  304. $this->return_val = $this->_affectedRows;
  305. } else {
  306. // Query was a select
  307. if (!\is_numeric($this->result) && !\is_bool($this->result)) {
  308. // Take note of column info
  309. $i = 0;
  310. while ($i < \mysqli_num_fields($this->result)) {
  311. $this->colInfo[$i] = \mysqli_fetch_field($this->result);
  312. $i++;
  313. }
  314. // Store Query Results
  315. $num_rows = 0;
  316. while ($row = \mysqli_fetch_object($this->result)) {
  317. // Store results as an objects within main array
  318. $this->lastResult[$num_rows] = $row;
  319. $num_rows++;
  320. }
  321. \mysqli_free_result($this->result);
  322. // Log number of rows the query returned
  323. $this->numRows = $num_rows;
  324. // Return number of rows selected
  325. $this->return_val = $this->numRows;
  326. }
  327. }
  328. }
  329. /**
  330. * Perform mySQL query and try to determine result value
  331. *
  332. * @param string $query
  333. * @param bool $use_prepare
  334. * @return bool|mixed
  335. */
  336. public function query(string $query, bool $use_prepare = false)
  337. {
  338. $param = [];
  339. if ($use_prepare)
  340. $param = $this->prepareValues();
  341. // check for ezQuery placeholder tag and replace tags with proper prepare tag
  342. $query = \str_replace(\_TAG, '?', $query);
  343. // Initialize return
  344. $this->return_val = 0;
  345. // Flush cached values..
  346. $this->flush();
  347. // For reg expressions
  348. $query = \trim($query);
  349. // Log how the function was called
  350. $this->log_query("\$db->query(\"$query\")");
  351. // Keep track of the last query for debug..
  352. $this->lastQuery = $query;
  353. // Count how many queries there have been
  354. $this->numQueries++;
  355. // Use core file cache function
  356. if ($cache = $this->get_cache($query)) {
  357. return $cache;
  358. }
  359. // If there is no existing database connection then try to connect
  360. if (!isset($this->dbh) || !$this->dbh) {
  361. $this->connect($this->database->getUser(), $this->database->getPassword(), $this->database->getHost());
  362. $this->dbSelect($this->database->getName());
  363. }
  364. // Perform the query via std mysql_query function..
  365. if (!empty($param) && \is_array($param) && ($this->isPrepareOn())) {
  366. $this->shortcutUsed = true;
  367. return $this->query_prepared($query, $param);
  368. }
  369. $this->result = \mysqli_query($this->dbh, $query);
  370. if ($this->processQueryResult($query) === false) {
  371. if ($this->isTransactional)
  372. throw new \Exception($this->lastError);
  373. return false;
  374. }
  375. // disk caching of queries
  376. $this->store_cache($query, $this->is_insert);
  377. // If debug ALL queries
  378. $this->trace || $this->debugAll ? $this->debug() : null;
  379. return $this->return_val;
  380. } // query
  381. /**
  382. * Close the database connection
  383. */
  384. public function disconnect()
  385. {
  386. if ($this->dbh) {
  387. \mysqli_close($this->dbh);
  388. $this->_connected = false;
  389. }
  390. $this->_connected = false;
  391. }
  392. /**
  393. * Reset database handle
  394. */
  395. public function reset()
  396. {
  397. $this->dbh = null;
  398. }
  399. /**
  400. * Get connection handle
  401. */
  402. public function handle()
  403. {
  404. return $this->dbh;
  405. }
  406. /**
  407. * Returns the current database server host
  408. *
  409. * @return string
  410. */
  411. public function getHost()
  412. {
  413. return $this->database->getHost();
  414. }
  415. /**
  416. * Returns the current database server port
  417. *
  418. * @return string
  419. */
  420. public function getPort()
  421. {
  422. return $this->database->getPort();
  423. }
  424. /**
  425. * Returns the current connection charset
  426. *
  427. * @return string
  428. */
  429. public function getCharset()
  430. {
  431. return $this->database->getCharset();
  432. }
  433. /**
  434. * Returns the last inserted Id - auto generated
  435. *
  436. * @return int
  437. */
  438. public function getInsertId()
  439. {
  440. return \mysqli_insert_id($this->dbh);
  441. } // getInsertId
  442. /**
  443. * Begin Mysql Transaction
  444. */
  445. public function beginTransaction()
  446. {
  447. /* turn autocommit off */
  448. $this->dbh->autocommit(false);
  449. $this->dbh->begin_transaction(\MYSQLI_TRANS_START_READ_WRITE);
  450. $this->isTransactional = true;
  451. }
  452. public function commit()
  453. {
  454. $this->dbh->commit();
  455. $this->dbh->autocommit(true);
  456. $this->isTransactional = false;
  457. }
  458. public function rollback()
  459. {
  460. $this->dbh->rollBack();
  461. $this->dbh->autocommit(true);
  462. $this->isTransactional = false;
  463. }
  464. } // ez_mysqli