Source for file Record.class.php

Documentation is available at Record.class.php

  1. <?php
  2. /**
  3.  * Gumbo Library Framework
  4.  *
  5.  * LICENSE
  6.  * This library is being released under the terms of the New BSD License.  A
  7.  * copy of the license is packaged with the software (LICENSE.txt).  If no
  8.  * copy is found, a copy of the license template can be found at:
  9.  * http://www.opensource.org/licenses/bsd-license.php
  10.  * 
  11.  * @category Gumbo
  12.  * @package Record
  13.  * @copyright Copyright (c) 2007, iBayou, Michael Luster
  14.  * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
  15.  * @author Michael Luster <mluster79@yahoo.com>
  16.  * @link http://sourceforge.net/projects/phpgumbo
  17.  * @version 0.0.1
  18.  */
  19.  
  20. /**
  21.  * Generic Record Class
  22.  * 
  23.  * The Record Class represents a single database record from a table.  The methods
  24.  * will provide access to the data of the record.  If the class contains a
  25.  * database record, the values will be read only.  In order to change records,
  26.  * the class must be extended.
  27.  * 
  28.  * Once the Record is loaded, the program will be able to access the internals of
  29.  * the class.  This can be accomplished by calling the get method with the column
  30.  * name or using the overloaded __get method.
  31.  * 
  32.  * In order to manipulate database records, the Gumbo_Record class must be extended.
  33.  * The class will represent a database table.  The child class must define the table
  34.  * structure to the Gumbo_Record parent.  It should also define get and set methods
  35.  * for each column.  A 'getColumnName' method will return the value.  A 'setColumnName'
  36.  * method will set the value, forcing the data to be validated and filtered as
  37.  * necessary.  The only way to modify Gumbo_Records is through a child class.
  38.  * 
  39.  * Implementation for a get method:
  40.  * <pre>
  41.  * public function get[Column] () {
  42.  *         return $this->get ("column_name");
  43.  * </pre>
  44.  * 
  45.  * Implementation for a set method:
  46.  * <pre>
  47.  * public function setColumn ($val) {
  48.  *         // perform validation/filtering, throw necessary Exceptions
  49.  *         // do not set value if validation fails
  50.  *         ...
  51.  *         $this->set ("column_name", $val);
  52.  * }
  53.  * </pre>
  54.  * 
  55.  * The Gumbo_Record class contains some very advanced features for accessing database
  56.  * information.  These features are not necessary, but are very useful.  The first
  57.  * step is to pass the value to the parent Constructor.
  58.  * <pre>
  59.  * public function __construct ($id=null) {
  60.  *         parent::__construct ($id);
  61.  *         ...
  62.  * }
  63.  * </pre>
  64.  * 
  65.  * The argument can be an integer or an array.  If an integer is passed, it will be
  66.  * used as the primary key column value to run a single SELECT statement against the
  67.  * database.  If an array is passed, it should be an associative array returned from
  68.  * a database query.  The keys in the array are the column names. (ie. mysql_fetch_assoc ()).
  69.  * This feature is useful when returning multiple rows of data, passing the associative
  70.  * array into the Gumbo_Record object.  If returning multiple records, this feature will
  71.  * avoid having to run a single query for each Gumbo_Record.
  72.  * 
  73.  * The child Record should instantiate a Prototype.  This will pass important setup information
  74.  * about the child Class and the Database Table it represents.  The Prototype will define
  75.  * the Table Name, the Primary Key, Table Columns, and more (see Gumbo_Abstract_Prototype).
  76.  * The best implementation of this feature is to create a single Prototype object
  77.  * during the programs lifetime.  This can be accomplished by defining the Prototype
  78.  * as a Singleton, or setting a static prototype property inside the child
  79.  * Record.  The child Record will then initialize the Prototype, passing the object to
  80.  * the parent 'init' method.  (The implementation is if a Prototype is not a Singleton.
  81.  * Make the appropriate substitution to retrieve a Prototype object for Singletons)
  82.  * <pre>
  83.  * // Singleton
  84.  * class User_Prototype extends Gumbo_Record_Prototype implements Gumbo_Interface_Singleton {
  85.  *     ...
  86.  * }
  87.  * 
  88.  * // static property in Record
  89.  * class User extends Gumbo_Record {
  90.  *     private static $_prototype;
  91.  *     
  92.  *     public function __construct ($id=null) {
  93.  *         ...
  94.  *         if (User::$_prototype == null) {
  95.  *             User::$_prototype = new User_Prototype ();
  96.  *         }
  97.  *         $this->init (User::$_prototype);
  98.  *         
  99.  *         $this->load (); // populates the Record
  100.  *     }
  101.  *     ...
  102.  * }
  103.  * </pre>
  104.  * 
  105.  * 
  106.  * The following features will help make the Record more dynamic.  This includes using
  107.  * maps to transmit data, or loading data through XML.
  108.  * 
  109.  * COUNT COLUMN (optional)
  110.  * This is a special feature, marking a registered column as a count column.  This is
  111.  * a column whose value is an integer, used for counting.  For example, if a table
  112.  * wants to track the number of times a record has been viewed 'view', the 'view'
  113.  * column could be registered as COUNT COLUMN and use the increment/decrement methods
  114.  * for easy changes.  Count Columns are defined by the Prototype.
  115.  * 
  116.  * CALLBACKS (optional)
  117.  * The Gumbo_Record class performs four types of queries: "SELECT,INSERT,UPDATE,DELETE".
  118.  * The four methods (_reload,create,save,kill) each have defined queries to perform the
  119.  * single operation.  In some cases, a more advanced or specific query is required.  This
  120.  * is where the CALLBACKS are useful.  The programmer can define more complex queries,
  121.  * placing them in a separate method.  The Prototype would define the callback functions
  122.  * for each operation.  If a callback is not defined, the Gumbo_Record class will use the
  123.  * pre-programmed defaults.  (see Gumbo_Record_Prototype for more details)
  124.  * 
  125.  * MAPS (optional)
  126.  * The Gumbo_Record class contains four predefined Maps.  This will associate the column names
  127.  * with particular elements.  Maps are defined by the Prototype.  In order to use a map,
  128.  * simple call the 'map' method, passing the appropriate data and which map to use.  This
  129.  * is best used with Form Input.
  130.  * 
  131.  * JOIN QUERIES
  132.  * There will be instances where accessing information from one table requires information from
  133.  * another table by means of a join.  This complex query should be placed inside a callback
  134.  * function.  The associative array returned will contain the columns from the table including
  135.  * keys referencing the joined table data.  The Gumbo_Record class takes the extra columns
  136.  * created by a join and places them in a special location called ColumnsExtra.  Extra JOIN
  137.  * data will be read only.  The child class should define 'get' methods to access the information.
  138.  * The parent get method just requires the column name and the value will be returned.
  139.  * 
  140.  * XML
  141.  * The child constructor should define the parent XML element name in the constructor.  This will
  142.  * be used to format an XML string or parse a supplied string.
  143.  * 
  144.  * The XML feature will accept an XML string containing the data to apply to the Gumbo_Record.  This
  145.  * will be applied to the column 'xml' Map.  The method will require the name of the Parent XML
  146.  * element name.  The parent element should contain an 'id' attribute with the value of the
  147.  * primary column key.
  148.  * 
  149.  * The next part of the feature will return the object as a formatted XML string.  This will use
  150.  * the 'xml' Map.  The formatted string will be surrounded by the parent XML value.
  151.  *
  152.  * @category Gumbo
  153.  * @package Record
  154.  * @copyright Copyright (c) 2007, iBayou, Michael Luster
  155.  * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
  156.  * @author Michael Luster <mluster79@yahoo.com>
  157.  * @link http://sourceforge.net/projects/phpgumbo
  158.  * @desc Generic Record Class
  159.  * @version 0.0.1
  160.  */
  161.  
  162. gumbo_load ("Interface_Record");
  163. gumbo_load ("Interface_Mapper");
  164.  
  165. class Gumbo_Record implements Gumbo_Interface_RecordGumbo_Interface_Mapper {
  166.     
  167.     /** @var Gumbo_Record_Prototype $_prototype child class Prototype */
  168.     private $_prototype;
  169.     
  170.     /** @var mixed $_primary_value primary key column value (usually an 'id' but could contain values) */
  171.     private $_primary_value;
  172.     
  173.     /** @var array $_columns table columns with values (if available) */
  174.     private $_columns = array ();
  175.     /** @var array $_columns_mod modified column values */
  176.     private $_columns_mod = array ();
  177.     /** @var array $_columns_extra additional columns not part of the table columns */
  178.     private $_columns_extra = array ();
  179.     /** @var array $_columns_count special columns that perform incremental/decremental counts */
  180.     private $_columns_count = array ();
  181.     
  182.     /** @var Gumbo_Interface_Map[] $_maps available Maps */
  183.     private $_maps = array ();
  184.     
  185.     /** @var bool $_loaded if the record was loaded */
  186.     private $_loaded = false;
  187.     /** @var bool $_modified if the object was modified */
  188.     private $_modified = false;
  189.     /** @var bool $_read_only tells if the object is read only */
  190.     private $_read_only = false;
  191.     
  192.     
  193.     
  194.     /**
  195.      * Constructor
  196.      * @param int|array$val 
  197.      */
  198.     public function __construct ($val=null{
  199.         $this->setPrimaryValue ($val);
  200.     }
  201.     
  202.     /**
  203.      * Serialized method
  204.      * @return array 
  205.      */
  206.     public function __sleep ({}
  207.     
  208.     /**
  209.      * Unserialized method
  210.      */
  211.     public function __wakeup ({}
  212.     
  213.     /**
  214.      * Sets the value of a table column using 'key' map
  215.      * @param string $key 
  216.      * @param mixed $val 
  217.      */
  218.     public function __set ($key$val{
  219.         if (!$this->isMap ("key")) return}
  220.         if (!$this->isMap ("method")) return}
  221.         
  222.         $map $this->_getMap ("key");
  223.         if (!$map->isForeignKey ($key)) return}
  224.         
  225.         $column $map->getKey ($key);
  226.         if (!$this->_getMap ("method")->isKey ($column)) return}
  227.         
  228.         $method "set" ucwords ($this->_getMap ("method")->getForeignKey ($column));
  229.         if (!method_exists ($this$method)) return}
  230.         
  231.         $this->$method ($val);
  232.     }
  233.     
  234.     /**
  235.      * Unsets the value of the supplied table column
  236.      * @param string $key 
  237.      */
  238.     public function __unset ($key{
  239.         if (!$this->isMap ("key")) return}
  240.         
  241.         $map $this->_getMap ("key");
  242.         if (!$map->isForeignKey ($key)) return}
  243.         
  244.         $this->set ($map->getKey ($key)null);
  245.     }
  246.     
  247.     /**
  248.      * Returns the raw value of the column name
  249.      * @param string $column 
  250.      * @return mixed 
  251.      */
  252.     public function __get ($column{
  253.         if ($this->isMap ("key"&& $this->isMap ("method")) {
  254.             $map $this->_getMap ("key");
  255.             if (!$map->isForeignKey ($key)) return}
  256.             
  257.             $column $map->getKey ($key);
  258.             if (!$this->_getMap ("method")->isKey ($column)) return}
  259.             
  260.             $method "get" ucfirst ($this->_getMap ("method")->getForeignKey ($column));
  261.             if (!method_exists ($this$method)) return}
  262.             
  263.             return $this->$method ($val);
  264.         }
  265.         
  266.         return $this->get ($column);
  267.     }
  268.     
  269.     /**
  270.      * Returns if the column is set
  271.      * @param string $key 
  272.      * @return bool 
  273.      */
  274.     public function __isset ($key{
  275.         if (!$this->isMap ("key")) return}
  276.         
  277.         $map $this->_getMap ("key");
  278.         if (!$map->isForeignKey ($key)) return}
  279.         
  280.         $column $map->getKey ($key);
  281.         if ($this->_isColumn ($column)) return true}
  282.         if ($this->_isColumnExtra ($column)) return true}
  283.         return false;
  284.     }
  285.     
  286.     
  287.     
  288.     /** ACTION METHODS **/
  289.     /**
  290.      * Initializes the Record by passing a Record_Prototype
  291.      * @param Gumbo_Record_Prototype $prototype 
  292.      * @throws Gumbo_Exception
  293.      */
  294.     final protected function init (Gumbo_Record_Prototype $prototype{
  295.         $this->_prototype = $prototype;
  296.         
  297.         // setup Columns
  298.         foreach ($prototype->getColumns as $key=>$val{
  299.             $this->_columns [$keynull;
  300.             
  301.             // add count column
  302.             if ($val{
  303.                 $this->_columns_count [$keytrue;
  304.             }
  305.         }
  306.         
  307.         // setup Maps
  308.         gumbo_load ("Map");
  309.         if ($prototype->isMap ("method")) {
  310.             $this->_maps ['method'new Gumbo_Map ();
  311.             foreach ($prototype->getMap ("method"as $key=>$val{
  312.                 $this->_maps ['method']->add ($key$val);
  313.             }
  314.         }
  315.         
  316.         if ($prototype->isMap ("key")) {
  317.             $this->_maps ['key'new Gumbo_Map ();
  318.             foreach ($prototype->getMap ("key"as $key=>$val{
  319.                 $this->_maps ['key']->add ($key$val);
  320.             }
  321.         }
  322.         
  323.         if ($prototype->isMap ("form")) {
  324.             $this->_maps ['form'new Gumbo_Map ();
  325.             foreach ($prototype->getMap ("form"as $key=>$val{
  326.                 $this->_maps ['form']->add ($key$val);
  327.             }
  328.         }
  329.         
  330.         if ($prototype->isMap ("xml")) {
  331.             $this->_maps ['xml'new Gumbo_Map ();
  332.             foreach ($prototype->getMap ("xml"as $key=>$val{
  333.                 $this->_maps ['xml']->add ($key$val);
  334.             }
  335.         }
  336.     }
  337.     
  338.     /**
  339.      * Loads the record into memory
  340.      */
  341.     final public function load ({
  342.         if (!$this->isLoaded ()) {
  343.             $this->_reload ();
  344.         }
  345.     }
  346.     
  347.     /**
  348.      * Loads an XML string, but only the first level of the parent
  349.      * 
  350.      * @param string $xml 
  351.      * @param string $parent name of top level XML element
  352.      * @throws Gumbo_Exception
  353.      */
  354.     public function loadXML ($xml$parent=null{
  355.         try {
  356.             // verify precondition
  357.             if (!is_string ($xml)) {
  358.                 throw new Gumbo_Exception ("Invalid Argument 'xml:str' => {$xml}:gettype ($xml));
  359.             }
  360.             if (!is_null ($parent&& !is_string ($parent)) {
  361.                 throw new Gumbo_Exception ("Invalid Argument 'parent:str' => {$parent}:gettype ($parent));
  362.             }
  363.             if (is_null ($parent&& !$this->getXMLParent ()) {
  364.                 throw new Gumbo_Exception ("XML Parent Undefined");
  365.             }
  366.             
  367.             if (is_null ($parent)) {
  368.                 $parent $this->getXMLParent ();
  369.             }
  370.             
  371.             if (!$this->isMap ("xml")) {
  372.                 throw new Gumbo_Exception ("Required Map Undefined: 'xml'");
  373.             }
  374.             if (!$this->isMap ("method")) {
  375.                 throw new Gumbo_Exception ("Required Map Undefined: 'method'");
  376.             }
  377.             
  378.             $map $this->_getMap ("xml");
  379.             $sxml new SimpleXMLElement ($xml);
  380.             
  381.             // set the primary value
  382.             if (isset ($xsml->$parent [$this->getPrimaryKey ()])) {
  383.                 $this->setPrimaryValue ((string) $xsml->$parent [$this->getPrimaryKey ()]);
  384.                 $this->set ($this->getPrimaryKey ()$this->getPrimaryValue ());
  385.             }
  386.             
  387.             foreach ($sxml->$parent->children (as $child{
  388.                 if (!$map->isForeignKey ($child->getName ())) continue}
  389.                 
  390.                 $column $map->getKey ($child->getName ());
  391.                 if (!$this->_getMap ("method")->isKey ($column)) continue}
  392.                 
  393.                 $method "set" ucwords ($this->_getMap ("method")->getForeignKey ($column));
  394.                 if (!method_exists ($this$method)) continue}
  395.                 
  396.                 $name $child->getName ();
  397.                 $this->$method ((string) $sxml->$parent->$name);
  398.             }
  399.             
  400.             $this->_setLoaded (true);
  401.             $this->_setModified (false);
  402.             $this->_columns_mod = array ();
  403.         catch (Exception $e{
  404.             if ($e instanceof Gumbo_Exception{
  405.                 $e->setFunction (__METHOD__);
  406.             }
  407.             gumbo_trigger ($e);
  408.         }
  409.     }
  410.     
  411.     /**
  412.      * Loads new Record or refreshes the saved/created Item values
  413.      * @uses Gumbo_Query
  414.      */
  415.     final private function _reload ({
  416.         // reset the values of the object
  417.         if ($this->isLoaded ()) {
  418.             $this->unload ();
  419.         }
  420.         
  421.         $pri_val $this->getPrimaryValue ();
  422.         
  423.         // load the specific object into memory
  424.         if ((is_int ($pri_val|| is_string ($pri_val))) {
  425.             // check for callback query function
  426.             if ($this->_getCallback ("select")) {
  427.                 $pri_val call_user_func ($this->_getCallback ("select")$pri_val);
  428.             else {
  429.                 if ($this->getTable (&& $this->getPrimaryKey ()) {
  430.                     gumbo_load ("Query");
  431.                     $q "select * from {$this->getTable ()} where {$this->getPrimaryKey ()}='{$pri_val}'";
  432.                     $tmp Gumbo_Query::instance ()->execute ($q);
  433.                     if (isset ($tmp [0])) {
  434.                         $pri_val $tmp [0];
  435.                     }
  436.                 }
  437.             }
  438.         }
  439.         
  440.         // the array should be a [fld] = val setup for one record
  441.         if (is_array ($pri_val)) {
  442.             foreach ($pri_val as $key=>$val{
  443.                 $this->set ($key$val);
  444.                 
  445.                 // check for primary key value
  446.                 if ($this->getPrimaryKey (&& $this->getPrimaryKey (=== $key{
  447.                     $this->setPrimaryValue ($val);
  448.                 }
  449.             }
  450.             
  451.             $this->_setLoaded (true);
  452.             $this->_setModified (false);
  453.             $this->_columns_mod = array ();
  454.         }
  455.     }
  456.     
  457.     /**
  458.      * Clears the object values, leaves the columns structure
  459.      * @param bool $erase_all clears extra values from memory
  460.      */
  461.     final public function unload ($erase_all=false{
  462.         try {
  463.             // verify precondition
  464.             if (!is_bool ($erase_all)) {
  465.                 throw new Gumbo_Exception ("Invalid Argument 'erase_all:bool' => {$erase_all}:gettype ($erase_all));
  466.             }
  467.         catch (Gumbo_Exception $e{
  468.             $e->setFunction (__METHOD__);
  469.             gumbo_trigger ($e);
  470.             $erase_all false;
  471.         }
  472.         
  473.         if ($this->isLoaded ()) {
  474.             // reset the columns array
  475.             foreach (array_keys ($this->getAllColumns ()) as $key{
  476.                 $this->set ($key);
  477.             }
  478.             $this->_columns_mod = array ();
  479.             
  480.             // remove extra data
  481.             if ($erase_all{
  482.                 $this->_columns_extra = array ();
  483.             }
  484.             
  485.             $this->_setLoaded (false);
  486.             $this->_setModified (false);
  487.         }
  488.     }
  489.     
  490.     /**
  491.      * Removes the record from the database
  492.      * @precondition isLoaded ()
  493.      * @precondition !isReadOnly ()
  494.      * @throws Gumbo_Exception
  495.      * @uses Gumbo_Query
  496.      */
  497.     final public function kill ({
  498.         try {
  499.             // verify precondition
  500.             if (!$this->isLoaded ()) {
  501.                 throw new Gumbo_Exception ("Record Not Loaded");
  502.             }
  503.             if ($this->isReadOnly ()) {
  504.                 throw new Gumbo_Exception ("Record Read Only");
  505.             }
  506.             
  507.             // check for callback query function
  508.             if ($this->_getCallback ("delete")) {
  509.                 call_user_func ($this->_getCallback ("delete")$this->getPrimaryValue ());
  510.             else {
  511.                 if ($this->getTable (&& $this->getPrimaryKey ()) {
  512.                     gumbo_load ("Query");
  513.                     $sql "delete from {$this->getTable ()} where {$this->getPrimaryKey ()}='{$this->getPrimaryValue ()}'";
  514.                     Gumbo_Query::instance ()->execute ($sql);
  515.                 }
  516.             }
  517.             
  518.             $this->unload (true);
  519.         catch (Gumbo_Exception $e{
  520.             $e->setFunction (__METHOD__);
  521.             gumbo_trigger ($e);
  522.         }
  523.     }
  524.     
  525.     /**
  526.      * Saves a modified record to the database
  527.      * @precondition isLoaded ()
  528.      * @precondition isModified ()
  529.      * @precondition !isReadOnly ()
  530.      * @throws Gumbo_Exception
  531.      * @uses Gumbo_Query
  532.      */
  533.     final public function save ({
  534.         try {
  535.             // verify precondition
  536.             if ($this->isReadOnly ()) {
  537.                 throw new Gumbo_Exception ("Record Read Only");
  538.             }
  539.             if (!$this->isLoaded ()) {
  540.                 $this->create ();
  541.                 return;
  542.             }
  543.             if (!$this->isModified ()) {
  544.                 throw new Gumbo_Exception ("Record Not Modified");
  545.             }
  546.             
  547.             // check callback function
  548.             if ($this->_getCallback ("update")) {
  549.                 call_user_func ($this->_getCallback ("update")$this->getPrimaryValue ()$this->getAllColumnsMod ());
  550.             else {
  551.                 if ($this->getTable (&& $this->getPrimaryKey ()) {
  552.                     gumbo_load ("Query");
  553.                     // prepare query
  554.                     $sql "update {$this->getTable ()} set ";
  555.                     
  556.                     // setting the specific values to update
  557.                     foreach ($this->getAllColumnsMod (as $key=>$val{
  558.                         $sql .= "{$key}='{$val}', ";
  559.                     }
  560.                     $sql substr ($sql0strlen ($sql2)// remove ", " from end of SET statement
  561.                     $sql .= " where {$this->getPrimaryKey ()}='{$this->getPrimaryValue ()}limit 1";
  562.                     
  563.                     Gumbo_Query::instance ()->execute ($sql);
  564.                 }
  565.             }
  566.             
  567.             $this->_setLoaded (false);
  568.             $this->reset ();
  569.             
  570.             $this->load ();
  571.         catch (Gumbo_Exception $e{
  572.             $e->setFunction (__METHOD__);
  573.             gumbo_trigger ($e);
  574.         }
  575.     }
  576.     
  577.     /**
  578.      * Creates a new record in the database
  579.      * @precondition !isLoaded ()
  580.      * @precondition isModified ()
  581.      * @precondition !isReadOnly
  582.      * @throws Gumbo_Exception
  583.      * @uses Gumbo_Query
  584.      */
  585.     final public function create ({
  586.         try {
  587.             // verify precondition
  588.             if ($this->isReadOnly ()) {
  589.                 throw new Gumbo_Exception ("Record Read Only");
  590.             }
  591.             if ($this->isLoaded ()) {
  592.                 throw new Gumbo_Exception ("Record Loaded");
  593.             }
  594.             if (!$this->isModified ()) {
  595.                 throw new Gumbo_Exception ("Record Not Modified");
  596.             }
  597.             
  598.             // check for callback query function
  599.             if ($this->_getCallback ("insert")) {
  600.                 $this->setPrimaryValue (call_user_func ($this->_getCallback ("insert")$this->getAllColumnsMod ()));
  601.             else {
  602.                 if ($this->getTable (&& $this->getPrimaryKey ()) {
  603.                     gumbo_load ("Query");
  604.                     $sql "insert into {$this->getTable ()} set ";
  605.                     foreach ($this->getAllColumnsMod (as $key=>$val{
  606.                         $sql .= "{$key}='{$val}', ";
  607.                     }
  608.                     $sql substr ($sql0strlen ($sql2);
  609.                     
  610.                     $this->setPrimaryValue (Gumbo_Query::instance ()->execute ($sql));
  611.                 }
  612.             }
  613.             
  614.             // execute query
  615.             $this->_setLoaded (false);
  616.             $this->reset ();
  617.             
  618.             $this->load ();
  619.         catch (Gumbo_Exception $e{
  620.             $e->setFunction (__METHOD__);
  621.             gumbo_trigger ($e);
  622.         }
  623.     }
  624.     
  625.     /**
  626.      * Resets the object by removing the values from the modified array
  627.      */
  628.     final public function reset ({
  629.         $this->_columns_mod = array ();
  630.         $this->_setModified (false);
  631.     }
  632.     
  633.     
  634.     
  635.     /** MUTATOR METHODS **/
  636.     /**
  637.      * Sets the primary key column value
  638.      * @param mixed $val 
  639.      * @throws Gumbo_Exception
  640.      */
  641.     final public function setPrimaryValue ($val{
  642.         try {
  643.             // verify precondition
  644.             if (!is_string ($val&& !is_numeric ($val&& !is_array ($val)) {
  645.                 throw new Gumbo_Exception ("Invalid Argument 'val:str|num|arr' => {$val}:gettype ($val));
  646.             }
  647.             
  648.             $this->_primary_value = $val;
  649.         catch (Gumbo_Exception $e{
  650.             $e->setFunction (__METHOD__);
  651.             gumbo_trigger ($e);
  652.         }
  653.     }
  654.     
  655.     /**
  656.      * Sets if the record was loaded
  657.      * @param bool $val 
  658.      * @throws Gumbo_Exception
  659.      */
  660.     final private function _setLoaded ($val{
  661.         try {
  662.             // verify precondition
  663.             if (!is_bool ($val)) {
  664.                 throw new Gumbo_Exception ("Invalid Argument 'val:bool' => {$val}:gettype ($val));
  665.             }
  666.             
  667.             $this->_loaded = $val;
  668.         catch (Gumbo_Exception $e{
  669.             $e->setFunction (__METHOD__);
  670.             gumbo_trigger ($e);
  671.         }
  672.     }
  673.     
  674.     /**
  675.      * Sets if the record was modified
  676.      * @param bool $val 
  677.      * @throws Gumbo_Exception
  678.      */
  679.     final private function _setModified ($val{
  680.         try {
  681.             // verify precondition
  682.             if (!is_bool ($val)) {
  683.                 throw new Gumbo_Exception ("Invalid Argument 'val:bool' => {$val}:gettype ($val));
  684.             }
  685.             
  686.             $this->_modified = $val;
  687.         catch (Gumbo_Exception $e{
  688.             $e->setFunction (__METHOD__);
  689.             gumbo_trigger ($e);
  690.         }
  691.     }
  692.     
  693.     /**
  694.      * Sets the object as read only
  695.      * @param bool $val 
  696.      * @throws Gumbo_Exception
  697.      */
  698.     final public function setReadOnly ($val{
  699.         try {
  700.             // verify precondition
  701.             if (!is_bool ($val)) {
  702.                 throw new Gumbo_Exception ("Invalid Argument 'val:bool' => {$val}:gettype ($val));
  703.             }
  704.             
  705.             $this->_read_only = $val;
  706.         catch (Gumbo_Exception $e{
  707.             $e->setFunction (__METHOD__);
  708.             gumbo_trigger ($e);
  709.         }
  710.     }
  711.     
  712.     
  713.     
  714.     // Table Columns
  715.     /**
  716.      * Sets the value of a table column
  717.      * @precondition !isReadOnly ()
  718.      * @param string $column table column name
  719.      * @param mixed $val primitive type value
  720.      * @throws Gumbo_Exception
  721.      */
  722.     final protected function set ($column$val=null{
  723.         try {
  724.             // verify precondition
  725.             if ($this->isReadOnly ()) {
  726.                 throw new Gumbo_Exception ("Record Read Only");
  727.             }
  728.             if (!is_string ($column)) {
  729.                 throw new Gumbo_Exception ("Invalid Argument 'column:str' => {$column}:gettype ($column));
  730.             }
  731.             if (!is_string ($val&& !is_numeric ($val&& !is_bool ($val&& !is_null ($val)) {
  732.                 throw new Gumbo_Exception ("Invalid Argument 'val:str|num|bool|null' => {$val}:gettype ($val));
  733.             }
  734.             
  735.             // check if active column, otherwise throw into extra
  736.             if ($this->_isColumn ($column)) {
  737.                 $this->_columns_mod [$column$val;
  738.             else {
  739.                 $this->_columns_extra [$column$val;
  740.             }
  741.         catch (Gumbo_Exception $e{
  742.             $e->setFunction (__METHOD__);
  743.             gumbo_trigger ($e);
  744.         }
  745.     }
  746.     
  747.     
  748.     
  749.     // Count Column ACTION METHODS
  750.     /**
  751.      * Adds (increments) to the current count columns value
  752.      * @precondition _isColumnCount ($column)
  753.      * @param string $column count column name
  754.      * @param int $num increases by the supplied number
  755.      * @throws Gumbo_Exception
  756.      */
  757.     final public function add ($column$num=1{
  758.         try {
  759.             // verify precondition
  760.             if (!$this->_isColumnCount ($column)) {
  761.                 throw new Gumbo_Exception ("Column Not Count Column: {$column}");
  762.             }
  763.             
  764.             try {
  765.                 if (!is_numeric ($num)) {
  766.                     throw new Gumbo_Exception ("Invalid Argument 'num:int' => {$num}:gettype ($num));
  767.                 }
  768.             catch (Gumbo_Exception $e{
  769.                 $e->setFunction (__METHOD__);
  770.                 gumbo_trigger ($e);
  771.                 $num 1;
  772.             }
  773.             $num = (int) $num;
  774.             if ($num <= 0{
  775.                 throw new Gumbo_Exception ("Out Of Range 'num > 0' => {$num}");
  776.             }
  777.             
  778.             $this->set ($column$this->get ($column$num);
  779.         catch (Gumbo_Exception $e{
  780.             $e->setFunction (__METHOD__);
  781.             gumbo_trigger ($e);
  782.         }
  783.     }
  784.     
  785.     /**
  786.      * Wrapper to add() method
  787.      * @param string $column count column name
  788.      * @param int $num increases by the supplied number
  789.      */
  790.     final public function increment ($column$num=1{
  791.         $this->add ($column$num);
  792.     }
  793.     
  794.     /**
  795.      * Subtracts (decrement) one from the count column value
  796.      * @precondition _isColumnCount ($column)
  797.      * @param string $column count column name
  798.      * @param int $num decreases by the supplied number
  799.      * @param bool $allow_negative allows negative values
  800.      * @throws Gumbo_Exception
  801.      */
  802.     final public function sub ($column$num=1$allow_negative=false{
  803.         try {
  804.             // verify precondition
  805.             if (!$this->_isColumnCount ($column)) {
  806.                 throw new Gumbo_Exception ("Column Not Count Column: {$column}");
  807.             }
  808.             
  809.             try {
  810.                 if (!is_numeric ($num)) {
  811.                     throw new Gumbo_Exception ("Invalid Argument 'num:int' => {$num}:gettype ($num));
  812.                 }
  813.             catch (Gumbo_Exception $e{
  814.                 $e->setFunction (__METHOD__);
  815.                 gumbo_trigger ($e);
  816.                 $num 1;
  817.             }
  818.             if ($num <= 0{
  819.                 throw new Gumbo_Exception ("Out Of Range 'num > 0' => {$num}");
  820.             }
  821.             
  822.             try {
  823.                 if (!is_bool ($allow_negative)) {
  824.                     throw new Gumbo_Exception ("Invalid Argument 'allow_negative:bool' => {$allow_negative}:gettype ($allow_negative));
  825.                 }
  826.             catch (Gumbo_Exception $e{
  827.                 $e->setFunction (__METHOD__);
  828.                 gumbo_trigger ($e);
  829.                 $allow_negative false;
  830.             }
  831.             
  832.             $diff $this->get ($column$num;
  833.             if (!$allow_negative && $diff 0{
  834.                 $diff 0;
  835.             }
  836.             
  837.             $this->set ($column$diff);
  838.         catch (Gumbo_Exception $e{
  839.             $e->setFunction (__METHOD__);
  840.             gumbo_trigger ($e);
  841.         }
  842.     }
  843.     
  844.     /**
  845.      * Wrapper to sub() method
  846.      * @param string $column count column name
  847.      * @param int $num decreases by the supplied number
  848.      * @param bool $allow_negative allows negative values
  849.      */
  850.     final public function decrement ($column$num=1$allow_negative=false{
  851.         $this->sub ($column$num$allow_negative);
  852.     }
  853.     
  854.     
  855.     
  856.     
  857.     
  858.     
  859.     
  860.     /** ACCESSOR METHODS **/
  861.     /**
  862.      * Returns the callback function/method
  863.      * @precondition getPrototype()
  864.      * @param string $for type of query "select|update|insert|delete"
  865.      * @return string|array
  866.      * @throws Gumbo_Exception
  867.      */
  868.     final private function _getCallback ($for{
  869.         try {
  870.             if (!is_string ($for)) {
  871.                 throw new Gumbo_Exception ("Invalid Argument 'for:str' => {$for}:gettype ($for));
  872.             }
  873.             
  874.             $for strtolower ($for);
  875.             switch ($for{
  876.                 case "select" break;
  877.                 case "update" break;
  878.                 case "delete" break;
  879.                 case "insert" break;
  880.                 default : throw new Gumbo_Exception ("Callback Undefined: {$for}");
  881.             }
  882.             
  883.             if ($proto $this->getPrototype ()) {
  884.                 return $proto->getCallback ($for);
  885.             }
  886.         catch (Gumbo_Exception $e{
  887.             $e->setFunction (__METHOD__);
  888.             gumbo_trigger ($e);
  889.         }
  890.         return null;
  891.     }
  892.     
  893.     /**
  894.      * Returns the Prototype
  895.      * @return Gumbo_Record_Prototype 
  896.      */
  897.     final protected function getPrototype ({
  898.         return $this->_prototype;
  899.     }
  900.     
  901.     /**
  902.      * Returns the table name
  903.      * @precondition getPrototype()
  904.      * @return string 
  905.      */
  906.     final public function getTable ({
  907.         if ($proto $this->getPrototype ()) {
  908.             return $proto->getTable ();
  909.         }
  910.         return null;
  911.     }
  912.     
  913.     /**
  914.      * Returns the primary key name
  915.      * @precondition getPrototype()
  916.      * @return string 
  917.      */
  918.     final public function getPrimaryKey ({
  919.         if ($proto $this->getPrototype ()) {
  920.             return $proto->getPrimaryKey ();
  921.         }
  922.         return null;
  923.     }
  924.     
  925.     /**
  926.      * Returns the XML parent element name
  927.      * @precondition getPrototype()
  928.      * @return string 
  929.      */
  930.     final public function getXMLParent ({
  931.         if ($proto $this->getPrototype ()) {
  932.             return $proto->getXMLParent ();
  933.         }
  934.         return null;
  935.     }
  936.     
  937.     /**
  938.      * Returns the primary key value
  939.      * @return mixed 
  940.      */
  941.     final public function getPrimaryValue ({
  942.         return $this->_primary_value;
  943.     }
  944.     
  945.     /**
  946.      * Returns if the records was loaded
  947.      * @return bool 
  948.      */
  949.     final public function isLoaded ({
  950.         return $this->_loaded;
  951.     }
  952.     
  953.     /**
  954.      * Returns if the record was modified
  955.      * @return bool 
  956.      */
  957.     final public function isModified ({
  958.         return $this->_modified;
  959.     }
  960.     
  961.     /**
  962.      * Returns if the record accesses the database
  963.      * @return bool 
  964.      */
  965.     final public function isReadOnly ({
  966.         return $this->_read_only;
  967.     }
  968.     
  969.     
  970.     
  971.     // Table Columns
  972.     /**
  973.      * Returns the value of a column
  974.      * @param string $column 
  975.      * @return mixed 
  976.      * @throws Gumbo_Exception
  977.      */
  978.     final public function get ($column{
  979.         try {
  980.             // verify precondition
  981.             if (!is_string ($column)) {
  982.                 throw new Gumbo_Exception ("Invalid Argument 'column:str' => {$column}:gettype ($column));
  983.             }
  984.             
  985.             if ($this->_isColumn ($column)) {
  986.                 return $this->_columns [$column];
  987.             elseif ($this->_isColumnExtra ($column)) {
  988.                 return $this->_columns_extra [$column];
  989.             }
  990.             
  991.             throw new Gumbo_Exception ("Column Does Not Exist: {$column}");
  992.         catch (Gumbo_Exception $e{
  993.             $e->setFunction (__METHOD__);
  994.             gumbo_trigger ($e);
  995.         }
  996.         return null;
  997.     }
  998.     
  999.     /**
  1000.      * Returns the object as an XML formatted string
  1001.      * @param string $parent parent element name
  1002.      * @return string 
  1003.      * @throws Gumbo_Exception
  1004.      */
  1005.     public function asXML ($parent=null{
  1006.         try {
  1007.             if (!is_null ($parent&& !is_string ($parent)) {
  1008.                 throw new Gumbo_Exception ("Invalid Argument 'parent:str' => {$parent}:gettype ($parent));
  1009.             }
  1010.             if (is_null ($parent&& !$this->getXMLParent ()) {
  1011.                 throw new Gumbo_Exception ("XML Parent Undefined");
  1012.             }
  1013.             
  1014.             if (is_null ($parent)) {
  1015.                 $parent $this->getXMLParent ();
  1016.             }
  1017.             
  1018.             $xml "<{$parent}";
  1019.             if ($this->getPrimaryKey ()) {
  1020.                 $xml .= " {$this->getPrimaryKey ()}=\"{$this->get ($this->getPrimaryKey ())}\"";
  1021.             }
  1022.             $xml .= ">";
  1023.             
  1024.             foreach ($this->getAllColumns (as $key=>$val{
  1025.                 if ($this->isMap ("xml"&& $this->_getMap ("xml")->isKey ($key)) {
  1026.                     $key $this->_getMap ("xml")->getForeignKey ($key);
  1027.                 }
  1028.                 $xml .= "<{$key}>{$val}</{$key}>";
  1029.             }
  1030.             
  1031.             $xml .= "</{$parent}>";
  1032.         catch (Gumbo_Exception $e{
  1033.             $e->setFunction (__METHOD__);
  1034.             gumbo_trigger ($e);
  1035.         }
  1036.     }
  1037.     
  1038.     
  1039.     
  1040.     /**
  1041.      * Returns the entire Columns array
  1042.      * @return array 
  1043.      */
  1044.     final public function getAllColumns ({
  1045.         return $this->_columns;
  1046.     }
  1047.     
  1048.     /**
  1049.      * Returns if the column exists
  1050.      * @param string $column table column name
  1051.      * @return bool 
  1052.      * @throws Gumbo_Exception
  1053.      */
  1054.     final private function _isColumn ($column{
  1055.         try {
  1056.             // verify precondition
  1057.             if (!is_string ($column)) {
  1058.                 throw new Gumbo_Exception ("Invalid Argument 'column:str' => {$column}:gettype ($column));
  1059.             }
  1060.             
  1061.             return isset ($this->_columns [$column]);
  1062.         catch (Gumbo_Exception $e{
  1063.             $e->setFunction (__METHOD__);
  1064.             gumbo_trigger ($e);
  1065.         }
  1066.         return false;
  1067.     }
  1068.     
  1069.     
  1070.     
  1071.     // Modified table columns
  1072.     /**
  1073.      * Returns all modified table columns array
  1074.      * @return array 
  1075.      */
  1076.     final public function getAllColumnsMod ({
  1077.         return $this->_columns_mod;
  1078.     }
  1079.     
  1080.     /**
  1081.      * Returns if the supplied column was modified
  1082.      * @param string $column modified key
  1083.      * @return bool 
  1084.      * @throws Gumbo_Exception
  1085.      */
  1086.     final private function _isColumnMod ($column{
  1087.         try {
  1088.             // verify precondition
  1089.             if (!is_string ($column)) {
  1090.                 throw new Gumbo_Exception ("Invalid Argument 'column:str' => {$column}:gettype ($column));
  1091.             }
  1092.             
  1093.             return isset ($this->_columns_mod [$column]);
  1094.         catch (Gumbo_Exception $e{
  1095.             $e->setFunction (__METHOD__);
  1096.             gumbo_trigger ($e);
  1097.         }
  1098.         return false;
  1099.     }
  1100.     
  1101.     
  1102.     
  1103.     // Extra columns
  1104.     /**
  1105.      * Returns all the extra columns array
  1106.      * @return array 
  1107.      */
  1108.     final public function getAllColumnsExtra ({
  1109.         return $this->_columns_extra;
  1110.     }
  1111.     
  1112.     /**
  1113.      * Returns if the extra column key exists
  1114.      * @param string $column extra column key
  1115.      * @return bool 
  1116.      * @throws Gumbo_Exception
  1117.      */
  1118.     final private function _isColumnExtra ($column{
  1119.         try {
  1120.             // verify precondition
  1121.             if (!is_string ($column)) {
  1122.                 throw new Gumbo_Exception ("Invalid Argument 'column:str' => {$column}:gettype ($column));
  1123.             }
  1124.             
  1125.             return isset ($this->_columns_extra [$column]);
  1126.         catch (Gumbo_Exception $e{
  1127.             $e->setFunction (__METHOD__);
  1128.             gumbo_trigger ($e);
  1129.         }
  1130.         return false;
  1131.     }
  1132.     
  1133.     
  1134.     
  1135.     // Count Columns
  1136.     /**
  1137.      * Returns all the count columns
  1138.      * @return array 
  1139.      */
  1140.     final public function getAllColumnsCount ({
  1141.         return array_keys ($this->_columns_count);
  1142.     }
  1143.     
  1144.     /**
  1145.      * Returns if the supplied column was marked as a Count Column
  1146.      * @param string $column 
  1147.      * @return bool 
  1148.      * @throws Gumbo_Exception
  1149.      */
  1150.     final private function _isColumnCount ($column{
  1151.         try {
  1152.             // verify precondition
  1153.             if (!is_string ($column)) {
  1154.                 throw new Gumbo_Exception ("Invalid Argument 'column:str' => {$column}:gettype ($column));
  1155.             }
  1156.             
  1157.             return isset ($this->_columns_count [$column]);
  1158.         catch (Gumbo_Exception $e{
  1159.             $e->setFunction (__METHOD__);
  1160.             gumbo_trigger ($e);
  1161.         }
  1162.         return false;
  1163.     }
  1164.     
  1165.     
  1166.     
  1167.     
  1168.     
  1169.     
  1170.     /** MAP INTERFACE METHODS **/
  1171.     /**
  1172.      * Adds a Map (not used)
  1173.      * @postcondition remove all non-alphanumeric characters (including underscores) from the key
  1174.      * @param Gumbo_Interface_Map $map 
  1175.      * @param string $key reference string to the Map object (null is 'method')
  1176.      * @param bool $replace replaces original if set
  1177.      * @throws Gumbo_Exception
  1178.      */
  1179.     public function addMap (Gumbo_Interface_Map $map$key=null$replace=false{
  1180.         return;
  1181.     }
  1182.     
  1183.     /**
  1184.      * Deletes a Map (not used)
  1185.      * @param string $key 
  1186.      * @throws Gumbo_Exception
  1187.      */
  1188.     public function removeMap ($key=null{
  1189.         return;
  1190.     }
  1191.     
  1192.     /**
  1193.      * Clears all the Maps (not used)
  1194.      * @postcondition no maps
  1195.      */
  1196.     public function resetMaps ({
  1197.         return;
  1198.     }
  1199.     
  1200.     /**
  1201.      * Maps information into the Mapper
  1202.      * @precondition isMap("method")
  1203.      * @precondition isMap(key)
  1204.      * @param array $data where key=>"form field name", value=>user data
  1205.      * @param string $key Map reference key (null is 'method')
  1206.      * @throws Gumbo_Exception
  1207.      */
  1208.     public function map ($data$key=null{
  1209.         try {
  1210.             // verify precondition
  1211.             if (!is_array ($data)) {
  1212.                 throw new Gumbo_Exception ("Invalid Argument 'data:arr' => {$data}:gettype ($data));
  1213.             }
  1214.             if (is_null ($key)) $key "method"}
  1215.             if (!is_string ($key)) {
  1216.                 throw new Gumbo_Exception ("Invalid Argument 'key:str|null' => {$key}:gettype ($key));
  1217.             }
  1218.             if (!$this->isMap ("method")) {
  1219.                 throw new Gumbo_Exception ("Required Map Undefined: 'method'");
  1220.             }
  1221.             if (!$this->isMap ($key)) {
  1222.                 throw new Gumbo_Exception ("Map Does Not Exist: {$key}");
  1223.             }
  1224.             
  1225.             foreach ($data as $ref=>$val{
  1226.                 if (!$map->isForeignKey ($ref)) continue}
  1227.                 
  1228.                 $method "set" ucwords ($this->_getMap ("method")->getForeignKey ($this->_getMap ($key)->getKey ($ref)));
  1229.                 if (!method_exists ($this$method)) continue}
  1230.                 
  1231.                 $this->$method ($val);
  1232.             }
  1233.         catch (Gumbo_Exception $e{
  1234.             $e->setFunction (__METHOD__);
  1235.             gumbo_trigger ($e);
  1236.         }
  1237.     }
  1238.     
  1239.     /**
  1240.      * Gets the selected Map (not used)
  1241.      * @param string $key (null is 'method')
  1242.      * @return Gumbo_Interface_Map 
  1243.      */
  1244.     public function getMap ($key=null{
  1245.         return;
  1246.     }
  1247.     
  1248.     /**
  1249.      * Gets the selected Map
  1250.      * @param string $key (null is 'method')
  1251.      * @return Gumbo_Interface_Map 
  1252.      * @throws Gumbo_Exception
  1253.      */
  1254.     private function _getMap ($key=null{
  1255.         try {
  1256.             // verify precondition
  1257.             if (is_null ($key)) $key "method"}
  1258.             if (!is_string ($key)) {
  1259.                 throw new Gumbo_Exception ("Invalid Argument 'key:str' => {$key}:gettype ($key));
  1260.             }
  1261.             if (!$this->isMap ($key)) {
  1262.                 throw new Gumbo_Exception ("Map Does Not Exist: {$key}");
  1263.             }
  1264.             
  1265.             return $this->_maps [$key];
  1266.         catch (Gumbo_Exception $e{
  1267.             $e->setFunction (__METHOD__);
  1268.             gumbo_trigger ($e);
  1269.         }
  1270.         return null;
  1271.     }
  1272.     
  1273.     /**
  1274.      * Returns if the Map exists
  1275.      * @param string $key reference key string (null is 'method')
  1276.      * @return bool 
  1277.      * @throws Gumbo_Exception
  1278.      */
  1279.     public function isMap ($key=null{
  1280.         try {
  1281.             // verify precondition
  1282.             if (is_null ($key)) $key "method"}
  1283.             if (!is_string ($key)) {
  1284.                 throw new Gumbo_Exception ("Invalid Argument 'key:str' => {$key}:gettype ($key));
  1285.             }
  1286.             
  1287.             return isset ($this->_maps [$key]);
  1288.         catch (Gumbo_Exception $e{
  1289.             $e->setFunction (__METHOD__);
  1290.             gumbo_trigger ($e);
  1291.         }
  1292.         return false;
  1293.     }
  1294.     
  1295. }
  1296.  
  1297. ?>