Location: PHPKode > projects > DIY Blog > diy-blog/lib/creole/classes/jargon/DataSet.php
<?php
/*
 *  $Id: DataSet.php,v 1.7 2005/07/13 17:28:13 hlellelid Exp $
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 *
 * This software consists of voluntary contributions made by many individuals
 * and is licensed under the LGPL. For more information please see
 * <http://creole.phpdb.org>.
 * 
 * This product includes software based on the Village framework,  
 * http://share.whichever.com/index.php?SCREEN=village.
 */

include_once 'jargon/Record.php';
require_once 'jargon/DataSetException.php';

/**
 * The DataSet represents the results of a query.
 * 
 * It contains a collection of records and implements the IteratorAggregate
 * interface so that you can use the dataset in a foreach() {} loop.
 * 
 * <code>
 *     $ds = new TableDataSet($conn, "mytable");
 *  $ds->fetchRecords();
 *  foreach($ds as $record) {
 *        $record->setValue("col1", "new value");
 *         $record->save();
 *  }
 * </code>
 * 
 * This class is extended by QueryDataSet and TableDataSet and should not be used directly. 
 * 
 * @author    Jon S. Stevens <hide@address.com> (Village)
 * @author    Hans Lellelid <hide@address.com> (Jargon)
 * @version   $Revision: 1.7 $
 * @package   jargon
 */
abstract class DataSet implements IteratorAggregate {

    /** indicates that all records should be retrieved during a fetch */
    const ALL_RECORDS = 0;

    /** this DataSet's collection of Record objects */
    protected $records;

    /** this DataSet's connection object */
    protected $conn;

    /** have all records been retrieved with the fetchRecords? */
    protected $allRecordsRetrieved = false;

    /** number of records retrieved */
    protected $recordRetrievedCount = 0;

    /** number of records that were last fetched */
    protected $lastFetchSize = 0;

    /** number of records total that have been fetched */
    protected $totalFetchCount = 0;

    /** the columns in the SELECT statement for this DataSet */
    protected $columns;

    /** the select string that was used to build this DataSet */
    protected $selectSql;

    /** the KeyDef for this DataSet */
    protected $keyDef;

    /** the result set for this DataSet */
    protected $resultSet;

    /** the Statement for this DataSet */
    protected $stmt;    

    /**
     * Return iterator (for IteratorAggregate interface).
     * This allows this class to be used in a foreach() loop.
     * <code>
     *     foreach($dataset as $record) {
     *         print "col1 = " . $record->getValue("col1") . "\n";
     *  }
     * </code>
     * @return DataSetIterator
     */
    public function getIterator()
    {
        $it = new DataSetIterator($this);
        return $it;
    }
    
    /**
     * Gets the ResultSet for this DataSet
     *
     * @return ResultSet The result set for this DataSet
     * @throws DataSetException - if resultset is null
     */
    public function resultSet()
    {
        if ($this->resultSet === null) {
            throw new DataSetException ("ResultSet is null.");
        }            
        return $this->resultSet;
    }

    /**
     * Check if all the records have been retrieve
     *
     * @return boolean True if all records have been retrieved
     */
    public function allRecordsRetrieved()
    {
        return $this->allRecordsRetrieved;
    }

    /**
      * Set all records retrieved
      * @param boolean $set
      * @return void
      */
    function setAllRecordsRetrieved($set)
    {
        $this->allRecordsRetrieved = $set;
    }

    /**
      * Remove a record from the DataSet's internal storage
      *
      * @param  Record $rec
      * @return Record The record removed
      */
    public function removeRecord(Record $rec)
    {
        $loc = array_search($rec, $this->records, true);
        $removeRec = array_splice($this->records, $loc, 1);                 
        return $removeRec;
    }

    /**
      *  Remove all records from the DataSet and nulls those records out
      *  and close() the DataSet.
      *
      * @return     an instance of myself
      */
    public function clearRecords()
    {
        $this->records = null;
        return $this;
    }

    /**
      * Removes the records from the DataSet, but does not null the records out
      *
      * @return     an instance of myself
      */
    public function releaseRecords()
    {
        $this->records = null;
        $this->recordRetrievedCount = 0;
        $this->lastFetchSize = 0;
        $this->setAllRecordsRetrieved(false);
        return $this;
    }

    /**
      * Releases the records, closes the ResultSet and the Statement, and
      * nulls the Schema and Connection references.
      *
      * @return void
      */
    public function close()
    {
        $this->releaseRecords();
        $this->schema = null;

        if ($this->resultSet !== null && !$this instanceof QueryDataSet) {
            $this->resultSet->close();
        }
            
        $this->resultSet = null;
        
        if ( $this->stmt !== null ) {
            $this->stmt->close();
        }

        $this->conn = null;
    }

    /**
      * Essentially the same as releaseRecords, but it won't work on a QueryDataSet that
      * has been created with a ResultSet
      *
      * @return DataSet This object.
      * @throws DataSetException
      */
    public function reset()
    {
        if (! ($this->resultSet !== null && ($this instanceof QueryDataSet))) {
            return $this->releaseRecords();
        } else {
            throw new DataSetException("You cannot call reset() on a QueryDataSet.");
        }
    }

    /**
      * Gets the current database connection
      *
      * @return Connection A database connection.
      */
    public function connection()
    {
        return $this->conn;
    }

    /**
      * Gets the Schema for this DataSet
      *
      * @return  Schema The Schema for this DataSet
      */
    public function schema()
    {
        return $this->schema;
    }

