PHP Classes

File: DATA/MySQL5/Table.php

Recommend this page to a friend!
  Classes of Martin Alterisio   DATA   DATA/MySQL5/Table.php   Download  
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
Author: By
Last change: v0.8 - abstraction of sql data types and inboxing of these values into objects with constraints checking
v0.8 - disabling of inboxing of sql data types is available through property ->withoutInboxing
v0.8 - changed how array offsets are mapped to the conditions of selecting a row (for possible security problems). If available, an unique field is used, and access using the pk is provided using the special property ->byXXXX
Date: 16 years ago
Size: 12,483 bytes
 

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; } } ?>