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