pdo.class.php 6.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  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. $count = 0;
  96. $query->execute();
  97. while($query->fetch() !== false){
  98. $count++;
  99. }
  100. if($count == 0){
  101. $count = $query->rowCount();
  102. }
  103. $query->closeCursor();
  104. return $count;
  105. }
  106. public function query(string $statement, int $mode = null, ...$args){
  107. $query = $this->prepare($statement);
  108. if(!is_null($mode)){
  109. $query->setFetchMode($mode, ...$args);
  110. }
  111. $query->execute();
  112. return $query;
  113. }
  114. public function quote($value, $type = \PDO::PARAM_STR){
  115. if($type == \PDO::PARAM_INT){
  116. return (int)$value;
  117. }elseif($type == \PDO::PARAM_BOOL){
  118. return (bool)$value ? "b'1'" : "b'0'";
  119. }elseif($type == \PDO::PARAM_NULL){
  120. return 'null';
  121. }
  122. return $this->pdo->quote((string)$value, $type);
  123. }
  124. public function beginTransaction(...$args){
  125. return $this->pdo->beginTransaction(...$args);
  126. }
  127. public function commit(...$args){
  128. return $this->pdo->commit(...$args);
  129. }
  130. public function rollback(...$args){
  131. return $this->pdo->rollback(...$args);
  132. }
  133. public function setAttribute(...$args){
  134. return $this->pdo->setAttribute(...$args);
  135. }
  136. public function getAttribute(...$args){
  137. return $this->pdo->getAttribute(...$args);
  138. }
  139. public function lastInsertId(...$args){
  140. return $this->pdo->lastInsertId(...$args);
  141. }
  142. public function getError(){
  143. $error = $this->pdo->errorInfo();
  144. return new \Exception($error[2], $error[1]);
  145. }
  146. public function stringFilter(array $filter = null){
  147. $where = '';
  148. if(!is_null($filter)){
  149. $where = 'where ';
  150. foreach($filter as $name => $value){
  151. if($value instanceof Filter){
  152. $where .= "`{$name}` {$value} and";
  153. }else{
  154. if(is_integer($value)){
  155. $type = \PDO::PARAM_INT;
  156. }elseif(is_bool($value)){
  157. $type = \PDO::PARAM_BOOL;
  158. }elseif(is_null($value)){
  159. $type = \PDO::PARAM_NULL;
  160. }else{
  161. $type = \PDO::PARAM_STR;
  162. }
  163. $where .= "`{$name}` = {$this->quote($value, $type)} and";
  164. }
  165. }
  166. $where = rtrim($where, ' and');
  167. }
  168. return $where;
  169. }
  170. public function stringSet(array $data){
  171. $sets = '';
  172. foreach($data as $name => $value){
  173. $sets .= "`{$name}` = {$this->quote($value)},";
  174. }
  175. if(count($sets) > 0){
  176. $sets = 'set '.rtrim($sets, ',');
  177. }
  178. return $sets;
  179. }
  180. public function stringColumn(string $name, array $column){
  181. $default = '';
  182. if(!is_null($column['default'])){
  183. $value = $column['default'];
  184. if($value instanceof DefaultValue){
  185. $default .= " DEFAULT {$value}";
  186. }else{
  187. if(is_integer($value)){
  188. $type = \PDO::PARAM_INT;
  189. }elseif(is_bool($value)){
  190. $type = \PDO::PARAM_BOOL;
  191. }else{
  192. $type = \PDO::PARAM_STR;
  193. }
  194. $default .= " DEFAULT {$this->quote($value, $type)}";
  195. }
  196. }
  197. $null = $column['null'] ? ' NULL' : ' NOT NULL';
  198. $ai = $column['increment'] ? ' AUTO_INCREMENT' : '';
  199. return "`{$name}` {$column['type']}{$null}{$default}{$ai}";
  200. }
  201. public function stringIndex(string $name, array $idx){
  202. if($idx['unique']){
  203. return "constraint unique index {$name} (".implode(',', $idx['columns']).")";
  204. }else{
  205. return "index {$name} (".implode(',', $idx['columns']).")";
  206. }
  207. }
  208. public function stringForeignKey(string $name, array $foreignKey){
  209. $cols0 = '';
  210. $cols1 = '';
  211. foreach($foreignKey['columns'] as $row){
  212. $cols0 .= "{$row[0]},";
  213. $cols1 .= "{$row[1]},";
  214. }
  215. $cols0 = rtrim($cols0, ',');
  216. $cols1 = rtrim($cols1, ',');
  217. return "constraint `{$name}` foreign key ({$cols0}) references `{$foreignKey['references']}` ({$cols1})";
  218. }
  219. }
  220. ?>