Source for file DibiResult.php

Documentation is available at DibiResult.php

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