Source for file Record.class.php
Documentation is available at Record.class.php
* Gumbo Library Framework
* This library is being released under the terms of the New BSD License. A
* copy of the license is packaged with the software (LICENSE.txt). If no
* copy is found, a copy of the license template can be found at:
* http://www.opensource.org/licenses/bsd-license.php
* @copyright Copyright (c) 2007, iBayou, Michael Luster
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
* @author Michael Luster <mluster79@yahoo.com>
* @link http://sourceforge.net/projects/phpgumbo
* The Record Class represents a single database record from a table. The methods
* will provide access to the data of the record. If the class contains a
* database record, the values will be read only. In order to change records,
* the class must be extended.
* Once the Record is loaded, the program will be able to access the internals of
* the class. This can be accomplished by calling the get method with the column
* name or using the overloaded __get method.
* In order to manipulate database records, the Gumbo_Record class must be extended.
* The class will represent a database table. The child class must define the table
* structure to the Gumbo_Record parent. It should also define get and set methods
* for each column. A 'getColumnName' method will return the value. A 'setColumnName'
* method will set the value, forcing the data to be validated and filtered as
* necessary. The only way to modify Gumbo_Records is through a child class.
* Implementation for a get method:
* public function get[Column] () {
* return $this->get ("column_name");
* Implementation for a set method:
* public function setColumn ($val) {
* // perform validation/filtering, throw necessary Exceptions
* // do not set value if validation fails
* $this->set ("column_name", $val);
* The Gumbo_Record class contains some very advanced features for accessing database
* information. These features are not necessary, but are very useful. The first
* step is to pass the value to the parent Constructor.
* public function __construct ($id=null) {
* parent::__construct ($id);
* The argument can be an integer or an array. If an integer is passed, it will be
* used as the primary key column value to run a single SELECT statement against the
* database. If an array is passed, it should be an associative array returned from
* a database query. The keys in the array are the column names. (ie. mysql_fetch_assoc ()).
* This feature is useful when returning multiple rows of data, passing the associative
* array into the Gumbo_Record object. If returning multiple records, this feature will
* avoid having to run a single query for each Gumbo_Record.
* The child Record should instantiate a Prototype. This will pass important setup information
* about the child Class and the Database Table it represents. The Prototype will define
* the Table Name, the Primary Key, Table Columns, and more (see Gumbo_Abstract_Prototype).
* The best implementation of this feature is to create a single Prototype object
* during the programs lifetime. This can be accomplished by defining the Prototype
* as a Singleton, or setting a static prototype property inside the child
* Record. The child Record will then initialize the Prototype, passing the object to
* the parent 'init' method. (The implementation is if a Prototype is not a Singleton.
* Make the appropriate substitution to retrieve a Prototype object for Singletons)
* class User_Prototype extends Gumbo_Record_Prototype implements Gumbo_Interface_Singleton {
* // static property in Record
* class User extends Gumbo_Record {
* private static $_prototype;
* public function __construct ($id=null) {
* if (User::$_prototype == null) {
* User::$_prototype = new User_Prototype ();
* $this->init (User::$_prototype);
* $this->load (); // populates the Record
* The following features will help make the Record more dynamic. This includes using
* maps to transmit data, or loading data through XML.
* COUNT COLUMN (optional)
* This is a special feature, marking a registered column as a count column. This is
* a column whose value is an integer, used for counting. For example, if a table
* wants to track the number of times a record has been viewed 'view', the 'view'
* column could be registered as COUNT COLUMN and use the increment/decrement methods
* for easy changes. Count Columns are defined by the Prototype.
* The Gumbo_Record class performs four types of queries: "SELECT,INSERT,UPDATE,DELETE".
* The four methods (_reload,create,save,kill) each have defined queries to perform the
* single operation. In some cases, a more advanced or specific query is required. This
* is where the CALLBACKS are useful. The programmer can define more complex queries,
* placing them in a separate method. The Prototype would define the callback functions
* for each operation. If a callback is not defined, the Gumbo_Record class will use the
* pre-programmed defaults. (see Gumbo_Record_Prototype for more details)
* The Gumbo_Record class contains four predefined Maps. This will associate the column names
* with particular elements. Maps are defined by the Prototype. In order to use a map,
* simple call the 'map' method, passing the appropriate data and which map to use. This
* is best used with Form Input.
* There will be instances where accessing information from one table requires information from
* another table by means of a join. This complex query should be placed inside a callback
* function. The associative array returned will contain the columns from the table including
* keys referencing the joined table data. The Gumbo_Record class takes the extra columns
* created by a join and places them in a special location called ColumnsExtra. Extra JOIN
* data will be read only. The child class should define 'get' methods to access the information.
* The parent get method just requires the column name and the value will be returned.
* The child constructor should define the parent XML element name in the constructor. This will
* be used to format an XML string or parse a supplied string.
* The XML feature will accept an XML string containing the data to apply to the Gumbo_Record. This
* will be applied to the column 'xml' Map. The method will require the name of the Parent XML
* element name. The parent element should contain an 'id' attribute with the value of the
* The next part of the feature will return the object as a formatted XML string. This will use
* the 'xml' Map. The formatted string will be surrounded by the parent XML value.
* @copyright Copyright (c) 2007, iBayou, Michael Luster
* @license http://www.opensource.org/licenses/bsd-license.php New BSD License
* @author Michael Luster <mluster79@yahoo.com>
* @link http://sourceforge.net/projects/phpgumbo
* @desc Generic Record Class
gumbo_load ("Interface_Record");
gumbo_load ("Interface_Mapper");
class Gumbo_Record implements Gumbo_Interface_Record, Gumbo_Interface_Mapper {
/** @var Gumbo_Record_Prototype $_prototype child class Prototype */
/** @var mixed $_primary_value primary key column value (usually an 'id' but could contain values) */
/** @var array $_columns table columns with values (if available) */
/** @var array $_columns_mod modified column values */
/** @var array $_columns_extra additional columns not part of the table columns */
/** @var array $_columns_count special columns that perform incremental/decremental counts */
/** @var Gumbo_Interface_Map[] $_maps available Maps */
/** @var bool $_loaded if the record was loaded */
/** @var bool $_modified if the object was modified */
/** @var bool $_read_only tells if the object is read only */
* Sets the value of a table column using 'key' map
public function __set ($key, $val) {
if (!$this->isMap ("key")) { return; }
if (!$this->isMap ("method")) { return; }
if (!$map->isForeignKey ($key)) { return; }
$column =
$map->getKey ($key);
if (!$this->_getMap ("method")->isKey ($column)) { return; }
$method =
"set" .
ucwords ($this->_getMap ("method")->getForeignKey ($column));
* Unsets the value of the supplied table column
if (!$this->isMap ("key")) { return; }
if (!$map->isForeignKey ($key)) { return; }
$this->set ($map->getKey ($key), null);
* Returns the raw value of the column name
public function __get ($column) {
if ($this->isMap ("key") &&
$this->isMap ("method")) {
if (!$map->isForeignKey ($key)) { return; }
$column =
$map->getKey ($key);
if (!$this->_getMap ("method")->isKey ($column)) { return; }
$method =
"get" .
ucfirst ($this->_getMap ("method")->getForeignKey ($column));
return $this->$method ($val);
return $this->get ($column);
* Returns if the column is set
if (!$this->isMap ("key")) { return; }
if (!$map->isForeignKey ($key)) { return; }
$column =
$map->getKey ($key);
if ($this->_isColumn ($column)) { return true; }
* Initializes the Record by passing a Record_Prototype
* @param Gumbo_Record_Prototype $prototype
* @throws Gumbo_Exception
final protected function init (Gumbo_Record_Prototype $prototype) {
foreach ($prototype->getColumns as $key=>
$val) {
if ($prototype->isMap ("method")) {
foreach ($prototype->getMap ("method") as $key=>
$val) {
$this->_maps ['method']->add ($key, $val);
if ($prototype->isMap ("key")) {
foreach ($prototype->getMap ("key") as $key=>
$val) {
$this->_maps ['key']->add ($key, $val);
if ($prototype->isMap ("form")) {
foreach ($prototype->getMap ("form") as $key=>
$val) {
$this->_maps ['form']->add ($key, $val);
if ($prototype->isMap ("xml")) {
foreach ($prototype->getMap ("xml") as $key=>
$val) {
$this->_maps ['xml']->add ($key, $val);
* Loads the record into memory
final public function load () {
* Loads an XML string, but only the first level of the parent
* @param string $parent name of top level XML element
* @throws Gumbo_Exception
public function loadXML ($xml, $parent=
null) {
if (!$this->isMap ("xml")) {
if (!$this->isMap ("method")) {
$sxml =
new SimpleXMLElement ($xml);
foreach ($sxml->$parent->children () as $child) {
if (!$map->isForeignKey ($child->getName ())) { continue; }
$column =
$map->getKey ($child->getName ());
if (!$this->_getMap ("method")->isKey ($column)) { continue; }
$method =
"set" .
ucwords ($this->_getMap ("method")->getForeignKey ($column));
$name =
$child->getName ();
$this->$method ((string)
$sxml->$parent->$name);
$e->setFunction (__METHOD__
);
* Loads new Record or refreshes the saved/created Item values
final private function _reload () {
// reset the values of the object
// load the specific object into memory
// check for callback query function
// the array should be a [fld] = val setup for one record
foreach ($pri_val as $key=>
$val) {
// check for primary key value
* Clears the object values, leaves the columns structure
* @param bool $erase_all clears extra values from memory
final public function unload ($erase_all=
false) {
$e->setFunction (__METHOD__
);
// reset the columns array
* Removes the record from the database
* @precondition isLoaded ()
* @precondition !isReadOnly ()
* @throws Gumbo_Exception
final public function kill () {
// check for callback query function
$e->setFunction (__METHOD__
);
* Saves a modified record to the database
* @precondition isLoaded ()
* @precondition isModified ()
* @precondition !isReadOnly ()
* @throws Gumbo_Exception
final public function save () {
// check callback function
$sql =
"update {$this->getTable ()} set ";
// setting the specific values to update
$sql .=
"{
$key}='{
$val}',
";
$sql =
substr ($sql, 0, strlen ($sql) -
2); // remove ", " from end of SET statement
$e->setFunction (__METHOD__
);
* Creates a new record in the database
* @precondition !isLoaded ()
* @precondition isModified ()
* @precondition !isReadOnly
* @throws Gumbo_Exception
final public function create () {
// check for callback query function
$sql =
"insert into {$this->getTable ()} set ";
$sql .=
"{
$key}='{
$val}',
";
$e->setFunction (__METHOD__
);
* Resets the object by removing the values from the modified array
final public function reset () {
* Sets the primary key column value
* @throws Gumbo_Exception
$e->setFunction (__METHOD__
);
* Sets if the record was loaded
* @throws Gumbo_Exception
$e->setFunction (__METHOD__
);
* Sets if the record was modified
* @throws Gumbo_Exception
$e->setFunction (__METHOD__
);
* Sets the object as read only
* @throws Gumbo_Exception
$e->setFunction (__METHOD__
);
* Sets the value of a table column
* @precondition !isReadOnly ()
* @param string $column table column name
* @param mixed $val primitive type value
* @throws Gumbo_Exception
final protected function set ($column, $val=
null) {
// check if active column, otherwise throw into extra
$e->setFunction (__METHOD__
);
// Count Column ACTION METHODS
* Adds (increments) to the current count columns value
* @precondition _isColumnCount ($column)
* @param string $column count column name
* @param int $num increases by the supplied number
* @throws Gumbo_Exception
final public function add ($column, $num=
1) {
$e->setFunction (__METHOD__
);
$this->set ($column, $this->get ($column) +
$num);
$e->setFunction (__METHOD__
);
* Wrapper to add() method
* @param string $column count column name
* @param int $num increases by the supplied number
final public function increment ($column, $num=
1) {
$this->add ($column, $num);
* Subtracts (decrement) one from the count column value
* @precondition _isColumnCount ($column)
* @param string $column count column name
* @param int $num decreases by the supplied number
* @param bool $allow_negative allows negative values
* @throws Gumbo_Exception
final public function sub ($column, $num=
1, $allow_negative=
false) {
$e->setFunction (__METHOD__
);
throw
new Gumbo_Exception ("Invalid Argument 'allow_negative:bool' => {$allow_negative}:" .
gettype ($allow_negative));
$e->setFunction (__METHOD__
);
$diff =
$this->get ($column) -
$num;
if (!$allow_negative &&
$diff <
0) {
$this->set ($column, $diff);
$e->setFunction (__METHOD__
);
* Wrapper to sub() method
* @param string $column count column name
* @param int $num decreases by the supplied number
* @param bool $allow_negative allows negative values
final public function decrement ($column, $num=
1, $allow_negative=
false) {
$this->sub ($column, $num, $allow_negative);
* Returns the callback function/method
* @precondition getPrototype()
* @param string $for type of query "select|update|insert|delete"
* @throws Gumbo_Exception
return $proto->getCallback ($for);
$e->setFunction (__METHOD__
);
* @return Gumbo_Record_Prototype
* @precondition getPrototype()
return $proto->getTable ();
* Returns the primary key name
* @precondition getPrototype()
return $proto->getPrimaryKey ();
* Returns the XML parent element name
* @precondition getPrototype()
return $proto->getXMLParent ();
* Returns the primary key value
* Returns if the records was loaded
* Returns if the record was modified
* Returns if the record accesses the database
* Returns the value of a column
* @throws Gumbo_Exception
final public function get ($column) {
$e->setFunction (__METHOD__
);
* Returns the object as an XML formatted string
* @param string $parent parent element name
* @throws Gumbo_Exception
public function asXML ($parent=
null) {
if ($this->isMap ("xml") &&
$this->_getMap ("xml")->isKey ($key)) {
$key =
$this->_getMap ("xml")->getForeignKey ($key);
$xml .=
"<{$key}>{$val}</{$key}>";
$e->setFunction (__METHOD__
);
* Returns the entire Columns array
* Returns if the column exists
* @param string $column table column name
* @throws Gumbo_Exception
return isset
($this->_columns [$column]);
$e->setFunction (__METHOD__
);
// Modified table columns
* Returns all modified table columns array
* Returns if the supplied column was modified
* @param string $column modified key
* @throws Gumbo_Exception
$e->setFunction (__METHOD__
);
* Returns all the extra columns array
* Returns if the extra column key exists
* @param string $column extra column key
* @throws Gumbo_Exception
$e->setFunction (__METHOD__
);
* Returns all the count columns
* Returns if the supplied column was marked as a Count Column
* @throws Gumbo_Exception
$e->setFunction (__METHOD__
);
/** MAP INTERFACE METHODS **/
* @postcondition remove all non-alphanumeric characters (including underscores) from the key
* @param Gumbo_Interface_Map $map
* @param string $key reference string to the Map object (null is 'method')
* @param bool $replace replaces original if set
* @throws Gumbo_Exception
public function addMap (Gumbo_Interface_Map $map, $key=
null, $replace=
false) {
* Deletes a Map (not used)
* @throws Gumbo_Exception
* Clears all the Maps (not used)
* Maps information into the Mapper
* @precondition isMap("method")
* @precondition isMap(key)
* @param array $data where key=>"form field name", value=>user data
* @param string $key Map reference key (null is 'method')
* @throws Gumbo_Exception
public function map ($data, $key=
null) {
if (is_null ($key)) { $key =
"method"; }
if (!$this->isMap ("method")) {
if (!$this->isMap ($key)) {
foreach ($data as $ref=>
$val) {
if (!$map->isForeignKey ($ref)) { continue; }
$method =
"set" .
ucwords ($this->_getMap ("method")->getForeignKey ($this->_getMap ($key)->getKey ($ref)));
$e->setFunction (__METHOD__
);
* Gets the selected Map (not used)
* @param string $key (null is 'method')
* @return Gumbo_Interface_Map
public function getMap ($key=
null) {
* @param string $key (null is 'method')
* @return Gumbo_Interface_Map
* @throws Gumbo_Exception
private function _getMap ($key=
null) {
if (is_null ($key)) { $key =
"method"; }
if (!$this->isMap ($key)) {
return $this->_maps [$key];
$e->setFunction (__METHOD__
);
* Returns if the Map exists
* @param string $key reference key string (null is 'method')
* @throws Gumbo_Exception
public function isMap ($key=
null) {
if (is_null ($key)) { $key =
"method"; }
return isset
($this->_maps [$key]);
$e->setFunction (__METHOD__
);