pdo.class.php 6.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233
  1. <?php
  2. namespace Juju;
  3. require_once('Data/securestring.class.php');
  4. require_once('PDO/transaction.class.php');
  5. require_once('PDO/defaultvalue.class.php');
  6. require_once('PDO/filter.class.php');
  7. require_once('PDO/table.class.php');
  8. use Juju\{Data\SecureString, PDO\DefaultValue, PDO\Filter, PDO\Table, PDO\Transaction};
  9. class PDO {
  10. public static function from(string $dsnstring){
  11. $parts = explode(':', $dsnstring);
  12. $dsnstring = $parts[1];
  13. $dsn = explode(';', $dsnstring);
  14. $dsn = array_reduce($dsn, function($dsn, $item){
  15. $item = explode('=', $item);
  16. $dsn[$item[0]] = $item[1];
  17. return $dsn;
  18. });
  19. if(!isset($dsn['host'])){
  20. throw new \Exception("DSN '{$dsnstring}' missing host");
  21. }
  22. if(!isset($dsn['dbname'])){
  23. throw new \Exception("DSN '{$dsnstring}' missing dbname");
  24. }
  25. if(!isset($dsn['user'])){
  26. $user = $dsn['dbname'];
  27. }else{
  28. $user = $dsn['user'];
  29. }
  30. if(!isset($dsn['pass'])){
  31. $dsn['pass'] = $user;
  32. }
  33. $pass = SecureString::from($dsn['pass']);
  34. unset($dsn['pass'], $dsn['user']);
  35. $dsn = array_reduce(array_keys($dsn), function($a, $key) use($dsn){
  36. $a[] = "{$key}={$dsn[$key]}";
  37. return $a;
  38. });
  39. $dsnstring = $parts[0].':'.implode(';', $dsn);
  40. return new PDO($dsnstring, $user, $pass);
  41. }
  42. private $pdo;
  43. private function __construct(string $dsn, string $user, SecureString $pass){
  44. $mysql = explode(':', $dsn)[0] === 'mysql';
  45. $options = [];
  46. if($mysql){
  47. $options[\PDO::MYSQL_ATTR_INIT_COMMAND] = "SET NAMES 'UTF8'";
  48. $options[\PDO::MYSQL_ATTR_MULTI_STATEMENTS] = false;
  49. $options[\PDO::MYSQL_ATTR_FOUND_ROWS] = true;
  50. }
  51. $pdo = new \PDO($dsn, $user, (string)$pass, $options);
  52. $pdo->setAttribute(\PDO::ATTR_AUTOCOMMIT, true);
  53. $pdo->setAttribute(\PDO::ATTR_CASE, \PDO::CASE_LOWER);
  54. $pdo->setAttribute(\PDO::ATTR_EMULATE_PREPARES, false);
  55. $pdo->setAttribute(\PDO::ATTR_DEFAULT_FETCH_MODE, \PDO::FETCH_ASSOC);
  56. $this->pdo = $pdo;
  57. }
  58. public function transaction(callable $fn){
  59. $pdo = $this->pdo;
  60. if($pdo->inTransaction()){
  61. throw new \Exception("Unable to start a new transaction");
  62. }
  63. $transaction = new Transaction($this);
  64. if($fn($transaction) === false){
  65. do{
  66. $transaction->rollback();
  67. }while($transaction->savepoint);
  68. $pdo->rollback();
  69. }else{
  70. $transaction->commit();
  71. if($pdo->inTransaction()){
  72. $pdo->commit();
  73. }
  74. }
  75. unset($transaction);
  76. $pdo->setAttribute(\PDO::ATTR_AUTOCOMMIT, true);
  77. return $this;
  78. }
  79. public function table(string $name){
  80. return new Table($this, $name);
  81. }
  82. public function prepare(string $statement, array $options = []){
  83. $pdo = $this->pdo;
  84. if($pdo->getAttribute(\PDO::ATTR_DRIVER_NAME) == 'mysql'){
  85. $options[\PDO::MYSQL_ATTR_USE_BUFFERED_QUERY] = true;
  86. }
  87. $query = $pdo->prepare($statement);
  88. if($query === false){
  89. throw $this->getError();
  90. }
  91. return $query;
  92. }
  93. public function exec(string $statement){
  94. $query = $this->prepare($statement);
  95. $query->execute();
  96. $count = 0;
  97. while($query->fetch() !== false){
  98. $count++;
  99. }
  100. if($count === 0){
  101. $query2 = $this->query("select ROW_COUNT()");
  102. $count = $query2->fetchColumn();
  103. $query2->closeCursor();
  104. if($count === 0){
  105. $count = $query->rowCount();
  106. }
  107. }
  108. $query->closeCursor();
  109. return $count;
  110. }
  111. public function query(string $statement, int $mode = null, ...$args){
  112. $query = $this->prepare($statement);
  113. if(!is_null($mode)){
  114. $query->setFetchMode($mode, ...$args);
  115. }
  116. $query->execute();
  117. return $query;
  118. }
  119. public function quote($value, $type = \PDO::PARAM_STR){
  120. if($type == \PDO::PARAM_INT){
  121. return (int)$value;
  122. }elseif($type == \PDO::PARAM_BOOL){
  123. return (bool)$value ? "b'1'" : "b'0'";
  124. }elseif($type == \PDO::PARAM_NULL){
  125. return 'null';
  126. }
  127. return $this->pdo->quote((string)$value, $type);
  128. }
  129. public function beginTransaction(...$args){
  130. return $this->pdo->beginTransaction(...$args);
  131. }
  132. public function commit(...$args){
  133. return $this->pdo->commit(...$args);
  134. }
  135. public function rollback(...$args){
  136. return $this->pdo->rollback(...$args);
  137. }
  138. public function setAttribute(...$args){
  139. return $this->pdo->setAttribute(...$args);
  140. }
  141. public function getAttribute(...$args){
  142. return $this->pdo->getAttribute(...$args);
  143. }
  144. public function lastInsertId(...$args){
  145. $id = $this->pdo->lastInsertId(...$args);
  146. if($id === 0){
  147. $query = $this->pdo->query("select LAST_INSERT_ID()");
  148. $id = $query->fetchColumn();
  149. $query->closeCursor();
  150. }
  151. return $id;
  152. }
  153. public function getError(){
  154. $error = $this->pdo->errorInfo();
  155. return new \Exception($error[2], $error[1]);
  156. }
  157. public function stringFilter(array $filter = null){
  158. $where = '';
  159. if(!is_null($filter)){
  160. $where = 'where ';
  161. foreach($filter as $name => $value){
  162. if($value instanceof Filter){
  163. $where .= "`{$name}` {$value} and";
  164. }else{
  165. if(is_integer($value)){
  166. $type = \PDO::PARAM_INT;
  167. }elseif(is_bool($value)){
  168. $type = \PDO::PARAM_BOOL;
  169. }elseif(is_null($value)){
  170. $type = \PDO::PARAM_NULL;
  171. }else{
  172. $type = \PDO::PARAM_STR;
  173. }
  174. $where .= "`{$name}` = {$this->quote($value, $type)} and";
  175. }
  176. }
  177. $where = rtrim($where, ' and');
  178. }
  179. return $where;
  180. }
  181. public function stringSet(array $data){
  182. $sets = '';
  183. foreach($data as $name => $value){
  184. $sets .= "`{$name}` = {$this->quote($value)},";
  185. }
  186. if(count($sets) > 0){
  187. $sets = 'set '.rtrim($sets, ',');
  188. }
  189. return $sets;
  190. }
  191. public function stringColumn(string $name, array $column){
  192. $default = '';
  193. if(!is_null($column['default'])){
  194. $value = $column['default'];
  195. if($value instanceof DefaultValue){
  196. $default .= " DEFAULT {$value}";
  197. }else{
  198. if(is_integer($value)){
  199. $type = \PDO::PARAM_INT;
  200. }elseif(is_bool($value)){
  201. $type = \PDO::PARAM_BOOL;
  202. }else{
  203. $type = \PDO::PARAM_STR;
  204. }
  205. $default .= " DEFAULT {$this->quote($value, $type)}";
  206. }
  207. }
  208. $null = $column['null'] ? ' NULL' : ' NOT NULL';
  209. $ai = $column['increment'] ? ' AUTO_INCREMENT' : '';
  210. return "`{$name}` {$column['type']}{$null}{$default}{$ai}";
  211. }
  212. public function stringIndex(string $name, array $idx){
  213. if($idx['unique']){
  214. return "constraint unique index {$name} (".implode(',', $idx['columns']).")";
  215. }else{
  216. return "index {$name} (".implode(',', $idx['columns']).")";
  217. }
  218. }
  219. public function stringForeignKey(string $name, array $foreignKey){
  220. $cols0 = '';
  221. $cols1 = '';
  222. foreach($foreignKey['columns'] as $row){
  223. $cols0 .= "{$row[0]},";
  224. $cols1 .= "{$row[1]},";
  225. }
  226. $cols0 = rtrim($cols0, ',');
  227. $cols1 = rtrim($cols1, ',');
  228. return "constraint `{$name}` foreign key ({$cols0}) references `{$foreignKey['references']}` ({$cols1})";
  229. }
  230. }
  231. ?>