Database.php 9.5 KB

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