PHP Classes
Icontem

File: DATA/MySQL5/Table.php


  Search   All class groups All class groups   Latest entries Latest entries   Top 10 charts Top 10 charts   Newsletter Newsletter   Blog Blog   Forums Forums   Help FAQ Help FAQ  
  Login   Register  
Recommend this page to a friend! ReTweet ReTweet Stumble It! Stumble It! Bookmark in del.icio.us Bookmark in del.icio.us
  Classes of Martin Alterisio  >  DATA  >  DATA/MySQL5/Table.php  
File: DATA/MySQL5/Table.php
Role: Class source
Content type: text/plain
Description: This class is the abstraction of a MySQL5 table implementing the array access and iteration behavior.
Class: DATA
Access data stored in MySQL tables like arrays
 

Contents

Class file image Download
<?php
/**
 * @package DATA_MySQL5
 */

/**
 * This class is the abstraction of a MySQL5 table implementing the
 * array access and iteration behavior.
 * 
 * Row offset may be indicated by either an int or a string.
 * Each access has different meanings:
 * 
 * int: the n-th row as fetched through a simple select query. This access is
 * read only.
 * 
 * string: either the primary key value or, if the table has an auto-increment
 * primary key and there is only one unique index, the unique field value.
 * 
 * Example:
 * <code>
 * // loop through a table
 * foreach ($DB['table'] as $rowNumber => $row) {
 *     ...
 * }
 * 
 * // check for row existence
 * if (isset($DB['table'][$rowIndex])) {
 *     ...
 * }
 * 
 * // get the first row in table
 * $readOnlyRow = $DB['table'][0];
 * // get the row with pk or an uniquely indexed field equal to 'name'
 * $row = $DB['table']['name'];
 * 
 * // insert a row
 * $DB['table'][] = array(...);
 * // update a row
 * $DB['table']['name'] = $row;
 * 
 * // delete a row
 * unset($DB['table']['name']);
 * </code>
 */
class DATA_MySQL5_Table implements ArrayAccess, Countable, IteratorAggregate {
    /**
     * Stores table name on construction for future operations.
     * @var string
     */
    protected $table;
    /**
     * Strategy for mapping an array offset to rows in the table.
     * @var DATA_MySQL5_AssociativeIndexingStrategy
     */
    protected $indexingStrategy;
    /**
     * Disables inboxing in this object.
     * @var bool
     */
    protected $inboxingDisabled;
    
    /**
     * Constructor.
     * 
     * @param string $table The table name.
     * @param DATA_MySQL5_AssociativeIndexingStrategy $indexingStrategy Indexing strategy. Optional, defaults to {@link DATA_MySQL5_DefaultIndexingStrategy}.
     */
    public function __construct($table, $indexingStrategy = null) {
        $this->table = $table;
        if ($indexingStrategy === null) {
            $indexingStrategy = new DATA_MySQL5_DefaultIndexingStrategy($table);
        }
        $this->indexingStrategy = $indexingStrategy;
        $this->inboxingDisabled = false;
    }
    
    /**
     * isset(..) handler. Indicates if row exists.
     * 
     * Throws {@link DATA_PrimaryKeyNeeded}, {@link DATA_PrimaryKeyTooLarge}.
     * 
     * @param mixed $row The row offset.
     * @return bool True if row exists, false otherwise.
     */
    public function offsetExists($row) {
        $row = $this->inboxRowOffset($row);
        if (is_int($row)) {
            return 0 <= $row && $row < $this->count();
        } else {
            $sql = $this->buildSelectRowQuery($row, "COUNT(*)");
            $query = DATA_MySQL5_Access::query($sql);
            return DATA_MySQL5_Access::result($query, 0) > 0;
        }
    }
    
    /**
     * [..] handler. Returns a row object corresponding to the row requested.
     * 
     * Throws {@link DATA_PrimaryKeyNeeded}, {@link DATA_PrimaryKeyTooLarge},
     * {@link DATA_RowDoesntExist}.
     * 
     * @param mixed $row The row offset.
     * @return DATA_MySQL5_Row The row object.
     */
    public function offsetGet($row) {
        $row = $this->inboxRowOffset($row);
        $sql = $this->buildSelectRowQuery($row);
        $query = DATA_MySQL5_Access::query($sql);
        $data = DATA_MySQL5_Access::fetchAssoc($query);
        if (!$data) throw new DATA_RowDoesntExist($this->table, $row);
        $row = new DATA_MySQL5_Row($this->table, $data, is_int($row));
        if ($this->inboxingDisabled) {
            $row = $row->withoutInboxing;
        }
        return $row;
    }
    
    /**
     * [..] = handler. Updates or insert a row.
     * 
     * Throws {@link DATA_PrimaryKeyNeeded}, {@link DATA_PrimaryKeyTooLarge}.
     * 
     * @param mixed $row The row offset.
     * @param array|ArrayAccess $rowObject The row data.
     */
    public function offsetSet($row, $rowObject) {
        $row = $this->inboxRowOffset($row);
        if (is_int($row)) {
            throw new DATA_ReadOnly();
        } else {
            if ($row !== null && $this->offsetExists($row)) {
                DATA_MySQL5_Access::query($this->buildUpdateRowQuery($row, $rowObject));
            } else {
                DATA_MySQL5_Access::query($this->buildInsertRowQuery($row, $rowObject));
            }
            if ($rowObject instanceof DATA_MySQL5_Row) {
                $rowObject->reattach(DATA_MySQL5_Access::getInsertID());
            }
        }
    }
    
    /**
     * unset(..) handler. Deletes a row.
     * 
     * Throws {@link DATA_PrimaryKeyNeeded}, {@link DATA_PrimaryKeyTooLarge}.
     * 
     * @param mixed $row The row offset.
     */
    public function offsetUnset($row) {
        $row = $this->inboxRowOffset($row);
        if (is_int($row)) {
            throw new DATA_ReadOnly();
        } else {
            DATA_MySQL5_Access::query($this->buildDeleteRowQuery($row));
        }
    }
    
    /**
     * count(..) handler. Returns rows count.
     * 
     * @return int How many rows there are on this table.
     */
    public function count() {
        return (int)DATA_MySQL5_Access::result(DATA_MySQL5_Access::query("SELECT COUNT(*) FROM `{$this->table}`"), 0);
    }
    
    /**
     * Provides the iterator to be used on a foreach loop.
     * 
     * @return DATA_MySQL5_TableIterator The rows iterator.
     */
    public function getIterator() {
        return new DATA_MySQL5_TableIterator($this->table);
    }
    
    /**
     * Builds a select query to fetch one row by the index used for array access.
     * 
     * @param int|DATA_SQLType $row The row offset.
     * @param string $select The string to be used as field selection.
     *                       Optional, default to "*".
     * @return string SQL query to select the requested row.
     */
    protected function buildSelectRowQuery($row, $select = "*") {
        $sql = "
            SELECT $select
              FROM `{$this->table}`
        ";
        if (is_int($row)) {
            return $sql . "
                 LIMIT 1 OFFSET $row
            ";
        } else {
            return $sql . $this->buildWhereStatement($row) . "
                 LIMIT 1
            ";
        }
    }
    
    /**
     * Builds an update query to modify one row using the data provided on the array access.
     * 
     * @param DATA_SQLType $row The row offset.
     * @param array|DATA_MySQL5_Row $rowObject The row data.
     * @return string SQL query to update the requested row.
     */
    protected function buildUpdateRowQuery($row, $rowObject) {
        $sql = "UPDATE `{$this->table}` SET ";
        $separator = '';
        foreach ($rowObject as $field => $value) {
            try {
                $value = $this->inboxField($field, $value);
            } catch (DATA_SQLTypeConstraintFailed $exception) {
                $exception->setTable($this->table);
                $exception->setField($field);
                throw $exception;
            }
            $sql .= $separator . "`{$field}` = " . DATA_MySQL5_Access::prepareData($value);
            $separator = ', ';
        }
        $sql .= $this->buildWhereStatement($row);
        return $sql;
    }
    
    /**
     * Builds a delete query to remove one row by the index used for array access.
     * 
     * @param DATA_SQLType $row The row offset.
     * @return string SQL query to delete the requested row.
     */
    protected function buildDeleteRowQuery($row) {
        $sql = "DELETE FROM `{$this->table}` ";
        $sql .= $this->buildWhereStatement($row);
        return $sql;
    }
    
    /**
     * Builds a where statement to select one row by the index used for array access.
     * 
     * @param DATA_SQLType $row The row offset.
     * @return string SQL where statement to select the requested row.
     */
    protected function buildWhereStatement($row) {
        $conditions = $this->indexingStrategy->buildWhereConditions($row);
        return " WHERE {$conditions} ";
    }
    
    /**
     * Builds an insert query to add one row using the data provided on the array access.
     * 
     * @param DATA_SQLType $row The row offset.
     * @param array|DATA_MySQL5_Row $rowObject The row data.
     * @return string SQL query to insert the requested row.
     */
    protected function buildInsertRowQuery($row, $rowObject) {
        $fieldList = '';
        $valueList = '';
        $separator = '';
        
        if ($row !== null) {
            foreach ($this->indexingStrategy->getAdditionalInsertFields($row) as $field => $value) {
                if (isset($rowObject[$field])) continue;
                $fieldList .= $separator . "`$field`";
                $valueList .= $separator . DATA_MySQL5_Access::prepareData($value);
                $separator = ', ';
            }
        }
        
        foreach ($rowObject as $field => $value) {
            try {
                $value = $this->inboxField($field, $value);
            } catch (DATA_SQLTypeConstraintFailed $exception) {
                $exception->setTable($this->table);
                $exception->setField($field);
                throw $exception;
            }
            $fieldList .= $separator . "`$field`";
            $valueList .= $separator . DATA_MySQL5_Access::prepareData($value);
            $separator = ', ';
        }
        
        return "INSERT INTO `{$this->table}`($fieldList) VALUES($valueList)";
    }
    
    /**
     * Returns inboxed version of the row offset provided.
     * 
     * Throws {@link DATA_PrimaryKeyNeeded}, {@link DATA_PrimaryKeyTooLarge}.
     * 
     * @param null|int|string|DATA_SQLType $row The row offset.
     * @return DATA_SQLType Inboxed row offset.
     */
    protected function inboxRowOffset($row) {
        if ($this->inboxingDisabled) return $row;
        if (is_int($row) || $row === null) return $row;
        return $this->indexingStrategy->inboxRowOffset($row);
    }
    
    /**
     * 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.
     * 
     * Watches for properties names byXXXX and maps to the corresponding
     * indexing strategy.
     * 
     * withoutInboxing property returns a table object with inboxing of
     * mysql types disabled.
     * 
     * @param string $propname Property name.
     * @return mixed Property value.
     */
    public function __get($propname) {
        if (substr($propname, 0, 2) == 'by') {
            $field = self::fromUpperCamelCaseToUnderscore(substr($propname, 2));
            
            $keys = DATA_MySQL5_Schema::getPrimaryKey($this->table);
            if (count($keys) == 1 && $keys[0] == $field) {
                $indexingStrategy = new DATA_MySQL5_PrimaryKeyIndexingStrategy($this->table);
                return new DATA_MySQL5_Table($this->table, $indexingStrategy);
            }
            
            throw new DATA_NotImplemented('->byXXXX access by any field other than the primary key');
        } else if ($propname == 'withoutInboxing') {
            $newTable = clone $this;
            $newTable->inboxingDisabled = true;
            return $newTable;
        }
        throw new Exception("Undefined property: {$propname}");
    }
    
    /**
     * Converts from upper camel case notation to underscore lowercase notation.
     * 
     * @param string $name Name in upper camel case.
     * @return string Name in underscore lowercase.
     * 
     * @todo Move this function somewhere more appropiate. 
     */
    protected static function fromUpperCamelCaseToUnderscore($name) {
        $name = preg_replace('/(.)([A-Z])/', '$1_$2', $name);
        $name = strtolower($name);
        return $name;
    }
}
?>

 
  Advertise on this site Advertise on this site   Site map Site map   Statistics Statistics   Site tips Site tips   Privacy policy Privacy policy   Contact Contact  

For more information send a message to :
info at phpclasses dot org.
Copyright (c) Icontem 1999-2009 PHP Classes - PHP Class Scripts
  PHP Book Reviews - Reviews of books and other products