· 10 years ago · Aug 20, 2016, 11:42 PM
1<?php
2/**
3 * Class untuk berinteraksi dengan database
4 *
5 * @author Khaerul Amin
6 * @version 1.0
7 * @license FreeBSD
8 */
9
10if (!defined('HARMONI')) {
11 exit;
12}
13
14class DataBase {
15 /**
16 * Konstan untuk index numeric dan asosiatif
17 *
18 */
19 const BOTH = 1;
20
21 /**
22 * Konstan untuk index numeric
23 *
24 */
25 const NUM = 2;
26
27 /**
28 * Konstan untuk index asosiatif
29 *
30 */
31 const ASSOC = 4;
32
33 /**
34 * Konstan untuk objek
35 *
36 */
37 const OBJ = 8;
38
39 /**
40 * Konstan untuk mendapatkan data sekaligus
41 *
42 * @var int
43 * @access public
44 * @constant
45 */
46 const ALL = 16;
47
48 /**
49 * Konstan untuk mengurutkan hasil query secara ascending (a-z)
50 *
51 * @var int
52 * @access public
53 * @constant
54 */
55 const ASC = 32;
56
57 /**
58 * Konstan untuk mengurutkan hasil query secara descending (z-a)
59 *
60 * @var int
61 * @access public
62 * @constant
63 */
64 const DESC = 64;
65
66 /**
67 * Tempat untuk menyimpan koneksi ke database
68 *
69 * @var object PDO
70 * @access protected
71 */
72 protected static $pdo_dbh;
73
74 /**
75 * Tempat untuk menyimpan hasil query terakhir
76 *
77 * @var obj
78 * @access protected
79 */
80 protected $pdo_stmt;
81
82 /**
83 * Internal token untuk membuat perintah sql
84 *
85 * @var array
86 * @access protected
87 */
88 protected $arr_token;
89
90 /**
91 * Array konfigurasi untuk terhubung ke database
92 *
93 * @var array
94 * @access protected
95 */
96 protected $arr_config;
97
98 /**
99 * Punctuation untuk klausa and dan or
100 *
101 * @var string
102 * @access protected
103 */
104 protected $str_punc;
105
106 /**
107 * Perintah yang sedang dijalankan
108 *
109 * @var string
110 * @access protected
111 */
112 protected $str_cmd;
113
114
115 /**
116 * Membuat instance baru
117 *
118 * @param array $config array konfigurasi untuk mengakses database
119 * @return object PDO
120 * @access public
121 * @constructor
122 */
123 function __construct(array $config) {
124 $this->str_punc = '';
125 $this->str_cmd = '';
126 $this->arr_token = [
127 'select' => [],
128 'insert' => [],
129 'update' => [],
130 'delete' => [],
131 'create' => [],
132 'clone' => [],
133 'copy' => []
134 ];
135 $this->arr_config = $config;
136 if (self::$pdo_dbh === null) {
137 $this->internalConnect();
138 }
139 }
140
141 /**
142 * Destructor untuk class ini
143 *
144 * @return void
145 * @access public
146 */
147 function __destruct() {
148 self::$pdo_dbh = null;
149 $this->pdo_stmt = null;
150 }
151
152 /**
153 * Fungsi untuk menjalankan query string
154 *
155 * @return bool true jika berhasil dijalankan atau false jika gagal
156 * @access public
157 */
158 function exec() {
159 $this->pdo_stmt = null;
160 $data = $this->internalBuildQuery();
161
162 $sql = $data[0]; $filler = $data[1];
163 $stmt = self::$pdo_dbh->prepare($sql);
164
165 if ($filler) {
166 $filler = $this->internalGetType($filler);
167
168 // Isi semua placeholder
169 foreach ($filler as $f) {
170 $stmt->bindValue($f['index'], $f['value'], $f['type']);
171 }
172 }
173
174 $stmt->execute();
175 $this->pdo_stmt = $stmt;
176
177 return $this;
178 }
179
180 /**
181 * Mengambil nilai dari hasil PDO::exec
182 *
183 * Fungsi ini hanya boleh dipanggil ketika perintah sebelumnya adalah
184 * DataBase::select jika tidak maka akan menampilkan error
185 *
186 * @param mixed &$var variable yang akan menerima hasil dari PDO::fetch
187 * @param int $flag key yang akan dipakai
188 * @return bool true jika masih ada data
189 * @access public
190 */
191 function fetch(&$var, $flag = self::BOTH) {
192 $cmd = $this->str_cmd;
193 $stmt = $this->pdo_stmt;
194
195 // Handle pemanggilan yang invalid
196 if ($cmd !== 'select') {
197 throw new Exception('DataBase::fetch: Hanya bisa dipanggil jika perintah terakhirnya DataBase::select');
198 }
199
200 // Handle jika sudah tidak ada data lagi
201 if ($stmt === null) {
202 $var = null;
203 return false;
204 }
205
206 // Handle type berdasarkan flag.
207 if (self::BOTH & $flag) {
208 $type = PDO::FETCH_BOTH;
209 }
210
211 else if (self::ASSOC & $flag) {
212 $type = PDO::FETCH_ASSOC;
213 }
214
215 else if (self::NUM & $flag) {
216 $type = PDO::FETCH_NUM;
217 }
218
219 else if (self::OBJ & $flag) {
220 $type = PDO::FETCH_OBJ;
221 }
222
223 else {
224 $type = PDO::FETCH_BOTH;
225 }
226
227 // Handle jika user ingin mengambil data sekaligus
228 if (self::ALL & $flag) {
229 $var = $stmt->fetchAll($type);
230 $this->pdo_stmt = null;
231
232 return true;
233 }
234
235 $var = $stmt->fetch($type);
236
237 !$var && $this->pdo_stmt = null;
238
239 return $var !== false;
240 }
241
242 /**
243 * Shortcut untuk DataBase::select('*', blablabla) yang terlihat nanggung
244 *
245 * @param string $table nama table tempat data berada
246 * @return self
247 * @access public
248 */
249 function selectAll($table) {
250 return $this->select([ '*' ], $table);
251 }
252
253 /**
254 * Fungsi untuk menjalankan Query yang rumit
255 *
256 * @param string $query kueri yang akan dieksekusi
257 * @return mixed
258 * @access public
259 */
260 function query($query) {
261 return self::$pdo_dbh->query($query);
262 }
263
264 /**
265 * Membuat perintah SELECT di sql
266 *
267 * @param mixed $column array yang berisi nama kolom yang akan dipilih. akan di rubah ke array jika bulan array
268 * @param string $table nama table yang akan dipilih
269 * @return self
270 * @access public
271 */
272 function select($column, $table) {
273 $this->str_cmd = 'select';
274
275 // Reset setiap pemanggilan agar tidak terjadi konflik
276 $this->arr_token['select'] = [];
277 $token =& $this->arr_token['select'];
278
279 !is_array($column) && $column = [ $column ];
280
281 $token['column'] = $column;
282 $token['table'] = $table;
283 $token['where'] = [];
284 $token['filter'] = [];
285 $token['tokenize'] = true;
286
287 return $this;
288 }
289
290 /**
291 * Membuat perintah INSERT di sql
292 *
293 * @param string $table nama table yang akan dimasukan data baru
294 * @param array $data nama kolom dan value yang akan dimasukan
295 * @return self
296 * @access public
297 */
298 function insert($table, array $data) {
299 $this->str_cmd = 'insert';
300
301 // Reset setiap pemanggilan agar tidak terjadi konflik
302 $this->arr_token['insert'] = [];
303 $token =& $this->arr_token['insert'];
304
305 $token['table'] = $table;
306 $token['column'] = array_keys($data);
307 $token['data'] = array_values($data);
308 $token['where'] = [];
309 $token['tokenize'] = true;
310
311 return $this;
312 }
313
314 /**
315 * Menjalankan perintah UPDATE di sql
316 *
317 * @param string $table table yang akan diubah
318 * @param array $data kolom yang akan dirubah dan nilai barunya
319 * @return self
320 * @access public
321 */
322 function update($table, array $data) {
323 $this->str_cmd = 'update';
324
325 // Reset setiap pemanggilan agar tidak terjadi konflik
326 $this->arr_token['update'] = [];
327 $token =& $this->arr_token['update'];
328
329 $token['table'] = $table;
330 $token['column'] = array_keys($data);
331 $token['data'] = array_values($data);
332 $token['where'] = [];
333 $token['tokenize'] = true;
334
335 return $this;
336 }
337
338 /**
339 * Menjalankan fungsi DELETE di sql
340 *
341 * Untuk alasan keamanan database, fungsi ini akan menampilkan error jika
342 * dipanggil tanpa ada WHERE klausa karena bisa menghapus semua data dalam suatu table
343 *
344 * @param string $table nama table dimana data akan dihapus
345 * @return self
346 * @access public
347 */
348 function delete($table) {
349 $this->str_cmd = 'delete';
350
351 // Reset setiap pemanggilan agar tidak terjadi konflik
352 $this->arr_token['delete'] = [];
353 $token =& $this->arr_token['delete'];
354
355 $token['table'] = $table;
356 $token['where'] = [];
357 $token['tokenize'] = true;
358
359 return $this;
360 }
361
362 /**
363 * Menjalankan perintah CREATE TABLE di sql
364 *
365 * @param string $table nama table yang akan dibuat
366 * @param array $data array untuk nama kolom dan tipe data
367 * misal: ['id' => 'SERIAL']
368 * @param array $config konfigurasi meliputi:
369 * [x] use_if => true Haruskah menggunakan klausa IF NOT EXISTS
370 * [x] truncate => false Akan menghapus table jika table sudah ada
371 * [x] engine => InnoDB default storage engine
372 * [x] charset => utf-8 default charset
373 * [x] primary_key => ''
374 * [x] unique => '' kolom yang akan menjadi index
375 * @return self
376 * @access public
377 */
378 function create($table, array $data = [], array $config = []) {
379 $this->str_cmd = 'create';
380
381 // Reset setiap pemanggilan agar tidak terjadi konflik
382 $this->arr_token['create'] = [];
383 $token =& $this->arr_token['create'];
384
385 // Default config
386 // TODO: Mensupport multiple unique dan primary key
387 static $default_config = [
388 'use_if' => true,
389 'truncate' => false,
390 'engine' => 'InnoDB',
391 'charset' => 'utf8',
392 'primary_key' => '',
393 'unique' => ''
394 ];
395
396 $config = array_merge($default_config, $config);
397
398 $token['table'] = $table;
399 $token['data'] = $data;
400 $token['config'] = $config;
401 $token['where'] = [];
402 $token['tokenize'] = true;
403
404 return $this;
405 }
406
407 /**
408 * Membuat perintah CREATE TABLE LIE di sql
409 *
410 * Fungsi ini bertugas untuk mengcopy table satu ketable yang lain dalam satu database
411 * Fungsi ini hanya mengcopy tablenya saja, jika ingin isinya juga gunakan DataBase::clone
412 * Jika parameter pertama beripa array, maka akan diasumsikan bahwa key berupa $original_table dan valuenya $new_table
413 *
414 * @param string $original_table table asal yang akan dicopy
415 * @param string $new_table nama table hasil dari copyan
416 * @param array $config array konfigurasi meliputi:
417 * [x] use_if => true Jika true maka akan menggunakan klausa IF NOT EXIST
418 * [x] truncate => false Jika true akan menghapus table yang sudah ada
419 * @return self
420 * @access public
421 */
422 function cloneTable($original_table, $new_table = '', array $config = []) {
423 $this->str_cmd = 'clone';
424
425 // Reset dalam setiap pemanggilan
426 $this->arr_token['clone'] = [];
427 $token =& $this->arr_token['clone'];
428
429 static $default_config = [
430 'use_if' => true,
431 'truncate' => false
432 ];
433
434 $config = array_merge($default_config, $config);
435 $data = $original_table;
436 $query = '';
437
438 if (is_string($original_table)) {
439 // Jika $original_table string, $new_table tidak boleh kosong
440 if (empty($new_table)) {
441 throw new Exception('DataBase::cloneTable: Nama table baru tidak boleh kosong');
442 }
443
444 $data = [ $original_table => $new_table ];
445 }
446
447 foreach ($data as $old => $new) {
448 if (empty($old) || empty($new)) {
449 throw new Exception(__METHOD__ . ': Original Table atau New Table tidak boleh kosong');
450 }
451
452 if ($config['truncate']) {
453 $query .= "DROP TABLE IF EXISTS {$new};";
454 }
455
456 $query .= $config['use_if'] ?
457 "CREATE TABLE IF NOT EXISTS {$new} LIKE {$old};" :
458 "CREATE TABLE {$new} LIKE {$old};";
459 }
460
461 $token['tokenize'] = false;
462 $token['query'] = $query;
463
464 return $this;
465 }
466
467 /**
468 * Membuat perintah SELECT INTO di sql
469 *
470 * @param array|string $column nama kolom yang akan diclone. Jika berupa string akan dirubah kebentuk array
471 * @param string $original_table nama tabledimana data akan diambil
472 * @param atring $target_table table dimana data baru akan ditempatkan
473 * @param array|string $dest kolom tempat data akan dimasukan
474 * @return self
475 * @access public
476 */
477 function copyTable($original_table, $target_table, $column, $dest = '') {
478 $this->str_cmd = 'copy';
479
480 // Reset dalam setiap pemanggilan
481 $this->arr_token['copy'] = [];
482 $token =& $this->arr_token['copy'];
483
484 // Rubah kebentuk array jika bukan array
485 if (!is_array($column)) {
486 $column = [ $column ];
487 }
488
489 if (!is_array($dest)) {
490 $dest = [ $dest ];
491 }
492
493 $token['original'] = $original_table;
494 $token['target'] = $target_table;
495 $token['column'] = $column;
496 $token['destination'] = $dest;
497 $token['where'] = [];
498 $token['tokenize'] = true;
499
500 return $this;
501 }
502
503 /**
504 * Shortcut untuk mengklon seluruh data dari satu table ke table yang lain
505 *
506 * @param string $original_table table dimana data berada
507 * @param string $target_table table data akab disimpan
508 * @return self
509 * @access public
510 */
511 function copyAll($original_table, $target_table) {
512 return $this->copyTable($original_table, $target_table, [ '*' ], [ '' ]);
513 }
514
515 /**
516 * Membuat WHERE klausa di sql
517 *
518 * Jika parameter pertama array maka akan diasumsikan itu adalah multiple
519 * where dimana key berupa nama kolom dan value berupa nilai-nya dengan tanda
520 * penghubung sama dengan (=) dan setiap where dipisahkan dengan Klausa AND
521 *
522 * @param mixed $column nama kolom yang akan ditest
523 * @param mixed $condition kondisi yang akan dites (=|!=|<>|like|..)
524 * @param mixed $value nilai yang akan ditest
525 * @return self
526 * @access public
527 */
528 function where($column, $condition = '', $value = '') {
529 // Perintah yang dijalankan saat ini. (SELECT|UPDATE|DELETE|..)
530 $cmd = $this->str_cmd;
531
532 // Handle penggunaan where yang tidak valid
533 if ($cmd === '') {
534 // TODO: Membuat class DataBaseException
535 throw new Exception('DataBase::where: Penggunaan where yang tidak benar');
536 }
537
538 $token =& $this->arr_token[$cmd];
539
540 // Handle simple where
541 if (!is_array($column)) {
542 $token['where'][] = [
543 'punctuation' => $this->str_punc,
544 'column' => $column,
545 'condition' => $condition,
546 'value' => $value
547 ];
548 $this->str_punc = '';
549
550 return $this;
551 }
552
553 // Ok. jadi ini adalah multiple where jadi
554 $first = true;
555 foreach ($column as $column => $value) {
556 $punc = 'AND';
557
558 // Handle pertama kali
559 if ($first) {
560 $first = false;
561 $punc = $this->str_punc;
562 $this->str_punc = '';
563 }
564
565 $token['where'][] = [
566 'punctuation' => $punc,
567 'column' => $column,
568 'value' => $value,
569 'condition' => '='
570 ];
571 }
572
573 return $this;
574 }
575
576 /**
577 * Membuat klausa ORDER | LIMIT | OFFSET di perintah sql
578 *
579 * Jika perintah yang dijalankan bukan DataBase::select maka akan diabaikan secara tersembunyi
580 *
581 * @param array $preference konfigurasi yang diharapkan
582 * @return self
583 * @access public
584 */
585 function filter(array $preference) {
586 // Kembali, jika bukan select
587 if ($this->str_cmd !== 'select') {
588 return $this;
589 }
590
591 $filter =& $this->arr_token['select']['filter'];
592
593 // foreach lebih praktis
594 if (isset($preference['order'])) {
595 $order = $preference['order'];
596 if (!is_array($order)) {
597 $col = $order;
598 $dir = 'ASC';
599 } else {
600 $col = $order[0];
601 $dir = (self::DESC & $order[1]) ? 'DESC' : 'ASC';
602 }
603
604 $filter['order'] = [
605 'column' => $col,
606 'direction' => $dir
607 ];
608 }
609
610 if (isset($preference['limit'])) {
611 $filter['limit'] = $preference['limit'];
612 }
613
614 // Harus ada limit jika offset diset. Akan error di mysql jika ada offset tanpa limit
615 if (isset($preference['offset']) && isset($preference['limit'])) {
616 $filter['offset'] = $preference['offset'];
617 }
618
619 return $this;
620 }
621
622 /**
623 * Menjalankan klausa AND di sql
624 *
625 * Ada suffix s karena AND sudah didefine oleh PHP sehingga kita tidak bisa menggunakan fungsi tersebut
626 *
627 * @return self
628 * @access public
629 */
630 function ands() {
631 $this->str_punc = 'AND';
632
633 return $this;
634 }
635
636 /**
637 * Menjalankan klausa OR di sql
638 *
639 * Ada suffix s karena OR sudah didefine oleh PHP sehingga kita tidak bisa menggunakan fungsi tersebut
640 *
641 * @return self
642 * @access public
643 */
644 function ors() {
645 $this->str_punc = 'OR';
646
647 return $this;
648 }
649
650 /**
651 * Internal fungsi untuk membuat perintah SQL
652 *
653 * @return string
654 * @access private
655 */
656 private function internalBuildQuery() {
657 // Perintah saat ini
658 $cmd = $this->str_cmd;
659
660 // Handle jika perintah tidak perlu ditokenize
661 if (!$this->arr_token[$cmd]['tokenize']) {
662 return [ $this->arr_token[$cmd]['query'], false ];
663 }
664
665 switch ($cmd) {
666 case 'select':
667 return $this->internalBuildSelect();
668
669 case 'insert':
670 return $this->internalBuildInsert();
671
672 case 'update':
673 return $this->internalBuildUpdate();
674
675 case 'delete':
676 return $this->internalBuildDelete();
677
678 case 'create':
679 return $this->internalBuildCreate();
680
681 case 'copy':
682 return $this->internalBuildCopy();
683
684 default:
685 throw new Exception('DataBase::internalBuildQuery: Methode tidak dikenali');
686 }
687 }
688
689 /**
690 * Membuat SQL query dengan perintah SELECT dari token yang ada
691 *
692 * @return array
693 * @access private
694 */
695 private function internalBuildSelect() {
696 $ret = '';
697 $cmd = $this->str_cmd;
698 $extra = ['', false];
699 $token = $this->arr_token[$cmd];
700
701 $column = implode(',', $token['column']);
702 $ret .= "SELECT $column FROM {$token['table']}";
703
704 if (count($token['where'])) {
705 $extra = $this->internalBuildWhere($token['where']);
706 $ret .= " $extra[0] ";
707 }
708
709 if (count($token['filter'])) {
710 $filter = $token['filter'];
711 if (isset($filter['order'])) {
712 $order = $filter['order'];
713 $ret .= " ORDER BY {$order['column']} {$order['direction']} ";
714 }
715
716 if (isset($filter['limit'])) {
717 $ret .= " LIMIT {$filter['limit']} ";
718
719 if (isset($filter['offset'])) {
720 $ret .= " OFFSET {$filter['offset']} ";
721 }
722 }
723 }
724
725 return [ $ret, $extra[1] ];
726 }
727
728 /**
729 * Membuat SQL query dengan perintah INSERT dari token yang ada
730 *
731 * @return array array yang berisi query string dan data untuk placeholder untuk digunakan di PDO::prepare
732 * @access private
733 */
734 private function internalBuildInsert() {
735 // Perintah saat ini
736 $ret = '';
737 $cmd = $this->str_cmd;
738 $token = $this->arr_token[$cmd];
739
740 $column = $token['column'];
741 $data = $token['data'];
742
743 $holder = $this->internalCreatePlaceholder(count($data));
744 $str_column = implode(',', $column);
745 $str_holder = implode(',', $holder);
746
747 $ret .= "INSERT INTO {$token['table']} ($str_column) VALUES ($str_holder)";
748
749 // INSERT tidak perlu WHERE klausa
750 return [$ret, $data];
751 }
752
753 /**
754 * Membuat SQL query UPDATE dari token yang ada
755 *
756 * @return array
757 * @access private
758 */
759 private function internalBuildUpdate() {
760 $ret = '';
761 $extra = ['', false];
762 $cmd = $this->str_cmd;
763 $token = $this->arr_token[$cmd];
764
765 $column = $token['column'];
766 $data = $token['data'];
767 // $holder = $this->internalCreatePlaceholder(count($data));
768
769 $str_column = implode(',', $column);
770
771 if (count($token['where'])) {
772 $extra = $this->internalBuildWhere($token['where']);
773 }
774
775 $ret .= "UPDATE {$token['table']} SET";
776 foreach ($column as $col) {
777 $ret .= " $col = ?,";
778 }
779 $ret = rtrim($ret, ',');
780
781 // Mungkin saja ada where klausa
782 if ($extra[1] !== false) {
783 $ret .= " $extra[0]";
784 $data = array_merge($data, $extra[1]);
785 }
786
787 return [$ret, $data];
788 }
789
790 /**
791 * Membuat SQL query DELETE dari token yang tersedia
792 *
793 * @return array
794 * @access private
795 */
796 private function internalBuildDelete() {
797 $ret = '';
798 $cmd = $this->str_cmd;
799 $token = $this->arr_token[$cmd];
800
801 // DELETE tanpa WHERE klausa
802 if (!count($token['where'])) {
803 throw new Exception('DataBase::internalBuildDelete: Tidak ada WHERE klausa');
804 }
805
806 $ret .= "DELETE FROM {$token['table']}";
807 $extra = $this->internalBuildWhere($token['where']);
808
809 return [ "$ret {$extra[0]}", $extra[1] ];
810 }
811
812 /**
813 * Membuat SQL query CREATE TABLE menggunakan token yang sudah ada
814 *
815 * @return array
816 * @access private
817 */
818 private function internalBuildCreate() {
819 $ret = '';
820 $cmd = $this->str_cmd;
821 $token = $this->arr_token[$cmd];
822
823 $table = $token['table'];
824 $data = $token['data'];
825 $config = $token['config'];
826
827 //-> Membuat klausa CREATE TABLE
828 // Jika truncate true, hapus table terlebih dahulu jika ada
829 if ($config['truncate']) {
830 $ret .= "DROP TABLE IF EXISTS {$table};";
831 }
832
833 // Jika use_if true, gunakan klausa IF NOT EXISTS
834 $ret .= $config['use_if'] ?
835 "CREATE TABLE IF NOT EXISTS {$table}" :
836 "CREATE TABLE {$table}";
837 $ret .= ' ( ';
838
839 // Tambahkan kolom dan definisinya
840 foreach ($data as $column => $definition) {
841 $ret .= "$column $definition,";
842 }
843
844 // Tambahkan Primary Key konstrain jika diset
845 if ($config['primary_key']) {
846 $ret .= "PRIMARY KEY({$config['primary_key']}),";
847 }
848
849 // Tambahkan Unique konstrain jika diset
850 if ($config['unique']) {
851 $ret .= "UNIQUE ({$config['unique']}),";
852 }
853
854 $ret = rtrim($ret, ',');
855 $ret .= ') ';
856
857 // Set Engine, Charset
858 $ret .= "ENGINE={$config['engine']} DEFAULT CHARSET={$config['charset']};";
859
860 return [ $ret, false ];
861 }
862
863 /**
864 * Memparse perintah clone sari token yang sudah ada
865 *
866 * @return array
867 * @access private
868 */
869 private function internalBuildCopy() {
870 $ret = '';
871 $extra = [ '', false ];
872 $cmd = $this->str_cmd;
873 $token = $this->arr_token[$cmd];
874
875 $original = $token['original'];
876 $target = $token['target'];
877 $column = $token['column'];
878 $dest = $token['destination'];
879
880 // Handle Ketika tidak ada kolom yang dipilih
881 if (!count($column) ||
882 empty($column[0]) ||
883 ($column[0] !== '*' &&
884 (!count($dest) ||
885 empty($dest[0])))
886 ) {
887 throw new Exception('DataBase::internalBuildCopy: Memerlukan paling tidak satu kolom untuk dipilih dan tempat data diletakan');
888 }
889
890 // Handle SELECT *
891 // NOTE: Hanya mengecek secara naif
892 if ($column[0] === '*') {
893 $ret .= "INSERT INTO ${target} SELECT * FROM ${original}";
894 } else {
895 // Langkah pertama pastikan kolom yang dipilih dan kolom tujuan memiliki jumlah yang sama.
896 // Saya juga inginnya mengechek apakah tipe datanya juga sama tapi biarkan mysql yang menghandlenya.
897 if (count($column) !== count($dest)) {
898 throw new Exception('DataBase::internalBuildCopy: Jumlah kolom data berasal tidak sama dengan jumlah kolom tujuan');
899 }
900
901 $str_column = implode(',', $column);
902 $str_dest = implode(',', $dest);
903
904 $ret .= "INSERT INTO ${target} (${str_dest}) SELECT ${str_columb} FROM ${original}";
905 }
906
907 if (count($token['where'])) {
908 $extra = $this->internalBuildWhere($token['where']);
909
910 $ret .= " {$extra[0]}";
911 }
912
913 return [ $ret, $extra[1] ];
914 }
915
916 /**
917 * Membuat placeholder untuk PDO::prepare
918 *
919 * @param int $count jumlah placeholder yang akan dibuat
920 * @return array
921 * @access private
922 */
923 private function internalCreatePlaceholder($count) {
924 $ret = [];
925 foreach (range(1, $count) as $num) {
926 $ret[] = '?';
927 }
928
929 return $ret;
930 }
931
932 /**
933 * Memparse WHERE klausa sesuai token
934 *
935 * @param array $token token yang akan di parse
936 * @return array
937 * @access private
938 */
939 private function internalBuildWhere(array $token) {
940 $ret = ['WHERE ', []];
941
942 foreach ($token as $T) {
943 $tmp = '';
944 !empty($T['punctuation']) && $tmp .= " {$T['punctuation']} ";
945 $tmp .= "{$T['column']} {$T['condition']} ?";
946
947 $ret[0] .= $tmp;
948 $ret[1][] = $T['value'];
949 }
950
951 return $ret;
952 }
953
954 /**
955 * Menentukan tipe data sebuah data sebelum dikirim ke database
956 *
957 * @param array $data data yang akan dikirim ke database
958 * @return array
959 * @access private
960 */
961 private function internalGetType(array $data) {
962 $translation = [
963 'string' => PDO::PARAM_STR,
964 'integer' => PDO::PARAM_INT,
965 'double' => PDO::PARAM_INT,
966 'null' => PDO::PARAM_NULL,
967 'bool' => PDO::PARAM_BOOL
968 ]; $ret = [];
969
970 foreach ($data as $k => $v) {
971 $type = gettype($v);
972 $type = isset($translation[$type]) ? $translation[$type] : PDO::PARAM_STR;
973 $ret[] = [
974 'index' => $k + 1,
975 'value' => $v,
976 'type' => $type
977 ];
978 }
979
980 return $ret;
981 }
982
983 /**
984 * Internal fungsi untuk mengkoneksi ke database
985 *
986 * Jika tidak ada konfigurasi maka secara default akan menggunakan konfigurasi yang
987 * ada di file /harmoni/config.php
988 *
989 * @return void
990 * @access private
991 */
992 private function internalConnect() {
993 $config = $this->arr_config;
994
995 $host = isset($config['host']) ? $config['host'] : DB_HOST;
996 $user = isset($config['user']) ? $config['user'] : DB_USER;
997 $pass = isset($config['pass']) ? $config['pass'] : DB_PASS;
998 $name = isset($config['name']) ? $config['name'] : DB_NAME;
999
1000 $dsn = "mysql: host=$host; dbname=$name";
1001
1002 try {
1003 self::$pdo_dbh = new PDO($dsn, $user, $pass);
1004 // Production : PDO::ERRMODE_SILENT
1005 // Development: PDO::ERRMODE_EXCEPTION
1006 self::$pdo_dbh->setAttribute(PDO::ATTR_ERRMODE, PDO::ERRMODE_EXCEPTION);
1007 } catch (PDOException $error) {
1008 // Berhenti mengeksekusi skrip karena koneksi ke database gagal
1009 exit($error->getMessage());
1010 }
1011 }
1012}
1013
1014?>