    /**
      * Get Record at 0 based index position
      *
      * @param int $pos
      * @return Record An instance of the found Record
      * @throws DataSetException
      */
    public function getRecord($pos)
    {
        if ($this->containsRecord($pos)) {
            $rec = $this->records[$pos];
            if ($this instanceof TableDataSet) {
                $rec->markForUpdate();
            }
            $this->recordRetrievedCount++;
            return $rec;
        }
        throw new DataSetException ("Record not found at index: " . $pos);
    }

    /**
      * Find Record at 0 based index position. This is an internal alternative 
      * to getRecord which tries to be smart about the type of record it is.
      *
      * @param int $pos
      * @return Record an instance of the found Record
      * @throws DataSetException
      */
    public function findRecord($pos) 
    {
        if ($this->containsRecord($pos)) {
            return $this->records[$pos];
        }
        throw new DataSetException ("Record not found at index: " . $pos);
    }

    /**
     * Check to see if the DataSet contains a Record at 0 based position
     *
     * @param   pos
     * @return     true if record exists
     */
    public function containsRecord($pos)
    {
        return (isset($this->records[$pos]));            
    }   

    /**
     * Causes the DataSet to hit the database and fetch max records,
     * starting at start. Record count begins at 0.
     *
     * This method supports two signatures:
       *     - fetchRecords(10); // LIMIT = 10
     *     - fetchRecords(5, 10); // OFFSET = 5, LIMIT = 10
     * 
     * @param int $p1 max - or start if $p2 is set
     * @param int $p2 start
     * @return DataSet This class.
     * @throws   SQLException
     * @throws   DataSetException
     */
    public function fetchRecords($p1 = 0, $p2 = null)
    {
        if ($p2 !== null) {
            $start = $p1;
            $max = $p2;
        } else {
            $start = 0;
            $max = $p1;
        }
        
        if ($this->lastFetchSize() > 0 && $this->records !== null) {
            throw new DataSetException("You must call DataSet::clearRecords() before executing DataSet::fetchRecords() again!");
        }

        try {
        
            if ($this->stmt === null && $this->resultSet === null) {
                $this->stmt = $this->conn->createStatement();
                $this->stmt->setOffset($start);
                $this->stmt->setLimit($max);
                // reset, since native limit applied
                $start = 0;
                $max = 0;
                $this->resultSet = $this->stmt->executeQuery($this->selectSql);
            }

            if ($this->resultSet !== null) {

                $this->records = array();

                $startCounter = 0;
                $fetchCount = 0;
                while (! $this->allRecordsRetrieved() ) {
                    if ($this->resultSet->next()) {
                        if ($startCounter >= $start) {
                            $this->records[] = new Record($this);
                            $fetchCount++;
                            if ($fetchCount === $max) { // check after because we must fetch at least 1
                                break;
                            }
                        } else {
                            $startCounter++;
                        }
                    } else {
                        $this->setAllRecordsRetrieved(true);
                        break;
                    }
                }                
                $this->lastFetchSize = $fetchCount;
            }
        } catch (SQLException $e) {
            if ($this->stmt) $this->stmt->close();
            throw $e;
        }

        return $this;
    }

    /**
     * The number of records that were fetched with the last fetchRecords.
     *
     * @return int
     */
    public function lastFetchSize()
    {
        return $this->lastFetchSize;
    }

    /**
     * gets the KeyDef object for this DataSet
     *
     * @return KeyDef The keydef for this DataSet, this value can be null
     */
    public function keydef()
    {
        return $this->keyDef;
    }

    /**
     * This returns a represention of this DataSet
     */
    public function __toString()
    {
        $sb = "";        
        for ($i = 0, $size = $this->size(); $i < $size; $i++) {
            $sb .= $this->getRecord($i);
        }
        return $sb;
    }       

    /**
      * Classes extending this class must implement this method.
      *
      * @return string The SELECT SQL.
      * @throws DataSetException;
      */
    public abstract function getSelectSql();

    /**
     * Returns the columns attribute for the DataSet
     *
     * @return     the columns attribute for the DataSet
     */
    public function getColumns()
    {
        return $this->columns;
    }

    /**
     * Gets the number of Records in this DataSet. It is 0 based. 
     *
     * @return int Number of Records in this DataSet
     */
    public function size()
    {
        if ( $this->records === null )
            return 0;
        return count($this->records);
    }
}


/**
 * The Iterator returned by DataSet::getIterator() that loops through the records.
 * 
 * Thanks to PHP5 SPL this allows you to foreach() over a DataSet:
 * <code>
 *   $ds = new QueryDataSet($conn, "select * from author");
 *   $ds->fetchRecords();
 *   foreach($ds as $rec) {
 *     print $rec->getValue("mycol");
 *   }
 * </code>
 * 
 * @see DataSet::getIterator()
 */
class DataSetIterator implements Iterator {

    private $ds;
    private $size;
    private $pos;
    
    function __construct(DataSet $ds) {
        $this->ds = $ds;
        $this->size = $ds->size();
    }
    
    function rewind() {
        $this->pos = 0;
    }
    
    function valid() {
        return $this->pos < $this->size;
    }
    
    function key() {
        return $this->pos;
    }
    
    function current() {
        return $this->ds->getRecord($this->pos);
    }
    
    function next() {
        $this->pos++;
    }
}
Return current item: DIY Blog