1: <?php
2:
3: 4: 5: 6: 7: 8: 9: 10: 11:
12:
13:
14:
15: 16: 17: 18: 19: 20: 21: 22: 23: 24:
25: class NTableSelection extends NObject implements Iterator, ArrayAccess, Countable
26: {
27:
28: protected $connection;
29:
30:
31: protected $sqlBuilder;
32:
33:
34: protected $name;
35:
36:
37: protected $primary;
38:
39:
40: protected $primarySequence = FALSE;
41:
42:
43: protected $rows;
44:
45:
46: protected $data;
47:
48:
49: protected $referenced = array();
50:
51:
52: protected $referencing = array();
53:
54:
55: protected $referencingPrototype = array();
56:
57:
58: protected $aggregation = array();
59:
60:
61: protected $accessed;
62:
63:
64: protected $prevAccessed;
65:
66:
67: protected $observeCache = FALSE;
68:
69:
70: protected $checkReferenced = FALSE;
71:
72:
73: protected $keys = array();
74:
75:
76:
77: 78: 79: 80: 81:
82: public function __construct($table, NConnection $connection)
83: {
84: $this->name = $table;
85: $this->connection = $connection;
86: $this->primary = $connection->getDatabaseReflection()->getPrimary($table);
87: $this->sqlBuilder = new NSqlBuilder($this);
88: }
89:
90:
91:
92: public function __destruct()
93: {
94: $this->saveCacheState();
95: }
96:
97:
98:
99: public function __clone()
100: {
101: $this->sqlBuilder = clone $this->sqlBuilder;
102: $this->sqlBuilder->setSelection($this);
103: }
104:
105:
106:
107: 108: 109:
110: public function getConnection()
111: {
112: return $this->connection;
113: }
114:
115:
116:
117: 118: 119:
120: public function getName()
121: {
122: return $this->name;
123: }
124:
125:
126:
127: 128: 129:
130: public function getPrimary()
131: {
132: return $this->primary;
133: }
134:
135:
136:
137: 138: 139:
140: public function getPrimarySequence()
141: {
142: if ($this->primarySequence === FALSE) {
143: $this->primarySequence = NULL;
144:
145: $driver = $this->connection->getSupplementalDriver();
146: if ($driver->isSupported(ISupplementalDriver::SUPPORT_SEQUENCE)) {
147: foreach ($driver->getColumns($this->name) as $column) {
148: if ($column['name'] === $this->primary) {
149: $this->primarySequence = $column['vendor']['sequence'];
150: break;
151: }
152: }
153: }
154: }
155:
156: return $this->primarySequence;
157: }
158:
159:
160:
161: 162: 163: 164:
165: public function setPrimarySequence($sequence)
166: {
167: $this->primarySequence = $sequence;
168: return $this;
169: }
170:
171:
172:
173: 174: 175:
176: public function getSql()
177: {
178: return $this->sqlBuilder->buildSelectQuery();
179: }
180:
181:
182:
183: 184: 185: 186: 187:
188: public function getPreviousAccessed()
189: {
190: $cache = $this->connection->getCache();
191: if ($this->rows === NULL && $cache && !is_string($this->prevAccessed)) {
192: $this->accessed = $this->prevAccessed = $cache->load(array(__CLASS__, $this->name, $this->sqlBuilder->getConditions()));
193: }
194:
195: return $this->prevAccessed;
196: }
197:
198:
199:
200: 201: 202: 203:
204: public function getSqlBuilder()
205: {
206: return $this->sqlBuilder;
207: }
208:
209:
210:
211:
212:
213:
214:
215: 216: 217: 218: 219:
220: public function get($key)
221: {
222: $clone = clone $this;
223: return $clone->find($key)->fetch();
224: }
225:
226:
227:
228: 229: 230: 231:
232: public function fetch()
233: {
234: $this->execute();
235: $return = current($this->data);
236: next($this->data);
237: return $return;
238: }
239:
240:
241:
242: 243: 244: 245: 246: 247:
248: public function fetchPairs($key, $value = NULL)
249: {
250: $return = array();
251: foreach ($this as $row) {
252: $return[is_object($row[$key]) ? (string) $row[$key] : $row[$key]] = ($value ? $row[$value] : $row);
253: }
254: return $return;
255: }
256:
257:
258:
259:
260:
261:
262:
263: 264: 265: 266: 267:
268: public function select($columns)
269: {
270: $this->emptyResultSet();
271: $this->sqlBuilder->addSelect($columns);
272: return $this;
273: }
274:
275:
276:
277: 278: 279: 280: 281:
282: public function find($key)
283: {
284: if (is_array($this->primary) && NValidators::isList($key)) {
285: foreach ($this->primary as $i => $primary) {
286: $this->where($primary, $key[$i]);
287: }
288: } elseif (is_array($key)) {
289: $this->where($key);
290: } else {
291: $this->where($this->primary, $key);
292: }
293:
294: return $this;
295: }
296:
297:
298:
299: 300: 301: 302: 303: 304: 305:
306: public function where($condition, $parameters = array())
307: {
308: if (is_array($condition)) {
309: foreach ($condition as $key => $val) {
310: if (is_int($key)) {
311: $this->where($val);
312: } else {
313: $this->where($key, $val);
314: }
315: }
316: return $this;
317: }
318:
319: $args = func_get_args();
320: if (call_user_func_array(array($this->sqlBuilder, 'addWhere'), $args)) {
321: $this->emptyResultSet();
322: }
323:
324: return $this;
325: }
326:
327:
328:
329: 330: 331: 332: 333:
334: public function order($columns)
335: {
336: $this->emptyResultSet();
337: $this->sqlBuilder->addOrder($columns);
338: return $this;
339: }
340:
341:
342:
343: 344: 345: 346: 347: 348:
349: public function limit($limit, $offset = NULL)
350: {
351: $this->emptyResultSet();
352: $this->sqlBuilder->setLimit($limit, $offset);
353: return $this;
354: }
355:
356:
357:
358: 359: 360: 361: 362: 363:
364: public function page($page, $itemsPerPage)
365: {
366: return $this->limit($itemsPerPage, ($page - 1) * $itemsPerPage);
367: }
368:
369:
370:
371: 372: 373: 374: 375: 376:
377: public function group($columns, $having = NULL)
378: {
379: $this->emptyResultSet();
380: $this->sqlBuilder->setGroup($columns, $having);
381: return $this;
382: }
383:
384:
385:
386:
387:
388:
389:
390: 391: 392: 393: 394:
395: public function aggregation($function)
396: {
397: $selection = $this->createSelectionInstance();
398: $selection->getSqlBuilder()->importConditions($this->getSqlBuilder());
399: $selection->select($function);
400: foreach ($selection->fetch() as $val) {
401: return $val;
402: }
403: }
404:
405:
406:
407: 408: 409: 410: 411:
412: public function count($column = NULL)
413: {
414: if (!$column) {
415: $this->execute();
416: return count($this->data);
417: }
418: return $this->aggregation("COUNT($column)");
419: }
420:
421:
422:
423: 424: 425: 426: 427:
428: public function min($column)
429: {
430: return $this->aggregation("MIN($column)");
431: }
432:
433:
434:
435: 436: 437: 438: 439:
440: public function max($column)
441: {
442: return $this->aggregation("MAX($column)");
443: }
444:
445:
446:
447: 448: 449: 450: 451:
452: public function sum($column)
453: {
454: return $this->aggregation("SUM($column)");
455: }
456:
457:
458:
459:
460:
461:
462:
463: protected function execute()
464: {
465: if ($this->rows !== NULL) {
466: return;
467: }
468:
469: $this->observeCache = TRUE;
470:
471: try {
472: $result = $this->query($this->sqlBuilder->buildSelectQuery());
473:
474: } catch (PDOException $exception) {
475: if (!$this->sqlBuilder->getSelect() && $this->prevAccessed) {
476: $this->prevAccessed = '';
477: $this->accessed = array();
478: $result = $this->query($this->sqlBuilder->buildSelectQuery());
479: } else {
480: throw $exception;
481: }
482: }
483:
484: $this->rows = array();
485: $usedPrimary = TRUE;
486: $result->setFetchMode(PDO::FETCH_ASSOC);
487: foreach ($result as $key => $row) {
488: $row = $this->createRow($result->normalizeRow($row));
489: $primary = $row->getSignature(FALSE);
490: $usedPrimary = $usedPrimary && $primary;
491: $this->rows[($tmp=$primary) ? $tmp : $key] = $row;
492: }
493: $this->data = $this->rows;
494:
495: if ($usedPrimary && !is_string($this->accessed)) {
496: foreach ((array) $this->primary as $primary) {
497: $this->accessed[$primary] = TRUE;
498: }
499: }
500: }
501:
502:
503:
504: protected function createRow(array $row)
505: {
506: return new NTableRow($row, $this);
507: }
508:
509:
510:
511: protected function createSelectionInstance($table = NULL)
512: {
513: return new NTableSelection(($tmp=$table) ? $tmp : $this->name, $this->connection);
514: }
515:
516:
517:
518: protected function createGroupedSelectionInstance($table, $column)
519: {
520: return new NGroupedTableSelection($this, $table, $column);
521: }
522:
523:
524:
525: protected function query($query)
526: {
527: return $this->connection->queryArgs($query, $this->sqlBuilder->getParameters());
528: }
529:
530:
531:
532: protected function emptyResultSet()
533: {
534: if ($this->rows === NULL) {
535: return;
536: }
537:
538: $this->rows = NULL;
539: $this->saveCacheState();
540: }
541:
542:
543:
544: protected function saveCacheState()
545: {
546: if ($this->observeCache && ($cache = $this->connection->getCache()) && !$this->sqlBuilder->getSelect() && $this->accessed != $this->prevAccessed) {
547: $cache->save(array(__CLASS__, $this->name, $this->sqlBuilder->getConditions()), $this->accessed);
548: }
549: }
550:
551:
552:
553: 554: 555: 556:
557: protected function getRefTable(& $refPath)
558: {
559: return $this;
560: }
561:
562:
563:
564: 565: 566: 567: 568: 569:
570: public function access($key, $cache = TRUE)
571: {
572: if ($cache === NULL) {
573: if (is_array($this->accessed)) {
574: $this->accessed[$key] = FALSE;
575: }
576: return FALSE;
577: }
578:
579: if ($key === NULL) {
580: $this->accessed = '';
581:
582: } elseif (!is_string($this->accessed)) {
583: $this->accessed[$key] = $cache;
584: }
585:
586: if ($cache && !$this->sqlBuilder->getSelect() && $this->prevAccessed && ($key === NULL || !isset($this->prevAccessed[$key]))) {
587: $this->prevAccessed = '';
588: $this->emptyResultSet();
589: return TRUE;
590: }
591:
592: return FALSE;
593: }
594:
595:
596:
597:
598:
599:
600:
601: 602: 603: 604: 605:
606: public function insert($data)
607: {
608: if ($data instanceof NTableSelection) {
609: $data = $data->getSql();
610:
611: } elseif ($data instanceof Traversable) {
612: $data = iterator_to_array($data);
613: }
614:
615: $return = $this->connection->query($this->sqlBuilder->buildInsertQuery(), $data);
616: $this->checkReferenced = TRUE;
617:
618: if (!is_array($data)) {
619: return $return->rowCount();
620: }
621:
622: if (!is_array($this->primary) && !isset($data[$this->primary]) && ($id = $this->connection->lastInsertId($this->getPrimarySequence()))) {
623: $data[$this->primary] = $id;
624: }
625:
626: $row = $this->createRow($data);
627: if ($signature = $row->getSignature(FALSE)) {
628: $this->rows[$signature] = $row;
629: }
630:
631: return $row;
632: }
633:
634:
635:
636: 637: 638: 639: 640: 641:
642: public function update($data)
643: {
644: if ($data instanceof Traversable) {
645: $data = iterator_to_array($data);
646:
647: } elseif (!is_array($data)) {
648: throw new InvalidArgumentException;
649: }
650:
651: if (!$data) {
652: return 0;
653: }
654:
655: return $this->connection->queryArgs(
656: $this->sqlBuilder->buildUpdateQuery(),
657: array_merge(array($data), $this->sqlBuilder->getParameters())
658: )->rowCount();
659: }
660:
661:
662:
663: 664: 665: 666:
667: public function delete()
668: {
669: return $this->query($this->sqlBuilder->buildDeleteQuery())->rowCount();
670: }
671:
672:
673:
674:
675:
676:
677:
678: 679: 680: 681: 682: 683: 684:
685: public function getReferencedTable($table, $column, $checkReferenced = FALSE)
686: {
687: $referenced = & $this->getRefTable($refPath)->referenced[$refPath . "$table.$column"];
688: if ($referenced === NULL || $checkReferenced || $this->checkReferenced) {
689: $this->execute();
690: $this->checkReferenced = FALSE;
691: $keys = array();
692: foreach ($this->rows as $row) {
693: if ($row[$column] === NULL)
694: continue;
695:
696: $key = $row[$column] instanceof NTableRow ? $row[$column]->getPrimary() : $row[$column];
697: $keys[$key] = TRUE;
698: }
699:
700: if ($referenced !== NULL && array_keys($keys) === array_keys($referenced->rows)) {
701: return $referenced;
702: }
703:
704: if ($keys) {
705: $referenced = $this->createSelectionInstance($table);
706: $referenced->where($referenced->primary, array_keys($keys));
707: } else {
708: $referenced = array();
709: }
710: }
711:
712: return $referenced;
713: }
714:
715:
716:
717: 718: 719: 720: 721: 722: 723:
724: public function getReferencingTable($table, $column, $active = NULL)
725: {
726: $prototype = & $this->getRefTable($refPath)->referencingPrototype[$refPath . "$table.$column"];
727: if (!$prototype) {
728: $prototype = $this->createGroupedSelectionInstance($table, $column);
729: $prototype->where("$table.$column", array_keys((array) $this->rows));
730: }
731:
732: $clone = clone $prototype;
733: $clone->setActive($active);
734: return $clone;
735: }
736:
737:
738:
739:
740:
741:
742:
743: public function rewind()
744: {
745: $this->execute();
746: $this->keys = array_keys($this->data);
747: reset($this->keys);
748: }
749:
750:
751:
752:
753: public function current()
754: {
755: if (($key = current($this->keys)) !== FALSE) {
756: return $this->data[$key];
757: } else {
758: return FALSE;
759: }
760: }
761:
762:
763:
764: 765: 766:
767: public function key()
768: {
769: return current($this->keys);
770: }
771:
772:
773:
774: public function next()
775: {
776: next($this->keys);
777: }
778:
779:
780:
781: public function valid()
782: {
783: return current($this->keys) !== FALSE;
784: }
785:
786:
787:
788:
789:
790:
791:
792: 793: 794: 795: 796: 797:
798: public function offsetSet($key, $value)
799: {
800: $this->execute();
801: $this->data[$key] = $value;
802: }
803:
804:
805:
806: 807: 808: 809: 810:
811: public function offsetGet($key)
812: {
813: $this->execute();
814: return $this->data[$key];
815: }
816:
817:
818:
819: 820: 821: 822: 823:
824: public function offsetExists($key)
825: {
826: $this->execute();
827: return isset($this->data[$key]);
828: }
829:
830:
831:
832: 833: 834: 835: 836:
837: public function offsetUnset($key)
838: {
839: $this->execute();
840: unset($this->data[$key]);
841: }
842:
843: }
844: