1
0
mirror of https://github.com/dg/dibi.git synced 2025-02-24 10:53:17 +01:00
php-dibi/dibi/libs/DibiResult.php

670 lines
13 KiB
PHP
Raw Normal View History

2008-07-17 03:51:29 +00:00
<?php
/**
* dibi - tiny'n'smart database abstraction layer
* ----------------------------------------------
*
2010-01-03 15:15:37 +01:00
* @copyright Copyright (c) 2005, 2010 David Grudl
2008-07-17 03:51:29 +00:00
* @license http://dibiphp.com/license dibi license
* @link http://dibiphp.com
* @package dibi
*/
/**
* dibi result-set.
2008-07-17 03:51:29 +00:00
*
* <code>
* $result = dibi::query('SELECT * FROM [table]');
*
* $row = $result->fetch();
* $value = $result->fetchSingle();
* $table = $result->fetchAll();
* $pairs = $result->fetchPairs();
* $assoc = $result->fetchAssoc('id');
* $assoc = $result->fetchAssoc('active,#,id');
*
* unset($result);
* </code>
*
2010-01-03 15:15:37 +01:00
* @copyright Copyright (c) 2005, 2010 David Grudl
2008-07-17 03:51:29 +00:00
* @package dibi
*/
class DibiResult extends DibiObject implements IDataSource
2008-07-17 03:51:29 +00:00
{
2008-10-28 15:24:47 +00:00
/** @var array IDibiDriver */
2008-07-17 03:51:29 +00:00
private $driver;
2008-10-28 15:24:47 +00:00
/** @var array Translate table */
2010-02-24 02:43:15 +01:00
private $types;
2008-07-17 03:51:29 +00:00
/** @var DibiResultInfo */
private $meta;
2008-07-17 03:51:29 +00:00
2008-10-28 15:24:47 +00:00
/** @var bool Already fetched? Used for allowance for first seek(0) */
2008-07-17 03:51:29 +00:00
private $fetched = FALSE;
2008-10-30 15:40:17 +00:00
/** @var string returned object class */
2010-02-24 02:43:15 +01:00
private $rowClass = 'DibiRow';
2008-10-30 15:40:17 +00:00
2008-07-17 03:51:29 +00:00
/**
* @param IDibiDriver
* @param array
*/
public function __construct($driver, $config)
{
$this->driver = $driver;
if (!empty($config[dibi::RESULT_DETECT_TYPES])) {
$this->detectTypes();
}
2008-07-17 03:51:29 +00:00
}
/**
* Returns the result set resource.
* @return mixed
*/
final public function getResource()
{
return $this->getDriver()->getResultResource();
}
2009-09-09 17:01:30 +02:00
/**
* Frees the resources allocated for this result set.
* @return void
*/
final public function free()
{
if ($this->driver !== NULL) {
$this->driver->free();
$this->driver = $this->meta = NULL;
}
}
/**
* Safe access to property $driver.
* @return IDibiDriver
* @throws InvalidStateException
*/
private function getDriver()
{
if ($this->driver === NULL) {
throw new InvalidStateException('Result-set was released from memory.');
}
return $this->driver;
}
/********************* rows ****************d*g**/
2008-07-17 03:51:29 +00:00
/**
* Moves cursor position without fetching row.
* @param int the 0-based cursor pos to seek to
* @return boolean TRUE on success, FALSE if unable to seek to specified record
* @throws DibiException
*/
final public function seek($row)
{
return ($row !== 0 || $this->fetched) ? (bool) $this->getDriver()->seek($row) : TRUE;
}
/**
2009-09-09 17:01:30 +02:00
* Required by the Countable interface.
2008-07-17 03:51:29 +00:00
* @return int
*/
2009-09-09 17:01:30 +02:00
final public function count()
{
return $this->getDriver()->getRowCount();
}
/**
2009-09-09 17:01:30 +02:00
* Returns the number of rows in a result set.
* @return int
*/
2009-09-09 17:01:30 +02:00
final public function getRowCount()
2008-07-17 03:51:29 +00:00
{
return $this->getDriver()->getRowCount();
2008-07-17 03:51:29 +00:00
}
/**
2009-09-09 17:01:30 +02:00
* Returns the number of rows in a result set. Alias for getRowCount().
* @deprecated
2008-07-17 03:51:29 +00:00
*/
2009-09-09 17:01:30 +02:00
final public function rowCount()
2008-07-17 03:51:29 +00:00
{
2009-09-09 17:01:30 +02:00
return $this->getDriver()->getRowCount();
2008-07-17 03:51:29 +00:00
}
/**
2009-09-09 17:01:30 +02:00
* Required by the IteratorAggregate interface.
* @param int offset
* @param int limit
* @return DibiResultIterator
2008-07-17 03:51:29 +00:00
*/
2009-09-09 17:01:30 +02:00
final public function getIterator($offset = NULL, $limit = NULL)
2008-07-17 03:51:29 +00:00
{
2009-09-09 17:01:30 +02:00
return new DibiResultIterator($this, $offset, $limit);
2008-07-17 03:51:29 +00:00
}
2009-09-09 17:01:30 +02:00
/********************* fetching rows ****************d*g**/
2008-07-17 03:51:29 +00:00
2008-10-30 15:40:17 +00:00
/**
* Set fetched object class. This class should extend the DibiRow class.
* @param string
* @return DibiResult provides a fluent interface
2008-10-30 15:40:17 +00:00
*/
public function setRowClass($class)
{
2010-02-24 02:43:15 +01:00
$this->rowClass = $class;
return $this;
2008-10-30 15:40:17 +00:00
}
/**
* Returns fetched object class name.
* @return string
*/
public function getRowClass()
{
2010-02-24 02:43:15 +01:00
return $this->rowClass;
2008-10-30 15:40:17 +00:00
}
2008-07-17 03:51:29 +00:00
/**
* Fetches the row at current position, process optional type conversion.
* and moves the internal cursor to the next position
* @return DibiRow|FALSE array on success, FALSE if no next record
2008-07-17 03:51:29 +00:00
*/
final public function fetch()
2008-07-17 03:51:29 +00:00
{
$row = $this->getDriver()->fetch(TRUE);
if (!is_array($row)) return FALSE;
2008-07-17 03:51:29 +00:00
$this->fetched = TRUE;
// types-converting?
2010-02-24 02:43:15 +01:00
if ($this->types !== NULL) {
foreach ($this->types as $col => $type) {
2008-07-17 03:51:29 +00:00
if (isset($row[$col])) {
$row[$col] = $this->convert($row[$col], $type['type'], $type['format']);
2008-07-17 03:51:29 +00:00
}
}
}
2010-02-24 02:43:15 +01:00
return new $this->rowClass($row);
2008-07-17 03:51:29 +00:00
}
/**
* Like fetch(), but returns only first field.
* @return mixed value on success, FALSE if no next record
*/
final public function fetchSingle()
2008-07-17 03:51:29 +00:00
{
$row = $this->getDriver()->fetch(TRUE);
if (!is_array($row)) return FALSE;
$this->fetched = TRUE;
$value = reset($row);
// types-converting?
$key = key($row);
2010-02-24 02:43:15 +01:00
if (isset($this->types[$key])) {
$type = $this->types[$key];
return $this->convert($value, $type['type'], $type['format']);
2008-07-17 03:51:29 +00:00
}
return $value;
}
/**
* Fetches all records from table.
* @param int offset
* @param int limit
* @return array of DibiRow
2008-07-17 03:51:29 +00:00
*/
final public function fetchAll($offset = NULL, $limit = NULL)
2008-07-17 03:51:29 +00:00
{
$limit = $limit === NULL ? -1 : (int) $limit;
$this->seek((int) $offset);
$row = $this->fetch();
if (!$row) return array(); // empty result set
$data = array();
do {
if ($limit === 0) break;
$limit--;
$data[] = $row;
} while ($row = $this->fetch());
2008-07-17 03:51:29 +00:00
return $data;
}
/**
* Fetches all records from table and returns associative tree.
* Examples:
* - associative descriptor: col1[]col2->col3
* builds a tree: $tree[$val1][$index][$val2]->col3[$val3] = {record}
* - associative descriptor: col1|col2->col3=col4
* builds a tree: $tree[$val1][$val2]->col3[$val3] = val4
2008-07-17 03:51:29 +00:00
* @param string associative descriptor
* @return DibiRow
2008-07-17 03:51:29 +00:00
* @throws InvalidArgumentException
*/
final public function fetchAssoc($assoc)
2008-07-17 03:51:29 +00:00
{
if (strpos($assoc, ',') !== FALSE) {
return $this->oldFetchAssoc($assoc);
}
2008-07-17 03:51:29 +00:00
$this->seek(0);
$row = $this->fetch();
2008-07-17 03:51:29 +00:00
if (!$row) return array(); // empty result set
$data = NULL;
$assoc = preg_split('#(\[\]|->|=|\|)#', $assoc, NULL, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
2008-07-17 03:51:29 +00:00
// check columns
foreach ($assoc as $as) {
// offsetExists ignores NULL in PHP 5.2.1, isset() surprisingly NULL accepts
if ($as !== '[]' && $as !== '=' && $as !== '->' && $as !== '|' && !isset($row[$as])) {
2008-07-17 03:51:29 +00:00
throw new InvalidArgumentException("Unknown column '$as' in associative descriptor.");
}
}
if ($as === '->') { // must not be last
array_pop($assoc);
}
if (empty($assoc)) {
$assoc[] = '[]';
}
// make associative tree
do {
$x = & $data;
// iterative deepening
foreach ($assoc as $i => $as) {
if ($as === '[]') { // indexed-array node
$x = & $x[];
} elseif ($as === '=') { // "value" node
$x = $row->{$assoc[$i+1]};
continue 2;
} elseif ($as === '->') { // "object" node
if ($x === NULL) {
$x = clone $row;
$x = & $x->{$assoc[$i+1]};
$x = NULL; // prepare child node
} else {
$x = & $x->{$assoc[$i+1]};
}
} elseif ($as !== '|') { // associative-array node
$x = & $x[$row->$as];
}
}
if ($x === NULL) { // build leaf
$x = $row;
}
} while ($row = $this->fetch());
unset($x);
return $data;
}
/**
* @deprecated
*/
private function oldFetchAssoc($assoc)
{
$this->seek(0);
$row = $this->fetch();
if (!$row) return array(); // empty result set
$data = NULL;
$assoc = explode(',', $assoc);
2008-07-17 03:51:29 +00:00
// strip leading = and @
$leaf = '@'; // gap
2008-07-17 03:51:29 +00:00
$last = count($assoc) - 1;
while ($assoc[$last] === '=' || $assoc[$last] === '@') {
$leaf = $assoc[$last];
unset($assoc[$last]);
$last--;
if ($last < 0) {
$assoc[] = '#';
break;
}
}
do {
$x = & $data;
foreach ($assoc as $i => $as) {
if ($as === '#') { // indexed-array node
$x = & $x[];
} elseif ($as === '=') { // "record" node
if ($x === NULL) {
2009-10-06 16:51:27 +02:00
$x = (array) $row;
2008-07-17 03:51:29 +00:00
$x = & $x[ $assoc[$i+1] ];
$x = NULL; // prepare child node
} else {
$x = & $x[ $assoc[$i+1] ];
}
} elseif ($as === '@') { // "object" node
if ($x === NULL) {
$x = clone $row;
2008-07-17 03:51:29 +00:00
$x = & $x->{$assoc[$i+1]};
$x = NULL; // prepare child node
} else {
$x = & $x->{$assoc[$i+1]};
}
} else { // associative-array node
2009-10-06 16:51:27 +02:00
$x = & $x[$row->$as];
2008-07-17 03:51:29 +00:00
}
}
if ($x === NULL) { // build leaf
if ($leaf === '=') {
2009-10-06 16:51:27 +02:00
$x = (array) $row;
} else {
$x = $row;
}
2008-07-17 03:51:29 +00:00
}
} while ($row = $this->fetch());
2008-07-17 03:51:29 +00:00
unset($x);
return $data;
}
/**
* Fetches all records from table like $key => $value pairs.
* @param string associative key
* @param string value
* @return array
* @throws InvalidArgumentException
*/
final public function fetchPairs($key = NULL, $value = NULL)
2008-07-17 03:51:29 +00:00
{
$this->seek(0);
$row = $this->fetch();
2008-07-17 03:51:29 +00:00
if (!$row) return array(); // empty result set
$data = array();
if ($value === NULL) {
if ($key !== NULL) {
throw new InvalidArgumentException("Either none or both columns must be specified.");
}
// autodetect
$tmp = array_keys((array) $row);
2008-07-17 03:51:29 +00:00
$key = $tmp[0];
if (count($row) < 2) { // indexed-array
do {
$data[] = $row[$key];
} while ($row = $this->fetch());
return $data;
}
2008-07-17 03:51:29 +00:00
$value = $tmp[1];
} else {
if (!isset($row[$value])) {
2008-07-17 03:51:29 +00:00
throw new InvalidArgumentException("Unknown value column '$value'.");
}
if ($key === NULL) { // indexed-array
do {
$data[] = $row[$value];
} while ($row = $this->fetch());
2008-07-17 03:51:29 +00:00
return $data;
}
if (!isset($row[$key])) {
2008-07-17 03:51:29 +00:00
throw new InvalidArgumentException("Unknown key column '$key'.");
}
}
do {
$data[ $row[$key] ] = $row[$value];
} while ($row = $this->fetch());
2008-07-17 03:51:29 +00:00
return $data;
}
2009-09-09 17:01:30 +02:00
/********************* meta info ****************d*g**/
2008-07-17 03:51:29 +00:00
/**
* Define column type.
2009-02-05 23:13:29 +00:00
* @param string column
* @param string type (use constant Dibi::*)
2009-02-05 23:13:29 +00:00
* @param string optional format
* @return DibiResult provides a fluent interface
2008-07-17 03:51:29 +00:00
*/
final public function setType($col, $type, $format = NULL)
{
2010-02-24 02:43:15 +01:00
$this->types[$col] = array('type' => $type, 'format' => $format);
return $this;
2008-07-17 03:51:29 +00:00
}
/**
* Autodetect column types.
* @return void
*/
final public function detectTypes()
{
foreach ($this->getInfo()->getColumns() as $col) {
2010-02-24 02:43:15 +01:00
$this->types[$col->getName()] = array('type' => $col->getType(), 'format' => NULL);
}
}
2008-07-17 03:51:29 +00:00
/**
2008-10-28 15:24:47 +00:00
* Define multiple columns types.
2009-02-05 23:13:29 +00:00
* @param array
* @return DibiResult provides a fluent interface
2008-10-28 15:24:47 +00:00
* @internal
2008-07-17 03:51:29 +00:00
*/
final public function setTypes(array $types)
{
2010-02-24 02:43:15 +01:00
$this->types = $types;
return $this;
2008-07-17 03:51:29 +00:00
}
/**
* Returns column type.
* @return array ($type, $format)
*/
final public function getType($col)
{
2010-02-24 02:43:15 +01:00
return isset($this->types[$col]) ? $this->types[$col] : NULL;
2008-07-17 03:51:29 +00:00
}
/**
2009-02-05 23:13:29 +00:00
* Converts value to specified type and format.
* @param mixed value
* @param int type
* @param string format
* @return mixed
2008-07-17 03:51:29 +00:00
*/
final public function convert($value, $type, $format = NULL)
{
if ($value === NULL || $value === FALSE) {
return NULL;
2008-07-17 03:51:29 +00:00
}
switch ($type) {
case dibi::TEXT:
2008-07-17 03:51:29 +00:00
return (string) $value;
case dibi::BINARY:
2008-07-17 03:51:29 +00:00
return $this->getDriver()->unescape($value, $type);
case dibi::INTEGER:
2008-07-17 03:51:29 +00:00
return (int) $value;
case dibi::FLOAT:
2008-07-17 03:51:29 +00:00
return (float) $value;
case dibi::DATE:
case dibi::DATETIME:
2009-11-16 01:54:27 +01:00
if ((int) $value === 0) { // '', NULL, FALSE, '0000-00-00', ...
return NULL;
} elseif ($format === NULL) { // return timestamp (default)
return is_numeric($value) ? (int) $value : strtotime($value);
} elseif ($format === TRUE) { // return DateTime object
return new DateTime53(is_numeric($value) ? date('Y-m-d H:i:s', $value) : $value);
} elseif (is_numeric($value)) { // single timestamp
return date($format, $value);
} else {
$value = new DateTime53($value);
2009-11-16 01:51:49 +01:00
return $value->format($format);
}
2008-07-17 03:51:29 +00:00
case dibi::BOOL:
2008-07-17 03:51:29 +00:00
return ((bool) $value) && $value !== 'f' && $value !== 'F';
default:
return $value;
}
}
2009-09-09 17:01:30 +02:00
/**
* Returns a meta information about the current result set.
* @return DibiResultInfo
2009-09-09 17:01:30 +02:00
*/
public function getInfo()
2009-09-09 17:01:30 +02:00
{
if ($this->meta === NULL) {
$this->meta = new DibiResultInfo($this->getDriver());
2009-09-09 17:01:30 +02:00
}
return $this->meta;
}
2008-07-17 03:51:29 +00:00
/**
* @deprecated
2008-07-17 03:51:29 +00:00
*/
final public function getColumns()
2008-07-17 03:51:29 +00:00
{
return $this->getInfo()->getColumns();
}
/**
* @deprecated
*/
public function getColumnNames($fullNames = FALSE)
{
return $this->getInfo()->getColumnNames($fullNames);
2008-07-17 03:51:29 +00:00
}
2009-09-09 17:01:30 +02:00
/********************* misc tools ****************d*g**/
2008-07-17 03:51:29 +00:00
/**
* Displays complete result-set as HTML table for debug purposes.
* @return void
*/
final public function dump()
{
$i = 0;
$this->seek(0);
while ($row = $this->fetch()) {
if ($i === 0) {
2008-07-17 03:51:29 +00:00
echo "\n<table class=\"dump\">\n<thead>\n\t<tr>\n\t\t<th>#row</th>\n";
foreach ($row as $col => $foo) {
echo "\t\t<th>" . htmlSpecialChars($col) . "</th>\n";
}
echo "\t</tr>\n</thead>\n<tbody>\n";
}
echo "\t<tr>\n\t\t<th>", $i, "</th>\n";
foreach ($row as $col) {
//if (is_object($col)) $col = $col->__toString();
echo "\t\t<td>", htmlSpecialChars($col), "</td>\n";
}
echo "\t</tr>\n";
$i++;
2008-07-17 03:51:29 +00:00
}
if ($i === 0) {
2008-07-17 03:51:29 +00:00
echo '<p><em>empty result set</em></p>';
} else {
echo "</tbody>\n</table>\n";
}
}
}