Location: PHPKode > scripts > DATA > data/DATA/MySQL5/Row.php
<?php
/**
 * @package DATA_MySQL5
 */

/**
 * This class is the abstraction of a MySQL5 table row implementing the
 * array access and iteration behavior.
 * 
 * Field modification will affect the real database row, unless the object
 * has been marked as read-only (example: when using integer index access
 * to the field), or unless the object has been detached (example: cloned
 * objects). Only the field modified will be affected.
 * 
 * When cloning a row the new object will become detached from database and
 * modifications won't be proxied to the database. The detached object will
 * become reattached once it is inserted again the in table array. This can
 * be used to bundle modifications to a row before commiting them, or
 * duplicating rows with small changes.
 * 
 * Data won't remain updated to external changes. Preferred use of row
 * objects is as volatile containers.
 * 
 * Field data returned will be inboxed into representative objects 
 * ({@link DATA_SQLChar}, {@link DATA_SQLVarchar}, {@link DATA_SQLDecimal},
 * {@link DATA_SQLInt}, {@link DATA_SQLSmallInt}
 * {@link DATA_SQLDate}, {@link DATA_SQLTime}, {@link DATA_SQLDatetime}).
 * 
 * Examples:
 * <code>
 * // Retrieve a field value
 * $fieldValue = $DB['table'][$rowOffset]['field'];
 * 
 * // Change a field value
 * $DB['table'][$rowOffset]['field'] = '...';
 * // or
 * $row = $DB['table'][$rowOffset];
 * $row['field'] = '...';
 * 
 * // Cloning
 * $row = clone $DB['table'][$rowOffset];
 * $row['field'] = '...';
 * $row['field2'] = '...';
 * $DB['table'][$rowOffset] = $row;
 * // or on a new row or offset
 * $DB['table'][] = $row;
 * </code>
 */
class DATA_MySQL5_Row implements ArrayAccess, Countable, IteratorAggregate {
    /**
     * The table name.
     * @var string
     */
    protected $table;
    /**
     * The row data. It may be in raw or inboxed format.
     * @var array
     */
    protected $data;
    /**
     * Array for field checking purposes.
     * @var array
     */
    protected $fields;
    /**
     * Read only flag.
     * @var bool
     */
    protected $readOnly;
    /**
     * Detached from db flag
     * @var bool
     */
    protected $detached;
    /**
     * Disables inboxing in this object.
     * @var bool
     */
    protected $inboxingDisabled;
    
    /**
     * Default constructor.
     * 
     * @access protected
     * @param string $table The table.
     * @param array $data The fetched row data.
     * @param bool $readOnly The created object has read only access if set to true.
     *                       Optional, defaults to false.
     */
    public function __construct($table, $data, $readOnly = false) {
        $this->table = $table;
        $this->setData($data);
        $this->readOnly = $readOnly;
        $this->detached = false;
        $this->inboxingDisabled = false;
    }
    
    /**
     * isset(..) handler. Indicates if field exists.
     * 
     * @param string $field The field name.
     * @return bool True if field exists, false otherwise.
     */
    public function offsetExists($field) {
        if (is_int($field)) {
            return count($this->data) > $field && $field >= 0;
        } else {
            return isset($this->fields[$field]);
        }
    }
    
    /**
     * [..] handler. Returns the value of the field requested.
     * 
     * Throws {@link DATA_FieldDoesntExist}.
     * 
     * @param string $field The field name.
     * @return string The field value.
     */
    public function offsetGet($field) {
        if (!$this->offsetExists($field)) {
            throw new DATA_FieldDoesntExist($this->table, $field);
        }
        if (is_int($field)) {
            $fields = array_keys($this->data);
            $field = $fields[$field];
        }
        return $this->inboxField($field, $this->data[$field]);
    }
    
    /**
     * [..] = handler. Sets the value of a field.
     * 
     * Throws {@link DATA_ReadOnly}, {@link DATA_FieldDoesntExist},
     * {@link DATA_SQLTypeConstraintFailed}.
     * 
     * @param string $field The field name.
     * @param string $value The field value.
     */
    public function offsetSet($field, $value) {
        if ($this->readOnly) {
            throw new DATA_ReadOnly();
        }
        if (!$this->offsetExists($field)) {
            throw new DATA_FieldDoesntExist($this->table, $field);
        }
        if (is_int($field)) {
            $fields = array_keys($this->data);
            $field = $fields[$field];
        }
        try {
            $value = $this->inboxField($field, $value);
        } catch (DATA_SQLTypeConstraintFailed $exception) {
            $exception->setTable($this->table);
            $exception->setField($field);
            throw $exception;
        }
        if (!$this->detached) {
            $keys = DATA_MySQL5_Schema::getPrimaryKey($this->table);
            DATA_MySQL5_Access::query("
                UPDATE `{$this->table}`
                   SET `{$field}` = " . DATA_MySQL5_Access::prepareData($value) . "
                 WHERE `{$keys[0]}` = '" . DATA_MySQL5_Access::escape($this->data[$keys[0]]) . "'
            ");
        }
        $this->data[$field] = $value;
    }
    
    /**
     * unset(..) handler. Fields cannot be unset.
     * 
     * Will throw {@link DATA_CannotRemoveField DATA_CannotRemoveField exception}.
     */
    public function offsetUnset($offset) {
        throw new DATA_CannotRemoveField();
    }
    
    /**
     * count(..) handler. Returns fields count.
     * 
     * @return int How many fields there are on this row.
     */
    public function count() {
        return count($this->data);
    }
    
    /**
     * Provides the iterator to be used on a foreach loop.
     * 
     * @return ArrayIterator The fields data iterator.
     */
    public function getIterator() {
        if ($this->inboxingDisabled) {
            return new ArrayIterator($this->data);
        }
        $inboxedData = array();
        foreach ($this->data as $field => $value) {
            $inboxedData[$field] = DATA_MySQL5_Schema::getSQLTypeFactory($this->table, $field)->inbox($value);
        }
        return new ArrayIterator($inboxedData);
    }
    
    /**
     * clone handler. Detaches cloned object from the database.
     */
    public function __clone() {
        $this->readOnly = false;
        $this->detached = true;
    }
    
    /**
     * Reattaches the object to the database.
     * 
     * @param int $autoid Autogenerated numeric id. Optional, 0 if no id was generated.
     */
    public function reattach($autoid = 0) {
        if ($autoid != 0) {
            $autofield = DATA_MySQL5_Schema::getAutoIncrementField($this->table);
            $this->data[$autofield] = (int)$autoid;
        }
        $sql = "SELECT * FROM `{$this->table}` ";
        $sql .= $this->buildWhereStatement();
        $query = DATA_MySQL5_Access::query($sql);
        $this->setData(DATA_MySQL5_Access::fetchAssoc($query));
        $this->readOnly = false;
        $this->detached = false;
    }
    
    /**
     * Builds a where statement to select this row.
     * 
     * @return string SQL where statement to select the requested row.
     */
    protected function buildWhereStatement() {
        $keys = DATA_MySQL5_Schema::getPrimaryKey($this->table);
        $sql = 'WHERE';
        $sep = '';
        foreach ($keys as $key) {
            $sql .= "$sep `$key` = " . DATA_MySQL5_Access::prepareData($this->data[$key]);
            $sep = ' AND';
        }
        return $sql;
    }
    
    /**
     * Sets the fetched row data.
     * 
     * @param array $data The fetched row data.
     */
    protected function setData($data) {
        $this->data = $data;
        $this->fields = array();
        foreach (array_keys($data) as $field) {
            $this->fields[$field] = true;
        }
    }
    
    /**
     * Returns inboxed version of the field provided.
     * 
     * Throws {@link DATA_SQLTypeConstraintFailed}.
     * 
     * @param string $field The field name.
     * @param null|int|string|DATA_SQLType $value The field value
     * @return DATA_SQLType Inboxed field.
     */
    protected function inboxField($field, $value) {
        if ($this->inboxingDisabled) return $value;
        return DATA_MySQL5_Schema::getSQLTypeFactory($this->table, $field)->inbox($value);
    }
    
    /**
     * Member property overloading.
     * 
     * withoutInboxing property returns a db object with inboxing of
     * mysql types disabled.
     * 
     * @param string $propname Property name.
     * @return mixed Property value.
     */
    public function __get($propname) {
        if ($propname == 'withoutInboxing') {
            $newRow = clone $this;
            $newRow->readOnly = $this->readOnly;
            $newRow->detached = $this->detached;
            $newRow->inboxingDisabled = true;
            return $newRow;
        }
        throw new Exception("Undefined property: {$propname}");
    }
}
?>
Return current item: DATA