table.class.php 9.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296
  1. <?php
  2. namespace Juju\PDO;
  3. require_once(realpath(dirname(__DIR__).'/pdo.class.php'));
  4. use Juju\{PDO, PDO\Transaction};
  5. class Table {
  6. private $pdo;
  7. private $name;
  8. private $exists;
  9. private $columns;
  10. private $primaryKey;
  11. private $index;
  12. private $foreignKey;
  13. public function __construct($pdo, string $name){
  14. if($pdo instanceof PDO || $pdo instanceof Transaction){
  15. $this->pdo = $pdo;
  16. $this->name = $name;
  17. $this->rollback();
  18. }else{
  19. throw new \Exception("Invalid pdo argument");
  20. }
  21. }
  22. public function __get($name){
  23. switch($name){
  24. case 'exists':case 'name':case 'columns':
  25. return $this->$name;
  26. break;
  27. default:
  28. throw new \Exception("Invalid property {$name}");
  29. }
  30. }
  31. public function exists(){
  32. $pdo = $this->pdo;
  33. try{
  34. $count = $pdo->exec("select count(1) from `{$this->name}` limit 1");
  35. $this->exists = $count > 0;
  36. }catch(\Exception $e){
  37. $this->exists = false;
  38. }
  39. return $this->exists;
  40. }
  41. public function describe(){
  42. $this->columns = [];
  43. $this->primaryKey = [];
  44. $this->index = [];
  45. $this->foreignKeys = [];
  46. if($this->exists){
  47. $pdo = $this->pdo;
  48. $query = $pdo->query("show create table `{$this->name}`");
  49. $sql = $query->fetch()['create table'];
  50. $query->closeCursor();
  51. if(preg_match_all('/ PRIMARY KEY \(((?:`[^`]+`,?)+)\)/s', $sql, $primaryKey, PREG_SET_ORDER) > 0){
  52. $this->primaryKey = array_map(
  53. function($item){
  54. return preg_replace('/`([^`]+)`/', '\\1', $item);
  55. },
  56. array_merge(
  57. ...array_map(
  58. function($item){
  59. return explode(',', $item[1]);
  60. }, $primaryKey
  61. )
  62. )
  63. );
  64. }
  65. unset($primaryKey);
  66. if(preg_match_all('/ ((?:UNIQUE)? ?KEY) `([^`]+)` \(((?:`[^`]+`,?)+)\)/s', $sql, $indexes, PREG_SET_ORDER) > 0){
  67. $this->indexes = array_reduce($indexes, function($indexes, $item){
  68. $indexes[$item[2]] = [
  69. 'unique'=> $item[1] == 'UNIQUE KEY',
  70. 'columns'=> array_map(function($item){
  71. return preg_replace('/`([^`]+)`/', '\\1', $item);
  72. }, explode(',', $item[3])),
  73. 'dirty'=> false
  74. ];
  75. return $indexes;
  76. });
  77. }
  78. unset($indexes);
  79. if(preg_match_all('/ CONSTRAINT `([^`]+)` FOREIGN KEY \(((?:`[^`]+`(?:, )?)+)\) REFERENCES `([^`]+)` \(((?:`[^`]+`(?:, )?)+)\)/s', $sql, $foreignKeys, PREG_SET_ORDER, 0) > 0){
  80. $this->foreignKeys = array_reduce($foreignKeys, function($foreignKeys, $item) use($sql){
  81. $columns = explode(',', $item[2]);
  82. $matches = explode(',', $item[4]);
  83. foreach($columns as $key => &$column){
  84. $column = [
  85. preg_replace('/`([^`]+)`/', '\\1', $column),
  86. preg_replace('/`([^`]+)`/', '\\1', $matches[$key])
  87. ];
  88. }
  89. $foreignKeys[$item[1]] = [
  90. 'references'=> $item[3],
  91. 'columns'=> $columns
  92. ];
  93. return $foreignKeys;
  94. });
  95. }
  96. unset($foreignKeys);
  97. if(preg_match_all('/ `([^`]+)` ([^\(]+\([^\)]+\))[^,\n]*,?/s', $sql, $columns, PREG_SET_ORDER) > 0){
  98. foreach($columns as $column){
  99. $default = null;
  100. $line = $column[0];
  101. $line .= substr($line, -1, 1) === ',' ? '' : ',';
  102. if(preg_match('/ `[^`]+` [^\(]+\([^\)]+\)[^D$]+DEFAULT (?!NULL)(.+)(?: AUTO_INCREMENT|UNIQUE|KEY|PRIMARY|COMMENT|COLUMN_FORMAT|STORAGE|REFERENCES|,)/u', $line, $match) == 1){
  103. $default = preg_replace("/'(.+)'/", "\\1", $match[1]);
  104. }
  105. $this->column(
  106. $column[1], // name
  107. $column[2], // type
  108. $default, // default
  109. preg_match('/`[^`]+` [^\(]+\([^\)]+\) NOT NULL/', $column[0]) === 1, // null
  110. preg_match('/ `[^`]+` [^\(]+\([^\)]+\)[^A$]+AUTO_INCREMENT,?/u', $line) == 1 // increment
  111. );
  112. $this->columns[$column[1]]['dirty'] = false;
  113. }
  114. }
  115. unset($columns);
  116. }
  117. }
  118. public function stringFilter(...$args){
  119. return $this->pdo->stringFilter(...$args);
  120. }
  121. public function stringSet(...$args){
  122. return $this->pdo->stringSet(...$args);
  123. }
  124. public function commit(){
  125. $pdo = $this->pdo;
  126. if(!$this->exists){
  127. $columns = '';
  128. foreach($this->columns as $name => $column){
  129. $default = '';
  130. if(!is_null($column['default'])){
  131. $default .= "DEFAULT {$pdo->quote($column['default'])}";
  132. }
  133. $null = $column['null'] ? 'NULL' : 'NOT NULL';
  134. $ai = $column['increment'] ? 'AUTO_INCREMENT' : '';
  135. $columns .= "{$name} {$column['type']} {$null} {$default} {$ai},";
  136. }
  137. if(count($columns) > 0){
  138. $columns = rtrim($columns, ',');
  139. }
  140. $pk = $this->primaryKey();
  141. if(count($pk)){
  142. $pk = ", primary key (".implode(',',$pk).")";
  143. }
  144. $index = '';
  145. if(count($this->index) > 0){
  146. foreach($this->index as $name => $idx){
  147. if($idx['unique']){
  148. $index .= ", constraint unique index {$name} (".implode(',', $idx['columns']).")";
  149. }else{
  150. $index .= ", index {$name} (".implode(',', $idx['columns']).")";
  151. }
  152. }
  153. }
  154. $fk = '';
  155. if(count($this->foreignKeys) > 0){
  156. foreach($this->foreignKeys as $name => $k){
  157. $cols0 = '';
  158. $cols1 = '';
  159. foreach($k['columns'] as $row){
  160. $cols0 .= "{$row[0]},";
  161. $cols1 .= "{$row[1]},";
  162. }
  163. $cols0 = rtrim($cols0, ',');
  164. $cols1 = rtrim($cols1, ',');
  165. $fk .= ", constraint `{$name}` foreign key ({$cols0}) references `{$k['references']}` ({$cols1})";
  166. }
  167. }
  168. $count = $pdo->exec("create table `{$this->name}` ({$columns}{$pk}{$index}{$fk})");
  169. if($count === false){
  170. throw $pdo->getError();
  171. }
  172. if(!$this->exists()){
  173. throw new \Exception("Unable to create table {$this->name}");
  174. }
  175. $this->exists();
  176. }else{
  177. // @todo alter table to add and remove columns
  178. $columns = array_filter($this->columns, function($item){
  179. return $item['dirty'];
  180. });
  181. }
  182. $this->describe();
  183. }
  184. public function rollback(){
  185. $this->exists();
  186. $this->describe();
  187. }
  188. public function drop(){
  189. if($this->exists){
  190. $this->pdo->exec("drop table `{$this->name}`");
  191. }
  192. }
  193. public function column(string $name, string $type = null, string $default = null, bool $null = false, bool $increment = false){
  194. if(!is_null($type)){
  195. $this->columns[$name] = [
  196. 'type'=> $type,
  197. 'default'=> $default,
  198. 'null'=> $null,
  199. 'increment'=> $increment,
  200. 'dirty'=> true
  201. ];
  202. $column['dirty'] = true;
  203. return $this;
  204. }else{
  205. return isset($this->columns[$name]) ? $this->columns[$name] : null;
  206. }
  207. }
  208. public function index(string $name, array $columns = null, bool $unique = false){
  209. if(!is_null($columns)){
  210. foreach($columns as $column){
  211. if(!isset($this->columns[$column])){
  212. throw new \Exception("Can't add index. Column {$this->name}.{$column} doesn't exist");
  213. }
  214. }
  215. $this->index[$name] = [
  216. 'columns'=> $columns,
  217. 'unique'=> $unique,
  218. 'dirty'=>true
  219. ];
  220. return $this;
  221. }else{
  222. return isset($this->index[$name]) ? $this->index[$name] : null;
  223. }
  224. }
  225. public function addToIndex(string $name, string $column){
  226. if(!isset($this->index[$name])){
  227. throw new \Exception("Can't add column to index. Index {$this->name}.{$name} doesn't exist");
  228. }
  229. if(!isset($this->columns[$column])){
  230. throw new \Exception("Can't add column to index. Column {$this->name}.{$column} doesn't exist");
  231. }
  232. $this->index[$name]['columns'] = array_merge($this->index[$name]['columns'], [$column]);
  233. }
  234. public function foreignKey(string $name, string $references = null, array $columns = []){
  235. if(!is_null($references)){
  236. $table = $this->pdo->table($references);
  237. if(!$table->exists){
  238. throw new \Exception("Can't create foreign key {$name}. Table {$references} does not exist.");
  239. }
  240. foreach($columns as $column){
  241. if(is_null($this->column($column[0]))){
  242. throw new \Exception("Can't create foreign key {$name}. Column {$this->name}.{$column[0]} does not exist");
  243. }
  244. if(is_null($table->column($column[1]))){
  245. throw new \Exception("Can't create foreign key {$name}. Column {$references}.{$column[1]} does not exist");
  246. }
  247. }
  248. $this->foreignKeys[$name] = [
  249. 'references'=> $references,
  250. 'columns'=> $columns
  251. ];
  252. return $this;
  253. }else{
  254. return isset($this->foreignKeys[$name]) ? $this->foreignKeys[$name] : null;
  255. }
  256. }
  257. public function primaryKey(...$columns){
  258. if(count($columns) > 0){
  259. foreach($columns as $column){
  260. if(!isset($this->columns[$column])){
  261. throw new \Exception("Can't add Primary key. Column {$this->name}.{$column} doesn't exist");
  262. }
  263. }
  264. $this->primaryKey = $columns;
  265. return $this;
  266. }else{
  267. return $this->primaryKey;
  268. }
  269. }
  270. public function insert(array $data){
  271. return $this->exists ? $this->pdo->exec("insert into `{$this->name}` {$this->stringSet($data)}") : 0;
  272. }
  273. public function update(array $data, array $filter = null){
  274. return $this->exists ? $this->pdo->exec("update `{$this->name} {$this->stringSet($data)} {$this->stringFilter($filter)}`") : 0;
  275. }
  276. public function delete(array $filter = null){
  277. return $this->exists ? $this->pdo->exec("delete from `{$this->name}` {$this->stringFilter($filter)}") : 0;
  278. }
  279. public function fetch(array $filter = null){
  280. if($this->exists){
  281. $query = $this->pdo->query("select * from `{$this->name}` {$this->stringFilter($filter)}");
  282. $results = $query->fetchAll();
  283. $query->closeCursor();
  284. }else{
  285. $results = [];
  286. }
  287. return $results;
  288. }
  289. public function count(array $filter = null){
  290. return $this->exists ? $this->pdo->exec("select 1 from `{$this->name}` {$this->stringFilter($filter)}") : 0;
  291. }
  292. }
  293. ?>