1: <?php
2: 3: 4: 5:
6:
7: class TinyDBException extends Exception{}
8:
9: 10: 11:
12: class TinyDB
13: {
14: protected $current;
15: protected $connections = array();
16: protected $configs = array();
17: public function __construct($dsn = null, $username = null, $password = null, $options = array()){
18: $this->current = 'default';
19: $this->addConnection($this->current, $dsn, $username, $password, $options);
20: }
21:
22: public function getConfig($name = null){
23: if(null === $name){
24: $name = $this->current;
25: }
26: return $this->configs[$name];
27: }
28:
29: public function getPDO($name = null){
30: if(null === $name){
31: $name = $this->current;
32: }
33: if(!isset($this->connections[$name])){
34: $config = $this->configs[$name];
35: $this->connections[$name] = new PDO($config['dsn'], $config['username'], $config['password'], $config['options']);
36: }
37:
38: return $this->connections[$this->current];
39: }
40:
41: 42: 43: 44: 45: 46: 47: 48: 49:
50: public function addConnection($name, $dsn, $username = null, $password = null, $options = array()){
51:
52: static $defaultOptions = array(
53: PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
54:
55: );
56: if(isset($options['prefix'])){
57: $prefix = $options['prefix'];
58: unset($options['prefix']);
59: }
60: else{
61: $prefix = '';
62: }
63: $this->configs[$name] = array(
64: 'dsn' => $dsn,
65: 'username' => $username,
66: 'password' => $password,
67: 'prefix' => $prefix,
68: 'options' => $options + $defaultOptions,
69: );
70:
71: return $this;
72: }
73:
74: 75: 76: 77:
78: public function switchConnection($name = 'default'){
79: $this->current = $name;
80:
81: return $this;
82: }
83:
84: 85: 86: 87: 88: 89:
90: public function factory($model){
91: return new TinyDBFactory($this, $model);
92: }
93:
94: 95: 96: 97: 98:
99: public function command(){
100: return new TinyDBCommand($this);
101: }
102:
103: 104: 105:
106: public function getAttribute($attribute){
107: return $this->getPDO()->getAttribute($attribute);
108: }
109:
110: 111: 112:
113: public function setAttribute($attribute, $value){
114: return $this->getPDO()->setAttribute($attribute, $value);
115: }
116:
117: 118: 119:
120: public function prepare(){
121: return call_user_func_array(array($this->getPDO(), 'prepare'), func_get_args());
122: }
123:
124: 125: 126:
127: public function query(){
128: return call_user_func_array(array($this->getPDO(), 'query'), func_get_args());
129: }
130:
131: 132: 133:
134: public function exec(){
135: return call_user_func_array(array($this->getPDO(), 'exec'), func_get_args());
136: }
137:
138: 139: 140:
141: public function beginTransaction(){
142: return $this->getPDO()->beginTransaction();
143: }
144:
145: 146: 147:
148: public function rollBack(){
149: return $this->getPDO()->rollBack();
150: }
151:
152: 153: 154:
155: public function commit(){
156: return $this->getPDO()->commit();
157: }
158:
159: 160: 161:
162: public function inTransaction(){
163: return $this->getPDO()->inTransaction();
164: }
165:
166: 167: 168:
169: public function lastInsertId(){
170: return call_user_func_array(array($this->getPDO(), 'lastInsertId'), func_get_args());
171: }
172:
173: 174: 175:
176: public function quote(){
177: return call_user_func_array(array($this->getPDO(), 'quote'), func_get_args());
178: }
179:
180: public function errorCode(){
181: return call_user_func_array(array($this->getPDO(), 'errorCode'), func_get_args());
182: }
183:
184: public function errorInfo(){
185: return call_user_func_array(array($this->getPDO(), 'errorInfo'), func_get_args());
186: }
187:
188: 189: 190: 191: 192:
193: public function quoteTable($name){
194: return $this->quoteIdentifier($name);
195: }
196:
197: 198: 199: 200: 201:
202: public function quoteColumn($name){
203: return $this->quoteIdentifier($name);
204: }
205:
206: 207: 208: 209: 210:
211: public function quoteIdentifier($name){
212: $quote = null;
213: switch($this->getAttribute(PDO::ATTR_DRIVER_NAME)){
214: case 'pgsql':
215: case 'sqlsrv':
216: case 'dblib':
217: case 'mssql':
218: case 'sybase':
219: $quote = '"';
220: case 'mysql':
221: case 'sqlite':
222: case 'sqlite2':
223: default:
224: $quote = '`';
225: }
226:
227: $parts = explode('.', $name);
228: foreach($parts as $k => $part){
229: if($part !== '*'){
230: $parts[$k] = $quote.$part.$quote;
231: }
232: }
233:
234: return implode('.', $parts);
235: }
236:
237: public function buildLimitOffset($sql, $limit, $offset = 0){
238: switch($this->getAttribute(PDO::ATTR_DRIVER_NAME)){
239: case 'sqlsrv':
240: case 'dblib':
241: case 'mssql':
242: case 'sybase':
243: throw new TinyDBException('Limit/offset not implemented yet');
244: case 'pgsql':
245: case 'mysql':
246: case 'sqlite':
247: case 'sqlite2':
248: default:
249: if($limit > 0){
250: $sql .= "\n LIMIT ".$limit;
251: }
252: if($offset > 0){
253: $sql .= " OFFSET ".$offset;
254: }
255:
256: return $sql;
257: }
258: }
259:
260: public function getPrefix(){
261: return $this->configs[$this->current]['prefix'];
262: }
263:
264: public function fixPrefix($sql){
265:
266: }
267: }
268:
269: 270: 271:
272: class TinyDBModel
273: {
274:
275:
276: private $_table;
277:
278: private $_pk;
279:
280: private $_relations;
281:
282:
283:
284: protected $_db;
285:
286: protected $_data = array();
287:
288: protected $_safe;
289:
290: protected $_dirty = array();
291:
292: protected $_isNew;
293:
294: 295: 296: 297: 298: 299:
300: public static function config(){
301: return array(
302:
303: 'pk' => 'id',
304:
305: );
306: }
307:
308: 309: 310: 311: 312: 313:
314: final public static function getConfig($key = null){
315: $config = static::config();
316: if(null === $key){
317: return $config;
318: }
319: elseif(isset($config[$key])){
320: return $config[$key];
321: }
322: else{
323: return null;
324: }
325: }
326:
327: 328: 329:
330: public function getTable(){
331: return $this->_table;
332: }
333:
334: 335: 336:
337: public function getPK(){
338: return $this->_pk;
339: }
340:
341: 342: 343:
344: public function getRelations(){
345: return $this->_relations;
346: }
347:
348: 349: 350: 351: 352: 353: 354: 355:
356: static public function entityNameToDBName($class){
357:
358: if(false !== $pos = strrpos($class, '\\')){
359: $class = substr($class, $pos + 1);
360: }
361:
362: return strtolower(preg_replace('/(?<=[a-z])([A-Z])/', '_$1', $class));
363: }
364:
365: 366: 367: 368: 369: 370: 371:
372: public function __construct($db, $data = array(), $isNew = true){
373: $this->_db = $db;
374:
375: if($isNew){
376: $this->_dirty = $data;
377: }
378: else{
379: $this->_data = $data;
380: }
381: $this->_isNew = $isNew;
382:
383:
384: $this->setAttributes(static::config());
385: }
386:
387: 388: 389:
390: public function setAttribute($attribute, $value){
391: if($attribute === 'table'){
392: $this->_table = $value;
393: }
394: elseif($attribute === 'pk'){
395: $this->_pk = $value;
396: }
397: elseif($attribute === 'relations'){
398: $this->_relations = $value;
399: }
400:
401: return $this;
402: }
403:
404: 405: 406: 407:
408: public function setAttributes($attributes){
409: foreach($attributes as $k => $v){
410: $this->setAttribute($k, $v);
411: }
412:
413: return $this;
414: }
415:
416: public function isNew(){
417: return $this->_isNew;
418: }
419:
420: public function isDirty(){
421: return !empty($this->_dirty);
422: }
423:
424: 425: 426: 427: 428:
429: public function getRaw($key = null){
430: if(null === $key){
431: return $this->_data;
432: }
433: else{
434: return isset($this->_data[$key])?$this->_data[$key]:null;
435: }
436: }
437:
438: 439: 440: 441: 442:
443: public function get($key = null){
444: if(null === $key){
445: return array_merge($this->_data, $this->_dirty);
446: }
447: else{
448: if(isset($this->_dirty[$key])){
449: return $this->_dirty[$key];
450: }
451: elseif(isset($this->_data[$key])){
452: return $this->_data[$key];
453: }
454: else{
455: return null;
456: }
457: }
458: }
459:
460: public function __get($key){
461: $relations = $this->getRelations();
462: if(isset($relations[$key])){
463: return $this->getWithRelation($key);
464: }
465: return $this->get($key);
466: }
467:
468: public function getWithRelation($name){
469: $relations = $this->getRelations();
470: if(isset($relations[$name])){
471: $relation = $relations[$name];
472: if($relation['relation'] === 'OTO'){
473: return $this->getOneToOne($relation['target'], isset($relation['key'])?$relation['key']:null, isset($relation['target_key'])?$relation['target_key']:null);
474: }
475: elseif($relation['relation'] == 'OTM'){
476: return $this->getOneToMany($relation['target'], isset($relation['key'])?$relation['key']:null, isset($relation['target_key'])?$relation['target_key']:null);
477: }
478: elseif($relation['relation'] == 'MTO'){
479: return $this->getManyToOne($relation['target'], isset($relation['key'])?$relation['key']:null, isset($relation['target_key'])?$relation['target_key']:null);
480: }
481: elseif($relation['relation'] == 'MTM'){
482: return $this->getManyToMany($relation['target'], $relation['through'], isset($relation['key'])?$relation['key']:null, isset($relation['target_key'])?$relation['target_key']:null);
483: }
484: else{
485: throw new TinyDBException('Invalid relation "'.$relation['relation'].'"');
486: }
487: }
488: else{
489: return false;
490: }
491: }
492:
493: 494: 495: 496: 497: 498: 499: 500: 501:
502: public function getOneToOne($target, $key = null, $target_key = null){
503: $factory = $this->_db->factory($target);
504: if(null === $key){
505: $key = $this->getPK();
506: }
507: if(null === $target_key){
508: $target_key = $factory->getPK();
509: }
510:
511: return $factory->findOneBy($target_key, $this->get($key));
512: }
513:
514: 515: 516: 517: 518: 519: 520: 521:
522: public function getOneToMany($target, $key = null, $target_key = null){
523: $factory = $this->_db->factory($target);
524: if(null === $key){
525: $key = $this->getPK();
526: }
527: if(null === $target_key){
528: $target_key = $key;
529: }
530:
531: return $factory->findManyBy($target_key, $this->get($key));
532: }
533:
534: 535: 536: 537: 538: 539: 540: 541:
542: public function getManyToOne($target, $key = null, $target_key = null){
543: $factory = $this->_db->factory($target);
544: if(null === $target_key){
545: $target_key = $factory->getPK();
546: }
547: if(null === $key){
548: $key = $target_key;
549: }
550:
551: return $factory->findOneBy($target_key, $this->get($key));
552: }
553:
554: 555: 556: 557: 558: 559: 560: 561: 562:
563: public function getManyToMany($target, $through, $key = null, $target_key = null){
564: $factory = $this->_db->factory($target);
565:
566: if(null === $key){
567: $key = $this->getPK();
568: }
569: if(null === $target_key){
570: $target_key = $factory->getPK();
571: }
572:
573: $through = $this->parseThrough($through);
574: if(!$through[1]){
575: $through[1] = $key;
576: }
577: if(!$through[2]){
578: $through[2] = $target_key;
579: }
580:
581: $rows = $this->_db->command()
582: ->select('t.*')
583: ->from($factory->getTable().' t')
584: ->leftJoin($through[0].' m', 'm.'.$through[2].'=t.'.$target_key)
585: ->where('m.'.$through[1].'=:value', array(
586: ':value' => $this->get($key)
587: ))
588: ->queryAll();
589:
590: if(false === $rows){
591: return false;
592: }
593:
594: return $factory->mapModels($rows);
595: }
596:
597: protected function parseThrough($through){
598: $through = explode(',', $through);
599: $table = trim($through[0]);
600: $key = isset($through[1])?trim($through[1]):null;
601: $target_key = isset($through[2])?trim($through[2]):null;
602:
603: return array($table, $key, $target_key);
604: }
605:
606: 607: 608: 609: 610:
611: public function set($key, $value = null){
612: if(is_array($key)){
613: $this->_dirty = $key;
614: }
615: else{
616: $this->_dirty[$key] = $value;
617: }
618:
619: return $this;
620: }
621:
622: public function __set($key, $value){
623: $this->_dirty[$key] = $value;
624: }
625:
626: public function __isset($key){
627: return isset($this->_dirty[$key]) || isset($this->_data[$key]);
628: }
629:
630: public function __unset($key){
631: if(isset($this->_dirty[$key])){
632: unset($this->_dirty[$key]);
633: }
634: }
635:
636: protected function buildPKConditions(){
637: $pk = $this->getPK();
638: if(is_string($pk)){
639: $pks = array($pk);
640: }
641: else{
642: $pks = $pk;
643: }
644: $params = array();
645: foreach($pks as $k => $pk){
646: $pks[$k] = $pk.'=:pk'.$k;
647: $params[':pk'.$k] = $this->_data[$pk];
648: }
649: array_unshift($pks, 'AND');
650:
651: return array($pks, $params);
652: }
653:
654: 655: 656: 657:
658: public function save(){
659: if($this->beforeSave()){
660: if($this->isNew()){
661: $data = $this->_dirty;
662:
663:
664: if(false !== $rst = $this->_db->command()->insert($this->getTable(), $data))
665: {
666: if(is_string($this->getPK()) && $id = $this->_db->lastInsertId()){
667: $data[$this->getPK()] = $id;
668: }
669: $this->_data = $data;
670: $this->_dirty = array();
671: $this->_isNew = false;
672: $this->afterSave();
673: return $rst;
674: }
675: }
676: else{
677: if($this->isDirty()){
678:
679: $pkConditions = $this->buildPKConditions();
680: if(false !== $rst = $this->_db->command()->update($this->getTable(), $this->_dirty, $pkConditions[0], $pkConditions[1])){
681: $this->_data = array_merge($this->_data, $this->_dirty);
682: $this->_dirty = array();
683: $this->afterSave();
684: return $rst;
685: }
686: }
687: }
688: }
689:
690: return false;
691: }
692:
693: 694: 695: 696:
697: public function delete(){
698: if($this->beforeDelete()){
699: $pkConditions = $this->buildPKConditions();
700: if(false !== $rst = ($this->isNew() || $this->_db->command()->delete($this->getTable(), $pkConditions[0], $pkConditions[1]))){
701: $this->_data = array();
702: $this->_dirty = array();
703: $this->afterDelete();
704:
705: return $rst;
706: }
707: }
708:
709: return false;
710: }
711:
712: protected function beforeSave(){
713: return true;
714: }
715:
716: protected function afterSave(){
717: return true;
718: }
719:
720: protected function beforeDelete(){
721: return true;
722: }
723:
724: protected function afterDelete(){
725: return true;
726: }
727:
728: }
729:
730: 731: 732:
733: class TinyDBFactory
734: {
735: protected $db;
736: protected $modelClass;
737: protected $table;
738: protected $pk;
739: protected $with;
740:
741: public function __construct($db, $model, $pk = null){
742: $this->db = $db;
743: if($model[0] === '@'){
744: $this->modelClass = 'TinyDBModel';
745: $this->table = substr($model, 1);
746: }
747: else{
748: $this->modelClass = $model;
749: if(null !== $model::getConfig('table')){
750: $this->table = $model::getConfig('table');
751: }
752: else{
753: $this->table = $model::entityNameToDBName($model);
754: }
755: }
756: if(null !== $pk){
757: $this->pk = $pk;
758: }
759: else{
760: $class = $this->modelClass;
761: $this->pk = $class::getConfig('pk');
762: }
763: }
764:
765: 766: 767:
768: public function getPK(){
769: return $this->pk;
770: }
771:
772: 773: 774:
775: public function getTable(){
776: return $this->table;
777: }
778:
779: 780: 781:
782: public function with($with){
783: $this->with = $with;
784:
785: return $this;
786: }
787:
788: public function buildWith(){
789: $with = $this->with;
790: $columns = explode(',', $with);
791: foreach($columns as $column){
792:
793: }
794: $this->with = null;
795: }
796:
797: 798: 799: 800: 801: 802:
803: public function map($row){
804: $class = $this->modelClass;
805: $model = new $class($this->db, $row, false);
806: $model->setAttribute('table', $this->table)->setAttribute('pk', $this->pk);
807: return $model;
808: }
809:
810: 811: 812: 813: 814: 815:
816: public function mapModels($rows){
817: $rst = array();
818: foreach($rows as $row){
819: $rst[] = $this->map($row);
820: }
821:
822: return $rst;
823: }
824:
825: 826: 827: 828: 829: 830:
831: public function create($row = array()){
832: $class = $this->modelClass;
833: $model = new $class($this->db, $row);
834: $model->setAttribute('table', $this->table)->setAttribute('pk', $this->pk);
835:
836: return $model;
837: }
838:
839: 840: 841: 842: 843: 844:
845: protected function buildPKConditions($pk, $_data){
846: if(is_string($pk)){
847: $pks = array($pk);
848: if(!is_array($_data)){
849: $_data = array($pk => $_data);
850: }
851: }
852: else{
853: $pks = $pk;
854: }
855: $params = array();
856: foreach($pks as $k => $pk){
857: $pks[$k] = $pk.'=:pk'.$k;
858: $params[':pk'.$k] = $_data[$pk];
859: }
860: array_unshift($pks, 'AND');
861:
862: return array($pks, $params);
863: }
864:
865: 866: 867: 868: 869: 870: 871:
872: public function count($conditions = '', $params = array()){
873: return $this->db->command()->select('COUNT(*)')->from($this->table)->where($conditions, $params)->queryScalar();
874: }
875:
876: 877: 878: 879: 880: 881: 882:
883: public function countBy($key, $value){
884: return $this->count($key.'=:key', array(':key' => $value));
885: }
886:
887: 888: 889: 890:
891: public function find($pk){
892: return $this->findByPK($pk);
893: }
894:
895: 896: 897:
898: public function findAll(){
899: $rows = $this->db->command()->select()->from($this->table)->queryAll();
900: if(false === $rows){
901: return $rows;
902: }
903:
904: return $this->mapModels($rows);
905: }
906:
907: 908: 909: 910: 911: 912: 913:
914: public function findOne($conditions, $params = array()){
915: $row = $this->db->command()->select()->from($this->table)->where($conditions, $params)->limit(1)->queryRow();
916: if(false === $row){
917: return false;
918: }
919:
920: return $this->map($row);
921: }
922:
923: 924: 925: 926: 927: 928: 929:
930: public function findOneBy($key, $value){
931: return $this->findOne($key.'=:key', array(':key' => $value));
932: }
933:
934: 935: 936: 937: 938: 939:
940: public function findByPK($pk){
941: $pkConditions = $this->buildPKConditions($this->pk, $pk);
942: return $this->findOne($pkConditions[0], $pkConditions[1]);
943: }
944:
945: 946: 947: 948: 949: 950: 951: 952: 953: 954:
955: public function findMany($conditions = '', $params = array(), $orderBy = null, $limit = null, $offset = null){
956: $cmd = $this->db->command()->select()->from($this->table)->where($conditions, $params);
957: if($orderBy){
958: $cmd->orderBy($orderBy);
959: }
960: $rows = $cmd->limit($limit, $offset)->queryAll();
961: if(false === $rows){
962: return false;
963: }
964: else{
965: return $this->mapModels($rows);
966: }
967: }
968:
969: 970: 971: 972: 973: 974: 975: 976: 977: 978:
979: public function findManyBy($key, $value, $orderBy = null, $limit = null, $offset = null){
980: return $this->findMany($key.'=:key', array(':key' => $value), $orderBy, $limit, $offset);
981: }
982:
983: 984: 985: 986: 987: 988: 989: 990:
991: public function update($values, $conditions = '', $params = array()){
992: return $this->db->command()->update($this->table, $values, $conditions, $params);
993: }
994:
995: 996: 997: 998: 999: 1000: 1001: 1002:
1003: public function updateBy($key, $value, $values){
1004: return $this->update($values, $key.'=:key', array(':key' => $value));
1005: }
1006:
1007: 1008: 1009: 1010: 1011: 1012:
1013: public function updateByPK($pk, $values){
1014: $pkConditions = $this->buildPKConditions($this->pk, $pk);
1015: return $this->update($values, $pkConditions[0], $pkConditions[1]);
1016: }
1017:
1018: 1019: 1020: 1021: 1022: 1023:
1024: public function insert($values){
1025: return $this->db->command()->insert($this->table, $values);
1026: }
1027:
1028: 1029: 1030: 1031: 1032: 1033: 1034:
1035: public function delete($conditions = '', $params = array()){
1036: return $this->db->command()->delete($this->table, $conditions, $params);
1037: }
1038:
1039: 1040: 1041: 1042: 1043: 1044: 1045:
1046: public function deleteBy($key, $value){
1047: return $this->delete($key.'=:key', array(':key' => $value));
1048: }
1049:
1050: 1051: 1052: 1053: 1054: 1055:
1056: public function deleteByPK($pk){
1057: $pkConditions = $this->buildPKConditions($this->pk, $pk);
1058: return $this->delete($pkConditions[0], $pkConditions[1]);
1059: }
1060:
1061: 1062: 1063: 1064: 1065: 1066: 1067:
1068: public function __call($name, $arguments){
1069:
1070: if(preg_match('#^find(One|Many)By(.+)$#', $name, $matches)){
1071: $one = $matches[1] === 'One';
1072: $findByKey = TinyDBModel::entityNameToDBName($matches[2]);
1073: array_unshift($arguments, $findByKey);
1074: if($one){
1075: return call_user_func_array(array($this, 'findOneBy'), $arguments);
1076: }
1077: else{
1078: return call_user_func_array(array($this, 'findManyBy'), $arguments);
1079: }
1080: }
1081: elseif(preg_match('#^(update|delete|count)By(.+)$#', $name, $matches)){
1082: $action = $matches[1];
1083: $actionByKey = TinyDBModel::entityNameToDBName($matches[2]);
1084: array_unshift($arguments, $actionByKey);
1085: return call_user_func_array(array($this, $action.'By'), $arguments);
1086: }
1087: else{
1088: throw new TinyDBException(sprintf('Helper method "%s" does not exist', $name));
1089: }
1090: }
1091: }
1092:
1093: 1094: 1095:
1096: class TinyDBCommand
1097: {
1098: protected $db;
1099: protected $statement;
1100: protected $params = array();
1101: protected $query = array();
1102: protected $sql;
1103:
1104: 1105: 1106: 1107: 1108:
1109: public function __construct($db){
1110: $this->db = $db;
1111: }
1112:
1113: protected function matchAlias($entry){
1114: if(preg_match('#^(.*?)(?i:\s+as\s+|\s+)(.*)$#', $entry, $matches)){
1115: return array(
1116: $matches[1],
1117: $matches[2],
1118: );
1119: }
1120: else{
1121: return false;
1122: }
1123: }
1124:
1125: protected function splitParts($parts){
1126: return preg_split('#\s*,\s*#', trim($parts), -1, PREG_SPLIT_NO_EMPTY);
1127: }
1128:
1129: 1130: 1131:
1132: public function reset(){
1133: $this->statement = null;
1134: $this->params = array();
1135: $this->query = array();
1136: $this->sql = null;
1137:
1138: return $this;
1139: }
1140:
1141: 1142: 1143:
1144: public function prepare(){
1145: if(null === $this->statement){
1146: $this->statement = $this->db->prepare($this->getSql());
1147: }
1148:
1149: return $this;
1150: }
1151:
1152: public function bindParam(){
1153: $this->prepare();
1154: call_user_func_array(array($this->statement, 'bindParam'), func_get_args);
1155: }
1156:
1157: public function bindValue(){
1158: $this->prepare();
1159: call_user_func_array(array($this->statement, 'bindValue'), func_get_args());
1160:
1161: return $this;
1162: }
1163:
1164: public function bindValues($values){
1165: $this->prepare();
1166:
1167: foreach($values as $k=> $value){
1168: $this->statement->bindValue($k, $value);
1169: }
1170:
1171: return $this;
1172: }
1173:
1174: public function mergeParams($params){
1175: foreach($params as $k => $v){
1176: $this->params[$k] = $v;
1177: }
1178: }
1179:
1180: 1181: 1182: 1183: 1184: 1185: 1186:
1187: public function select($fields = '*'){
1188: if(is_string($fields) && strpos($fields, '(') !== false){
1189: $this->query['select'] = $fields;
1190: }
1191: else{
1192: if(!is_array($fields)){
1193: $fields = $this->splitParts($fields);
1194: }
1195: foreach($fields as $k => $field){
1196: if(false === strpos($field, '(')){
1197: if($alias = $this->matchAlias($field)){
1198: $fields[$k] = $this->db->quoteColumn($alias[0]).' AS '.$this->db->quoteColumn($alias[1]);
1199: }
1200: else{
1201: $fields[$k] = $this->db->quoteColumn($field);
1202: }
1203: }
1204: }
1205: $this->query['select'] = implode(', ', $fields);
1206: }
1207:
1208: return $this;
1209: }
1210:
1211: 1212: 1213:
1214: public function distinct(){
1215: $this->query['distinct'] = true;
1216:
1217: return $this;
1218: }
1219:
1220: 1221: 1222: 1223: 1224: 1225: 1226:
1227: public function from($tables){
1228: if(is_string($tables)){
1229: $tables = $this->splitParts($tables);
1230: }
1231: foreach($tables as $k => $table){
1232: if($alias = $this->matchAlias($table)){
1233: $tables[$k] = $this->db->quoteTable($alias[0]).' AS '.$this->db->quoteTable($alias[1]);
1234: }
1235: else{
1236: $tables[$k] = $this->db->quoteTable($table);
1237: }
1238: }
1239:
1240: $this->query['from'] = implode(', ', $tables);
1241:
1242: return $this;
1243: }
1244:
1245: 1246: 1247: 1248:
1249: public function buildConditions($conditions){
1250: if(!is_array($conditions)){
1251: if(null === $conditions){
1252: return '';
1253: }
1254: return $conditions;
1255: }
1256: elseif($conditions === array()){
1257: return '';
1258: }
1259:
1260: $n = count($conditions);
1261: $operator = strtoupper($conditions[0]);
1262: if($operator === 'AND' || $operator === 'OR'){
1263: $result = array();
1264: for($i = 1; $i < $n; $i++){
1265: $condition = $this->buildConditions($conditions[$i]);
1266: if('' !== $condition){
1267: $result[] = '('.$condition.')';
1268: }
1269: }
1270: if($result === array()){
1271: return '';
1272: }
1273: else{
1274: return implode(' '.$operator.' ', $result);
1275: }
1276: }
1277:
1278: if(!isset($conditions[1], $conditions[2])){
1279: return '';
1280: }
1281:
1282: $column = $this->db->quoteColumn($conditions[1]);
1283: $values = $conditions[2];
1284: if(!is_array($values)){
1285: $values = array($values);
1286: }
1287: if($operator === 'IN' || $operator === 'NOT IN'){
1288: if($values === array()){
1289: return $operator === 'IN'?'0':'';
1290: }
1291: foreach($values as $k => $value){
1292: $values[$k] = $this->db->quote($value);
1293: }
1294:
1295: return $column.' '.$operator.' ('.implode(',', $values).')';
1296: }
1297: elseif($operator === 'LIKE' || $operator === 'NOT LIKE' || $operator === 'OR LIKE' || $operator === 'OR NOT LIKE'){
1298: if($values === array()){
1299: return ($operator === 'LIKE' || $operator === 'OR LIKE')?'0':'';
1300: }
1301: if($operator === 'LIKE' || $operator === 'NOT LIKE'){
1302: $andor = 'AND';
1303: }
1304: else{
1305: $andor = 'OR';
1306: $operator = $operator === 'OR LIKE'?'LIKE':'NOT LIKE';
1307: }
1308:
1309: $result = array();
1310: foreach($values as $k => $value){
1311: $result[] = $column.' '.$operator.' '.$this->db->quote($value);
1312: }
1313:
1314: return implode($andor, $result);
1315: }
1316: else{
1317: throw new TinyDBException('Invalid operator "'.$operator.'"');
1318: }
1319: }
1320:
1321: 1322: 1323: 1324: 1325:
1326: public function where($conditions, $params = array()){
1327: $this->mergeParams($params);
1328: $this->query['where'] = $this->buildConditions($conditions);
1329:
1330: return $this;
1331: }
1332:
1333: 1334: 1335: 1336: 1337: 1338: 1339:
1340: public function orderBy($fields){
1341: if(!is_array($fields)){
1342: $fields = $this->splitParts($fields);
1343: }
1344:
1345: foreach($fields as $k => $field){
1346: if(preg_match('#^(.*?)\s+(asc|desc)$#i', $field, $matches)){
1347: $fields[$k] = $this->db->quoteColumn($matches[1]).' '.$matches[2];
1348: }
1349: else{
1350: $fields[$k] = $this->db->quoteColumn($field);
1351: }
1352: }
1353:
1354: $this->query['order'] = implode(',', $fields);
1355:
1356: return $this;
1357: }
1358:
1359: 1360: 1361: 1362: 1363:
1364: public function limit($limit, $offset = null){
1365: $this->query['limit'] = $limit;
1366: if(null !== $offset){
1367: $this->offset($offset);
1368: }
1369:
1370: return $this;
1371: }
1372:
1373: 1374: 1375: 1376:
1377: public function offset($offset){
1378: $this->query['offset'] = $offset;
1379:
1380: return $this;
1381: }
1382:
1383: 1384: 1385: 1386: 1387: 1388: 1389:
1390: protected function anyJoin($type, $table, $conditions = '', $params = array()){
1391: $this->mergeParams($params);
1392: if($alias = $this->matchAlias($table)){
1393: $table = $this->db->quoteTable($alias[0]).' AS '.$this->db->quoteTable($alias[1]);
1394: }
1395: else{
1396: $table = $this->db->quoteTable($table);
1397: }
1398: $conditions = $this->buildConditions($conditions);
1399: if('' !== $conditions){
1400: $conditions = ' ON '.$conditions;
1401: }
1402:
1403: if(isset($this->query['join']) && is_string($this->query['join'])){
1404: $this->query['join'] = array($this->query['join']);
1405: }
1406:
1407: $this->query['join'][] = $type.' '.$table.' '.$conditions;
1408:
1409: return $this;
1410: }
1411:
1412: 1413: 1414: 1415: 1416: 1417: 1418:
1419: public function join($table, $conditions = '', $params = array()){
1420: return $this->anyJoin('JOIN', $table, $conditions, $params);
1421: }
1422:
1423: 1424: 1425: 1426: 1427: 1428: 1429:
1430: public function leftJoin($table, $conditions = '', $params = array()){
1431: return $this->anyJoin('LEFT JOIN', $table, $conditions, $params);
1432: }
1433:
1434: 1435: 1436: 1437: 1438: 1439: 1440:
1441: public function rightJoin($table, $condtions = '', $params = array()){
1442: return $this->anyJoin('RIGHT JOIN', $table, $conditions, $params);
1443: }
1444:
1445: 1446: 1447: 1448: 1449:
1450: public function groupBy($fields){
1451: if(!is_array($fields)){
1452: $fields = $this->splitParts($fields);
1453: }
1454:
1455: foreach($fields as $k => $field){
1456: $fields[$k] = $this->db->quoteColumn($field);
1457: }
1458:
1459: $this->query['group'] = implode(',', $fields);
1460:
1461: return $this;
1462: }
1463:
1464: 1465: 1466: 1467: 1468: 1469:
1470: public function having($conditions, $params = array()){
1471: $this->mergeParams($params);
1472: $this->query['having'] = $this->buildConditions($conditions);
1473:
1474: return $this;
1475: }
1476:
1477: 1478: 1479: 1480: 1481:
1482: public function union($sql){
1483: if(isset($this->query['union']) && is_string($this->query['union'])){
1484: $this->query['union'][] = $this->query['union'];
1485: }
1486:
1487: $this->query['union'][] = $sql;
1488:
1489: return $this;
1490: }
1491:
1492: 1493: 1494: 1495: 1496: 1497:
1498: public function buildQuery($query = null){
1499: if(null === $query){
1500: $query = $this->query;
1501: }
1502:
1503: $sql = "SELECT ";
1504: if(isset($query['distinct']) && $query['distinct']){
1505: $sql .= 'DISTINCT ';
1506: }
1507: $sql .= isset($query['select'])?$query['select']:'*';
1508:
1509: if(!isset($query['from'])){
1510: return false;
1511: }
1512:
1513: $sql .= "\nFROM ".$query['from'];
1514: if(isset($query['join'])){
1515: $sql .= "\n".(is_array($query['join'])?implode("\n", $query['join']):$query['join']);
1516: }
1517: if(isset($query['where']) && $query['where'] !== ''){
1518: $sql .= "\nWHERE ".$query['where'];
1519: }
1520: if(isset($query['group'])){
1521: $sql .= "\nGROUP BY ".$query['group'];
1522: if(isset($query['having'])){
1523: $sql .= "\nHAVING ".$query['having'];
1524: }
1525: }
1526: if(isset($query['order'])){
1527: $sql .= "\n ORDER BY ".$query['order'];
1528: }
1529: $limit = isset($query['limit'])?$query['limit']:0;
1530: $offset = isset($query['offset'])?$query['offset']:0;
1531: $sql = $this->db->buildLimitOffset($sql, $limit, $offset);
1532:
1533: if(isset($query['union'])){
1534: $sql .= "\n".(is_array($query['union'])?implode("\n", $query['union']):$query['union']);
1535: }
1536:
1537: return $sql;
1538: }
1539:
1540: 1541: 1542: 1543: 1544:
1545: public function setSql($sql){
1546: if('' !== $prefix = $this->db->getPrefix()){
1547: $sql = preg_replace('#{{(.*?)}}#', $prefix.'\1', $sql);
1548: }
1549: $this->sql = $sql;
1550:
1551: return $this;
1552: }
1553:
1554: 1555: 1556: 1557: 1558:
1559: public function getSql(){
1560: if(null === $this->sql){
1561: if(!empty($this->query)){
1562: $this->setSql($this->buildQuery());
1563: }
1564: else{
1565: return false;
1566: }
1567: }
1568:
1569: return $this->sql;
1570: }
1571:
1572: 1573: 1574: 1575:
1576: protected function beginQuery($params = array()){
1577: $params = array_merge($this->params, $params);
1578: $this->prepare();
1579: if(false === $this->statement->execute($params)){
1580: $info = $this->statement->errorInfo();
1581: throw new TinyDBException(sprintf('Statement error #%s: %s', $info[0], $info[2]));
1582: }
1583: }
1584:
1585: 1586: 1587: 1588: 1589: 1590: 1591:
1592: public function query($params = array()){
1593: $this->beginQuery($params);
1594: return $this->statement;
1595: }
1596:
1597: 1598: 1599: 1600: 1601:
1602: public function queryAll($params = array()){
1603: $this->beginQuery($params);
1604: $rst = $this->statement->fetchAll(PDO::FETCH_ASSOC);
1605: $this->statement->closeCursor();
1606: return $rst;
1607: }
1608:
1609: 1610: 1611: 1612: 1613:
1614: public function queryRow($params = array()){
1615: $this->beginQuery($params);
1616: $rst = $this->statement->fetch(PDO::FETCH_ASSOC);
1617: $this->statement->closeCursor();
1618: return $rst;
1619: }
1620:
1621: 1622: 1623: 1624: 1625:
1626: public function queryColumn($params = array()){
1627: $this->beginQuery($params);
1628: $rst = $this->statement->fetchAll(PDO::FETCH_COLUMN);
1629: $this->statement->closeCursor();
1630: return $rst;
1631: }
1632:
1633: 1634: 1635: 1636: 1637:
1638: public function queryScalar($params = array()){
1639: $this->beginQuery($params);
1640: $rst = $this->statement->fetchColumn();
1641: $this->statement->closeCursor();
1642:
1643: return $rst;
1644: }
1645:
1646: 1647: 1648: 1649: 1650:
1651: public function execute($params = array()){
1652: $this->prepare();
1653: if(false === $rst = $this->statement->execute($params)){
1654: return false;
1655: }
1656:
1657: return $this->statement->rowCount();
1658: }
1659:
1660: 1661: 1662: 1663: 1664: 1665: 1666:
1667: public function insert($table, $values){
1668: $keys = array();
1669: $params = array();
1670: $placeholders = array();
1671: foreach($values as $k => $v){
1672: $keys[] = $this->db->quoteColumn($k);
1673: $params[':'.$k] = $v;
1674: $placeholders[] = ':'.$k;
1675: }
1676: $sql = "INSERT INTO ".$this->db->quoteTable($table).' ('.implode(', ',$keys).') VALUES ('.implode(',', $placeholders).')';
1677: return $this->setSql($sql)->execute($params);
1678: }
1679:
1680: 1681: 1682: 1683: 1684: 1685: 1686: 1687: 1688:
1689: public function update($table, $values, $conditions = '', $params = array()){
1690: $updates = array();
1691: if (!is_array($values)) {
1692: $values = array($values);
1693: }
1694: foreach($values as $k => $value){
1695: if (is_integer($k)) {
1696: $updates[] = $value;
1697: } else {
1698: $updates[] = $this->db->quoteColumn($k).' = :'.$k;
1699: $params[':'.$k] = $value;
1700: }
1701: }
1702:
1703: $sql = "UPDATE ".$this->db->quoteTable($table).' SET '.implode(', ', $updates);
1704: $conditions = $this->buildConditions($conditions);
1705: if('' !== $conditions){
1706: $sql .= ' WHERE '.$conditions;
1707: }
1708:
1709: return $this->setSql($sql)->execute($params);
1710: }
1711:
1712: 1713: 1714: 1715: 1716: 1717: 1718: 1719: 1720:
1721: public function delete($table, $conditions = '', $params = array()){
1722: $sql = 'DELETE FROM '.$this->db->quoteTable($table);
1723: $conditions = $this->buildConditions($conditions);
1724: if('' !== $conditions){
1725: $sql .= ' WHERE '.$conditions;
1726: }
1727:
1728: return $this->setSql($sql)->execute($params);
1729: }
1730: }
1731: