pdo.class.php 6.1 KB

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