Packages

  • dibi
    • drivers
    • nette
    • reflection
  • None
  • PHP

Classes

  • dibi
  • DibiConnection
  • DibiDataSource
  • DibiDateTime
  • DibiEvent
  • DibiFileLogger
  • DibiFirePhpLogger
  • DibiFluent
  • DibiObject
  • DibiResult
  • DibiResultIterator
  • DibiRow
  • DibiTranslator

Interfaces

  • IDataSource
  • IDibiDriver
  • IDibiReflector
  • IDibiResultDriver

Exceptions

  • DibiDriverException
  • DibiException
  • DibiNotImplementedException
  • DibiNotSupportedException
  • DibiPcreException
  • Overview
  • Package
  • Class
  • Tree
  1: <?php
  2: 
  3: /**
  4:  * This file is part of the "dibi" - smart database abstraction layer.
  5:  *
  6:  * Copyright (c) 2005 David Grudl (http://davidgrudl.com)
  7:  *
  8:  * For the full copyright and license information, please view
  9:  * the file license.txt that was distributed with this source code.
 10:  */
 11: 
 12: 
 13: 
 14: /**
 15:  * dibi result set.
 16:  *
 17:  * <code>
 18:  * $result = dibi::query('SELECT * FROM [table]');
 19:  *
 20:  * $row   = $result->fetch();
 21:  * $value = $result->fetchSingle();
 22:  * $table = $result->fetchAll();
 23:  * $pairs = $result->fetchPairs();
 24:  * $assoc = $result->fetchAssoc('id');
 25:  * $assoc = $result->fetchAssoc('active,#,id');
 26:  *
 27:  * unset($result);
 28:  * </code>
 29:  *
 30:  * @author     David Grudl
 31:  * @package    dibi
 32:  *
 33:  * @property-read mixed $resource
 34:  * @property-read IDibiResultDriver $driver
 35:  * @property-read int $rowCount
 36:  * @property-read DibiResultIterator $iterator
 37:  * @property string $rowClass
 38:  * @property-read DibiResultInfo $info
 39:  */
 40: class DibiResult extends DibiObject implements IDataSource
 41: {
 42:     /** @var array  IDibiResultDriver */
 43:     private $driver;
 44: 
 45:     /** @var array  Translate table */
 46:     private $types = array();
 47: 
 48:     /** @var DibiResultInfo */
 49:     private $meta;
 50: 
 51:     /** @var bool  Already fetched? Used for allowance for first seek(0) */
 52:     private $fetched = FALSE;
 53: 
 54:     /** @var string  returned object class */
 55:     private $rowClass = 'DibiRow';
 56: 
 57:     /** @var string  date-time format */
 58:     private $dateFormat = '';
 59: 
 60: 
 61: 
 62:     /**
 63:      * @param  IDibiResultDriver
 64:      * @param  array
 65:      */
 66:     public function __construct($driver, $config)
 67:     {
 68:         $this->driver = $driver;
 69:         $this->detectTypes();
 70: 
 71:         if (!empty($config['formatDateTime'])) {
 72:             $this->dateFormat = is_string($config['formatDateTime']) ? $config['formatDateTime'] : '';
 73:         }
 74:     }
 75: 
 76: 
 77: 
 78:     /**
 79:      * Returns the result set resource.
 80:      * @return mixed
 81:      */
 82:     final public function getResource()
 83:     {
 84:         return $this->getDriver()->getResultResource();
 85:     }
 86: 
 87: 
 88: 
 89:     /**
 90:      * Frees the resources allocated for this result set.
 91:      * @return void
 92:      */
 93:     final public function free()
 94:     {
 95:         if ($this->driver !== NULL) {
 96:             $this->driver->free();
 97:             $this->driver = $this->meta = NULL;
 98:         }
 99:     }
100: 
101: 
102: 
103:     /**
104:      * Safe access to property $driver.
105:      * @return IDibiResultDriver
106:      * @throws RuntimeException
107:      */
108:     private function getDriver()
109:     {
110:         if ($this->driver === NULL) {
111:             throw new RuntimeException('Result-set was released from memory.');
112:         }
113: 
114:         return $this->driver;
115:     }
116: 
117: 
118: 
119:     /********************* rows ****************d*g**/
120: 
121: 
122: 
123:     /**
124:      * Moves cursor position without fetching row.
125:      * @param  int      the 0-based cursor pos to seek to
126:      * @return boolean  TRUE on success, FALSE if unable to seek to specified record
127:      * @throws DibiException
128:      */
129:     final public function seek($row)
130:     {
131:         return ($row !== 0 || $this->fetched) ? (bool) $this->getDriver()->seek($row) : TRUE;
132:     }
133: 
134: 
135: 
136:     /**
137:      * Required by the Countable interface.
138:      * @return int
139:      */
140:     final public function count()
141:     {
142:         return $this->getDriver()->getRowCount();
143:     }
144: 
145: 
146: 
147:     /**
148:      * Returns the number of rows in a result set.
149:      * @return int
150:      */
151:     final public function getRowCount()
152:     {
153:         return $this->getDriver()->getRowCount();
154:     }
155: 
156: 
157: 
158:     /**
159:      * Returns the number of rows in a result set. Alias for getRowCount().
160:      * @deprecated
161:      */
162:     final public function rowCount()
163:     {
164:         trigger_error(__METHOD__ . '() is deprecated; use count($res) or $res->getRowCount() instead.', E_USER_WARNING);
165:         return $this->getDriver()->getRowCount();
166:     }
167: 
168: 
169: 
170:     /**
171:      * Required by the IteratorAggregate interface.
172:      * @return DibiResultIterator
173:      */
174:     final public function getIterator()
175:     {
176:         if (func_num_args()) {
177:             trigger_error(__METHOD__ . ' arguments $offset & $limit have been dropped; use SQL clauses instead.', E_USER_WARNING);
178:         }
179:         return new DibiResultIterator($this);
180:     }
181: 
182: 
183: 
184:     /********************* fetching rows ****************d*g**/
185: 
186: 
187: 
188:     /**
189:      * Set fetched object class. This class should extend the DibiRow class.
190:      * @param  string
191:      * @return DibiResult  provides a fluent interface
192:      */
193:     public function setRowClass($class)
194:     {
195:         $this->rowClass = $class;
196:         return $this;
197:     }
198: 
199: 
200: 
201:     /**
202:      * Returns fetched object class name.
203:      * @return string
204:      */
205:     public function getRowClass()
206:     {
207:         return $this->rowClass;
208:     }
209: 
210: 
211: 
212:     /**
213:      * Fetches the row at current position, process optional type conversion.
214:      * and moves the internal cursor to the next position
215:      * @return DibiRow|FALSE  array on success, FALSE if no next record
216:      */
217:     final public function fetch()
218:     {
219:         $row = $this->getDriver()->fetch(TRUE);
220:         if (!is_array($row)) {
221:             return FALSE;
222:         }
223:         $this->fetched = TRUE;
224:         $this->normalize($row);
225:         return new $this->rowClass($row);
226:     }
227: 
228: 
229: 
230:     /**
231:      * Like fetch(), but returns only first field.
232:      * @return mixed  value on success, FALSE if no next record
233:      */
234:     final public function fetchSingle()
235:     {
236:         $row = $this->getDriver()->fetch(TRUE);
237:         if (!is_array($row)) {
238:             return FALSE;
239:         }
240:         $this->fetched = TRUE;
241:         $this->normalize($row);
242:         return reset($row);
243:     }
244: 
245: 
246: 
247:     /**
248:      * Fetches all records from table.
249:      * @param  int  offset
250:      * @param  int  limit
251:      * @return array of DibiRow
252:      */
253:     final public function fetchAll($offset = NULL, $limit = NULL)
254:     {
255:         $limit = $limit === NULL ? -1 : (int) $limit;
256:         $this->seek((int) $offset);
257:         $row = $this->fetch();
258:         if (!$row) return array();  // empty result set
259: 
260:         $data = array();
261:         do {
262:             if ($limit === 0) break;
263:             $limit--;
264:             $data[] = $row;
265:         } while ($row = $this->fetch());
266: 
267:         return $data;
268:     }
269: 
270: 
271: 
272:     /**
273:      * Fetches all records from table and returns associative tree.
274:      * Examples:
275:      * - associative descriptor: col1[]col2->col3
276:      *   builds a tree:          $tree[$val1][$index][$val2]->col3[$val3] = {record}
277:      * - associative descriptor: col1|col2->col3=col4
278:      *   builds a tree:          $tree[$val1][$val2]->col3[$val3] = val4
279:      * @param  string  associative descriptor
280:      * @return DibiRow
281:      * @throws InvalidArgumentException
282:      */
283:     final public function fetchAssoc($assoc)
284:     {
285:         if (strpos($assoc, ',') !== FALSE) {
286:             return $this->oldFetchAssoc($assoc);
287:         }
288: 
289:         $this->seek(0);
290:         $row = $this->fetch();
291:         if (!$row) return array();  // empty result set
292: 
293:         $data = NULL;
294:         $assoc = preg_split('#(\[\]|->|=|\|)#', $assoc, NULL, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
295: 
296:         // check columns
297:         foreach ($assoc as $as) {
298:             // offsetExists ignores NULL in PHP 5.2.1, isset() surprisingly NULL accepts
299:             if ($as !== '[]' && $as !== '=' && $as !== '->' && $as !== '|' && !property_exists($row, $as)) {
300:                 throw new InvalidArgumentException("Unknown column '$as' in associative descriptor.");
301:             }
302:         }
303: 
304:         if ($as === '->') { // must not be last
305:             array_pop($assoc);
306:         }
307: 
308:         if (empty($assoc)) {
309:             $assoc[] = '[]';
310:         }
311: 
312:         // make associative tree
313:         do {
314:             $x = & $data;
315: 
316:             // iterative deepening
317:             foreach ($assoc as $i => $as) {
318:                 if ($as === '[]') { // indexed-array node
319:                     $x = & $x[];
320: 
321:                 } elseif ($as === '=') { // "value" node
322:                     $x = $row->{$assoc[$i+1]};
323:                     continue 2;
324: 
325:                 } elseif ($as === '->') { // "object" node
326:                     if ($x === NULL) {
327:                         $x = clone $row;
328:                         $x = & $x->{$assoc[$i+1]};
329:                         $x = NULL; // prepare child node
330:                     } else {
331:                         $x = & $x->{$assoc[$i+1]};
332:                     }
333: 
334:                 } elseif ($as !== '|') { // associative-array node
335:                     $x = & $x[$row->$as];
336:                 }
337:             }
338: 
339:             if ($x === NULL) { // build leaf
340:                 $x = $row;
341:             }
342: 
343:         } while ($row = $this->fetch());
344: 
345:         unset($x);
346:         return $data;
347:     }
348: 
349: 
350: 
351:     /**
352:      * @deprecated
353:      */
354:     private function oldFetchAssoc($assoc)
355:     {
356:         $this->seek(0);
357:         $row = $this->fetch();
358:         if (!$row) return array();  // empty result set
359: 
360:         $data = NULL;
361:         $assoc = explode(',', $assoc);
362: 
363:         // strip leading = and @
364:         $leaf = '@';  // gap
365:         $last = count($assoc) - 1;
366:         while ($assoc[$last] === '=' || $assoc[$last] === '@') {
367:             $leaf = $assoc[$last];
368:             unset($assoc[$last]);
369:             $last--;
370: 
371:             if ($last < 0) {
372:                 $assoc[] = '#';
373:                 break;
374:             }
375:         }
376: 
377:         do {
378:             $x = & $data;
379: 
380:             foreach ($assoc as $i => $as) {
381:                 if ($as === '#') { // indexed-array node
382:                     $x = & $x[];
383: 
384:                 } elseif ($as === '=') { // "record" node
385:                     if ($x === NULL) {
386:                         $x = $row->toArray();
387:                         $x = & $x[ $assoc[$i+1] ];
388:                         $x = NULL; // prepare child node
389:                     } else {
390:                         $x = & $x[ $assoc[$i+1] ];
391:                     }
392: 
393:                 } elseif ($as === '@') { // "object" node
394:                     if ($x === NULL) {
395:                         $x = clone $row;
396:                         $x = & $x->{$assoc[$i+1]};
397:                         $x = NULL; // prepare child node
398:                     } else {
399:                         $x = & $x->{$assoc[$i+1]};
400:                     }
401: 
402: 
403:                 } else { // associative-array node
404:                     $x = & $x[$row->$as];
405:                 }
406:             }
407: 
408:             if ($x === NULL) { // build leaf
409:                 if ($leaf === '=') {
410:                     $x = $row->toArray();
411:                 } else {
412:                     $x = $row;
413:                 }
414:             }
415: 
416:         } while ($row = $this->fetch());
417: 
418:         unset($x);
419:         return $data;
420:     }
421: 
422: 
423: 
424:     /**
425:      * Fetches all records from table like $key => $value pairs.
426:      * @param  string  associative key
427:      * @param  string  value
428:      * @return array
429:      * @throws InvalidArgumentException
430:      */
431:     final public function fetchPairs($key = NULL, $value = NULL)
432:     {
433:         $this->seek(0);
434:         $row = $this->fetch();
435:         if (!$row) return array();  // empty result set
436: 
437:         $data = array();
438: 
439:         if ($value === NULL) {
440:             if ($key !== NULL) {
441:                 throw new InvalidArgumentException("Either none or both columns must be specified.");
442:             }
443: 
444:             // autodetect
445:             $tmp = array_keys($row->toArray());
446:             $key = $tmp[0];
447:             if (count($row) < 2) { // indexed-array
448:                 do {
449:                     $data[] = $row[$key];
450:                 } while ($row = $this->fetch());
451:                 return $data;
452:             }
453: 
454:             $value = $tmp[1];
455: 
456:         } else {
457:             if (!property_exists($row, $value)) {
458:                 throw new InvalidArgumentException("Unknown value column '$value'.");
459:             }
460: 
461:             if ($key === NULL) { // indexed-array
462:                 do {
463:                     $data[] = $row[$value];
464:                 } while ($row = $this->fetch());
465:                 return $data;
466:             }
467: 
468:             if (!property_exists($row, $key)) {
469:                 throw new InvalidArgumentException("Unknown key column '$key'.");
470:             }
471:         }
472: 
473:         do {
474:             $data[ $row[$key] ] = $row[$value];
475:         } while ($row = $this->fetch());
476: 
477:         return $data;
478:     }
479: 
480: 
481: 
482:     /********************* column types ****************d*g**/
483: 
484: 
485: 
486:     /**
487:      * Autodetect column types.
488:      * @return void
489:      */
490:     private function detectTypes()
491:     {
492:         $cache = DibiColumnInfo::getTypeCache();
493:         try {
494:             foreach ($this->getDriver()->getResultColumns() as $col) {
495:                 $this->types[$col['name']] = $cache->{$col['nativetype']};
496:             }
497:         } catch (DibiNotSupportedException $e) {}
498:     }
499: 
500: 
501: 
502:     /**
503:      * Converts values to specified type and format.
504:      * @param  array
505:      * @return void
506:      */
507:     private function normalize(array & $row)
508:     {
509:         foreach ($this->types as $key => $type) {
510:             if (!isset($row[$key])) { // NULL
511:                 continue;
512:             }
513:             $value = $row[$key];
514:             if ($value === FALSE || $type === dibi::TEXT) {
515: 
516:             } elseif ($type === dibi::INTEGER) {
517:                 $row[$key] = is_float($tmp = $value * 1) ? $value : $tmp;
518: 
519:             } elseif ($type === dibi::FLOAT) {
520:                 $row[$key] = (string) ($tmp = (float) $value) === $value ? $tmp : $value;
521: 
522:             } elseif ($type === dibi::BOOL) {
523:                 $row[$key] = ((bool) $value) && $value !== 'f' && $value !== 'F';
524: 
525:             } elseif ($type === dibi::DATE || $type === dibi::DATETIME) {
526:                 if ((int) $value === 0) { // '', NULL, FALSE, '0000-00-00', ...
527: 
528:                 } elseif ($this->dateFormat === '') { // return DateTime object (default)
529:                     $row[$key] = new DibiDateTime(is_numeric($value) ? date('Y-m-d H:i:s', $value) : $value);
530: 
531:                 } elseif ($this->dateFormat === 'U') { // return timestamp
532:                     $row[$key] = is_numeric($value) ? (int) $value : strtotime($value);
533: 
534:                 } elseif (is_numeric($value)) { // formatted date
535:                     $row[$key] = date($this->dateFormat, $value);
536: 
537:                 } else {
538:                     $value = new DibiDateTime($value);
539:                     $row[$key] = $value->format($this->dateFormat);
540:                 }
541: 
542:             } elseif ($type === dibi::BINARY) {
543:                 $row[$key] = $this->getDriver()->unescape($value, $type);
544:             }
545:         }
546:     }
547: 
548: 
549: 
550:     /**
551:      * Define column type.
552:      * @param  string  column
553:      * @param  string  type (use constant Dibi::*)
554:      * @return DibiResult  provides a fluent interface
555:      */
556:     final public function setType($col, $type)
557:     {
558:         $this->types[$col] = $type;
559:         return $this;
560:     }
561: 
562: 
563: 
564:     /**
565:      * Returns column type.
566:      * @return string
567:      */
568:     final public function getType($col)
569:     {
570:         return isset($this->types[$col]) ? $this->types[$col] : NULL;
571:     }
572: 
573: 
574: 
575:     /********************* meta info ****************d*g**/
576: 
577: 
578: 
579:     /**
580:      * Returns a meta information about the current result set.
581:      * @return DibiResultInfo
582:      */
583:     public function getInfo()
584:     {
585:         if ($this->meta === NULL) {
586:             $this->meta = new DibiResultInfo($this->getDriver());
587:         }
588:         return $this->meta;
589:     }
590: 
591: 
592: 
593:     /**
594:      * @deprecated
595:      */
596:     final public function getColumns()
597:     {
598:         return $this->getInfo()->getColumns();
599:     }
600: 
601: 
602: 
603:     /** @deprecated */
604:     public function getColumnNames($fullNames = FALSE)
605:     {
606:         trigger_error(__METHOD__ . '() is deprecated; use $res->getInfo()->getColumnNames() instead.', E_USER_WARNING);
607:         return $this->getInfo()->getColumnNames($fullNames);
608:     }
609: 
610: 
611: 
612:     /********************* misc tools ****************d*g**/
613: 
614: 
615: 
616:     /**
617:      * Displays complete result set as HTML table for debug purposes.
618:      * @return void
619:      */
620:     final public function dump()
621:     {
622:         $i = 0;
623:         $this->seek(0);
624:         while ($row = $this->fetch()) {
625:             if ($i === 0) {
626:                 echo "\n<table class=\"dump\">\n<thead>\n\t<tr>\n\t\t<th>#row</th>\n";
627: 
628:                 foreach ($row as $col => $foo) {
629:                     echo "\t\t<th>" . htmlSpecialChars($col) . "</th>\n";
630:                 }
631: 
632:                 echo "\t</tr>\n</thead>\n<tbody>\n";
633:             }
634: 
635:             echo "\t<tr>\n\t\t<th>", $i, "</th>\n";
636:             foreach ($row as $col) {
637:                 //if (is_object($col)) $col = $col->__toString();
638:                 echo "\t\t<td>", htmlSpecialChars($col), "</td>\n";
639:             }
640:             echo "\t</tr>\n";
641:             $i++;
642:         }
643: 
644:         if ($i === 0) {
645:             echo '<p><em>empty result set</em></p>';
646:         } else {
647:             echo "</tbody>\n</table>\n";
648:         }
649:     }
650: 
651: }
652: 
dibi API documentation API documentation generated by ApiGen 2.3.0