Database.php 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. <?php
  2. class Database
  3. {
  4. /**
  5. * @var Database
  6. */
  7. protected static $instance = null;
  8. /**
  9. * @var mysqli
  10. */
  11. protected $db;
  12. /**
  13. * @var string
  14. */
  15. protected $config;
  16. /**
  17. * @var string
  18. */
  19. protected $lastQuery;
  20. /**
  21. * @param string $host
  22. * @param string $user
  23. * @param string $password
  24. * @param string $database
  25. *
  26. * @throws Exception
  27. */
  28. protected function __construct($host, $user, $password, $database)
  29. {
  30. if(!static::isInitialized()){
  31. $this->config = $database;
  32. try{
  33. mysqli_report(MYSQLI_REPORT_ERROR | MYSQLI_REPORT_STRICT);
  34. $this->db = new mysqli($host, $user, $password, $database);
  35. }
  36. catch(mysqli_sql_exception $e){
  37. throw new Exception('Unable to connect to the database.', 0, $e);
  38. }
  39. }
  40. }
  41. protected function __clone()
  42. {
  43. }
  44. /**
  45. * @return Database
  46. *
  47. * @throws Exception
  48. */
  49. public static function getInstance()
  50. {
  51. if(!static::isInitialized()){
  52. throw new Exception('Database must be initialized before using it (see Database::init).');
  53. }
  54. return static::$instance;
  55. }
  56. /**
  57. * @param Database $instance
  58. *
  59. * @codeCoverageIgnore
  60. */
  61. protected static function setInstance($instance)
  62. {
  63. static::$instance = $instance;
  64. }
  65. /**
  66. * @param string $host
  67. * @param string $user
  68. * @param string $password
  69. * @param string $database
  70. *
  71. * @throws InvalidArgumentException
  72. * @throws Exception
  73. *
  74. * @codeCoverageIgnore
  75. */
  76. public static function init($host, $user = null, $password = null, $database = null)
  77. {
  78. if(!static::isInitialized()){
  79. if(is_array($host)){
  80. $database = isset($host['database']) ? $host['database'] : $database;
  81. $password = isset($host['password']) ? $host['password'] : $password;
  82. $user = isset($host['user']) ? $host['user'] : $user;
  83. $host = isset($host['host']) ? $host['host'] : $host;
  84. }
  85. if(is_null($host) || is_null($user) || is_null($password) || is_null($database)){
  86. throw new InvalidArgumentException('Missing parameters for database initialization.');
  87. }
  88. static::setInstance(
  89. new static($host, $user, $password, $database)
  90. );
  91. }
  92. }
  93. /**
  94. * @return bool
  95. */
  96. public static function isInitialized()
  97. {
  98. return !is_null(static::$instance);
  99. }
  100. /**
  101. * Execute query
  102. *
  103. * @param string $query
  104. *
  105. * @return bool|mysqli_result
  106. *
  107. * @throws DatabaseException
  108. */
  109. public function query($query)
  110. {
  111. $this->lastQuery = $query;
  112. $result = $this->db->query($query);
  113. if($this->db->errno !== 0){
  114. $ex = new DatabaseException('There was an error running the query ['.$this->db->error.']');
  115. if(!is_null($this->lastQuery)){
  116. $ex->setQuery($this->lastQuery);
  117. }
  118. throw $ex;
  119. }
  120. return $result;
  121. }
  122. /**
  123. * @return mixed
  124. */
  125. public function getInsertId()
  126. {
  127. return $this->db->insert_id;
  128. }
  129. /**
  130. * Escape string
  131. *
  132. * @param string $input
  133. *
  134. * @return string
  135. */
  136. public function escape($input)
  137. {
  138. return $this->db->real_escape_string($input);
  139. }
  140. /**
  141. * @param string $table
  142. * @param array $conditions
  143. * @param string $conditionConnector
  144. * @param null $orderBy
  145. * @param int $limit
  146. *
  147. * @return bool|mysqli_result
  148. *
  149. * @throws DatabaseException
  150. */
  151. public function select($table, $conditions = array(), $conditionConnector = 'AND', $orderBy = null, $limit = 0)
  152. {
  153. return $this->query(
  154. sprintf(
  155. "SELECT * FROM `%s` %s%s%s",
  156. $table,
  157. static::helperWhere($conditions, $conditionConnector),
  158. static::helperOrderBy($orderBy),
  159. static::helperLimit($limit)
  160. )
  161. );
  162. }
  163. /**
  164. * Insert into table
  165. *
  166. * @param string $table
  167. * @param array $values
  168. *
  169. * @return mixed
  170. *
  171. * @throws DatabaseException
  172. */
  173. public function insert($table, $values)
  174. {
  175. if(count($values) === 0){
  176. return null;
  177. }
  178. $this->query(
  179. sprintf(
  180. "INSERT INTO `%s` (%s) VALUES %s",
  181. $table,
  182. static::helperAttributeList(array_keys($values)),
  183. static::helperValueList(array_values($values))
  184. )
  185. );
  186. return $this->getInsertId();
  187. }
  188. /**
  189. * Update table
  190. *
  191. * @param string $table
  192. * @param array $values
  193. * @param array $conditions
  194. * @param string $conditionConnector
  195. *
  196. * @throws DatabaseException
  197. */
  198. public function update($table, $values, $conditions = array(), $conditionConnector = 'AND')
  199. {
  200. if(count($values) === 0){
  201. return;
  202. }
  203. $sqlValues = array();
  204. foreach($values as $attribute => $value){
  205. $sqlValues[] = array($attribute, '=', $value);
  206. }
  207. $this->query(
  208. sprintf(
  209. "UPDATE `%s` SET %s %s",
  210. $table,
  211. static::helperConditionList($sqlValues, ','),
  212. static::helperWhere($conditions, $conditionConnector)
  213. )
  214. );
  215. }
  216. /**
  217. * Count in table
  218. *
  219. * @param string $table
  220. * @param string $byAttribute
  221. * @param array $conditions
  222. * @param string $conditionConnector
  223. *
  224. * @return int
  225. *
  226. * @throws DatabaseException
  227. */
  228. public function count($table, $byAttribute, $conditions = array(), $conditionConnector = 'AND')
  229. {
  230. $result = $this->query(
  231. sprintf(
  232. "SELECT COUNT(`%s`) FROM `%s` %s",
  233. $byAttribute,
  234. $table,
  235. static::helperWhere($conditions, $conditionConnector)
  236. )
  237. );
  238. return intval($result->fetch_array(MYSQLI_NUM)[0]);
  239. }
  240. /**
  241. * @param string $table
  242. * @param string $attribute
  243. * @param mixed $value
  244. *
  245. * @throws DatabaseException
  246. */
  247. public function delete($table, $attribute, $value)
  248. {
  249. $sql = sprintf(
  250. "DELETE FROM `%s` %s",
  251. $table,
  252. static::helperWhere(array($attribute, $value))
  253. );
  254. $this->query($sql);
  255. }
  256. /**
  257. * @param string $potentialKeyword
  258. *
  259. * @return bool
  260. */
  261. protected static function isKeyword($potentialKeyword)
  262. {
  263. return in_array(
  264. strtoupper($potentialKeyword),
  265. array('AS', 'ASC', 'DESC')
  266. );
  267. }
  268. /**
  269. * @param array $attributes
  270. *
  271. * @return string
  272. */
  273. public static function helperAttributeList($attributes)
  274. {
  275. $sqlAttributes = array();
  276. foreach($attributes as $attribute){
  277. if(is_string($attribute)){ // raw
  278. $sqlAttributes[] = $attribute;
  279. continue;
  280. }
  281. if(!is_array($attribute)){
  282. $attribute = array($attribute);
  283. }
  284. $sqlPieces = array();
  285. for($i = 0; $i < count($attribute); ++$i){
  286. if(static::isKeyword($attribute[$i])){
  287. $sqlPieces[] = sprintf("%s", $attribute[$i]);
  288. }
  289. elseif(isset($attribute[$i + 1]) && !static::isKeyword($attribute[$i + 1])){
  290. $sqlPieces[] = sprintf("`%s`.`%s`", $attribute[$i], $attribute[++$i]);
  291. }
  292. else{
  293. $sqlPieces[] = sprintf("`%s`", $attribute[$i]);
  294. }
  295. }
  296. $sqlAttributes[] = implode(" ", $sqlPieces);
  297. }
  298. return sprintf(
  299. "%s",
  300. implode(', ', $sqlAttributes)
  301. );
  302. }
  303. /**
  304. * @param mixed $value
  305. *
  306. * @return string
  307. */
  308. public static function helperValue($value)
  309. {
  310. if(is_null($value) || (is_string($value) && strtoupper($value) === 'NULL')){
  311. return "NULL";
  312. }
  313. elseif(is_array($value)){
  314. return static::helperValueList($value);
  315. }
  316. return sprintf(
  317. "'%s'",
  318. static::getInstance()->escape($value)
  319. );
  320. }
  321. /**
  322. * @param array $values
  323. *
  324. * @return string
  325. */
  326. public static function helperValueList($values)
  327. {
  328. $sqlValues = array();
  329. foreach($values as $val){
  330. $sqlValues[] = static::helperValue($val);
  331. }
  332. return sprintf(
  333. "(%s)",
  334. implode(', ', $sqlValues)
  335. );
  336. }
  337. /**
  338. * @param array $conditions
  339. * array('attr', '=', '3') => "`attr` = '3'"
  340. * array(
  341. * array('`attr` = '3') (raw SQL) => `attr` = '3'
  342. * array('attr', 3) => `attr` = '3'
  343. * array('attr', '=', '3') => `attr` = '3'
  344. * array('attr', '<=', 3) => `attr` <= '3'
  345. * array('attr', 'LIKE', '%asd') => `attr` LIKE '%asd'
  346. * array('attr', 'IS', null) => `attr` IS NULL
  347. * array('attr', 'IS NOT', null) => `attr` IS NOT NULL
  348. * )
  349. * @param string $conditionConnector AND, OR
  350. *
  351. * @return string
  352. */
  353. public static function helperConditionList($conditions, $conditionConnector = 'AND')
  354. {
  355. // detect non nested array
  356. if(count($conditions) > 0 && !is_array($conditions[0])){
  357. $conditions = array($conditions);
  358. }
  359. $conditionConnector = strtoupper($conditionConnector);
  360. if(in_array($conditionConnector, array('AND', 'OR'))){
  361. $conditionConnector = " ".$conditionConnector;
  362. }
  363. $values = array();
  364. foreach($conditions as $val){
  365. switch(count($val)){
  366. case 1:
  367. // raw
  368. $values[] = $val;
  369. break;
  370. case 2:
  371. $v = static::helperValue($val[1]);
  372. $values[] = sprintf("`%s` = %s", $val[0], $v);
  373. break;
  374. case 3:
  375. $v = static::helperValue($val[2]);
  376. $values[] = sprintf("`%s` %s %s", $val[0], strtoupper($val[1]), $v);
  377. break;
  378. }
  379. }
  380. return implode($conditionConnector." ", $values);
  381. }
  382. /**
  383. * @param array $conditions
  384. * @param string $conditionConnector AND, OR
  385. *
  386. * @return string
  387. */
  388. public static function helperWhere($conditions, $conditionConnector = 'AND')
  389. {
  390. if(count($conditions) > 0){
  391. return sprintf(
  392. " WHERE %s",
  393. static::helperConditionList($conditions, $conditionConnector)
  394. );
  395. }
  396. return "";
  397. }
  398. /**
  399. * @param array|null $orderBy Examples below:
  400. * null => ""
  401. * array() => ""
  402. * array('attr1' => 'asc', 'attr2' => 'desc') => " ORDER BY `attr1` ASC, `attr2` DESC"
  403. * array('attr1') => " ORDER BY `attr1` ASC"
  404. *
  405. * @return string
  406. */
  407. public static function helperOrderBy($orderBy = null)
  408. {
  409. if(is_null($orderBy) || count($orderBy) === 0){
  410. return "";
  411. }
  412. $values = array();
  413. foreach($orderBy as $key => $val){
  414. if(is_int($key)){
  415. $values[] = array($val);
  416. }
  417. else{
  418. $values[] = array($key, strtoupper($val));
  419. }
  420. }
  421. return sprintf(
  422. " ORDER BY %s",
  423. static::helperAttributeList($values)
  424. );
  425. }
  426. /**
  427. * @param int|array $limit
  428. * 0 => ""
  429. * 3 => " LIMIT 3"
  430. * array(3, 4) => " LIMIT 3,4"
  431. *
  432. * @return string
  433. */
  434. public static function helperLimit($limit = 0)
  435. {
  436. if(is_array($limit) && count($limit) == 2){
  437. $limit = $limit[0].",".$limit[1];
  438. }
  439. if(is_string($limit) || (is_int($limit) && $limit > 0)){
  440. return sprintf(
  441. " LIMIT %s",
  442. $limit
  443. );
  444. }
  445. return "";
  446. }
  447. }