Alpha Framework alpha--model
[ class tree: alpha--model ] [ index: alpha--model ] [ all elements ]

Source for file AlphaDAO.inc

Documentation is available at AlphaDAO.inc

  1. <?php
  2.  
  3. require_once $config->get('sysRoot').'alpha/util/AlphaErrorHandlers.inc';
  4. require_once $config->get('sysRoot').'alpha/util/Logger.inc';
  5. require_once $config->get('sysRoot').'alpha/util/InputFilter.inc';
  6. require_once $config->get('sysRoot').'alpha/exceptions/BONotFoundException.inc';
  7. require_once $config->get('sysRoot').'alpha/exceptions/FailedSaveException.inc';
  8. require_once $config->get('sysRoot').'alpha/exceptions/FailedDeleteException.inc';
  9. require_once $config->get('sysRoot').'alpha/exceptions/LockingException.inc';
  10. require_once $config->get('sysRoot').'alpha/exceptions/ValidationException.inc';
  11. require_once $config->get('sysRoot').'alpha/exceptions/IllegalArguementException.inc';
  12. require_once $config->get('sysRoot').'alpha/exceptions/MailNotSentException.inc';
  13. require_once $config->get('sysRoot').'alpha/exceptions/BadBOTableNameException.inc';
  14. require_once $config->get('sysRoot').'alpha/exceptions/FailedIndexCreateException.inc';
  15. require_once $config->get('sysRoot').'alpha/exceptions/FailedLookupCreateException.inc';
  16. require_once $config->get('sysRoot').'alpha/model/types/Date.inc';
  17. require_once $config->get('sysRoot').'alpha/model/types/Timestamp.inc';
  18. require_once $config->get('sysRoot').'alpha/model/types/Double.inc';
  19. require_once $config->get('sysRoot').'alpha/model/types/Integer.inc';
  20. require_once $config->get('sysRoot').'alpha/model/types/String.inc';
  21. require_once $config->get('sysRoot').'alpha/model/types/Text.inc';
  22. require_once $config->get('sysRoot').'alpha/model/types/Enum.inc';
  23. require_once $config->get('sysRoot').'alpha/model/types/DEnum.inc';
  24. require_once $config->get('sysRoot').'alpha/model/types/DEnumItem.inc';
  25. require_once $config->get('sysRoot').'alpha/model/types/Boolean.inc';
  26. require_once $config->get('sysRoot').'alpha/model/types/Relation.inc';
  27. require_once $config->get('sysRoot').'alpha/model/types/Sequence.inc';
  28.  
  29. /**
  30.  * Base business object class definition
  31.  * 
  32.  * @package alpha::model
  33.  * @since 1.0
  34.  * @author John Collins <dev@alphaframework.org>
  35.  * @version $Id: AlphaDAO.inc 1342 2011-03-17 16:09:36Z johnc $
  36.  * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
  37.  * @copyright Copyright (c) 2011, John Collins (founder of Alpha Framework).
  38.  *  All rights reserved.
  39.  * 
  40.  *  <pre>
  41.  *  Redistribution and use in source and binary forms, with or
  42.  *  without modification, are permitted provided that the
  43.  *  following conditions are met:
  44.  * 
  45.  *  * Redistributions of source code must retain the above
  46.  *    copyright notice, this list of conditions and the
  47.  *    following disclaimer.
  48.  *  * Redistributions in binary form must reproduce the above
  49.  *    copyright notice, this list of conditions and the
  50.  *    following disclaimer in the documentation and/or other
  51.  *    materials provided with the distribution.
  52.  *  * Neither the name of the Alpha Framework nor the names
  53.  *    of its contributors may be used to endorse or promote
  54.  *    products derived from this software without specific
  55.  *    prior written permission.
  56.  *   
  57.  *  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
  58.  *  CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
  59.  *  INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
  60.  *  MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  61.  *  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
  62.  *  CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
  63.  *  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
  64.  *  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  65.  *  LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
  66.  *  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
  67.  *  CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
  68.  *  OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
  69.  *  SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
  70.  *  </pre>
  71.  *  
  72.  */
  73. abstract class AlphaDAO {
  74.     /**
  75.      * The object ID
  76.      * 
  77.      * @var integer 
  78.      * @since 1.0
  79.      */
  80.     protected $OID;
  81.     
  82.     /**
  83.      * The last database query run by this object.  Useful for tracing an error.
  84.      * 
  85.      * @var string 
  86.      * @since 1.0
  87.      */
  88.     protected $lastQuery;
  89.     
  90.     /**
  91.      * The version number of the object, used for locking mechanism
  92.      * 
  93.      * @var Integer 
  94.      * @since 1.0
  95.      */
  96.     protected $version_num;
  97.     
  98.     /**
  99.      * The timestamp of creation
  100.      * 
  101.      * @var Timestamp 
  102.      * @since 1.0
  103.      */
  104.     protected $created_ts;
  105.     
  106.     /**
  107.      * The OID of the person who created this BO
  108.      * 
  109.      * @var Integer 
  110.      * @since 1.0
  111.      */
  112.     protected $created_by;
  113.     
  114.     /**
  115.      * The timestamp of the last update
  116.      * 
  117.      * @var Timestamp 
  118.      * @since 1.0
  119.      */
  120.     protected $updated_ts;
  121.     
  122.     /**
  123.      * The OID of the person who last updated this BO
  124.      * 
  125.      * @var Integer 
  126.      * @since 1.0
  127.      */
  128.     protected $updated_by;
  129.     
  130.     /**
  131.      * An array of the names of all of the default attributes of a persistent BO defined in this class
  132.      * 
  133.      * @var array 
  134.      * @since 1.0
  135.      */
  136.     protected $defaultAttributes = array("OID""lastQuery""version_num""dataLabels""created_ts""created_by""updated_ts""updated_by""defaultAttributes""transientAttributes""uniqueAttributes""TABLE_NAME""logger");
  137.     
  138.     /**
  139.      * An array of the names of all of the transient attributes of a persistent BO which are not saved to the DB
  140.      * 
  141.      * @var array 
  142.      * @since 1.0
  143.      */
  144.     protected $transientAttributes = array("lastQuery""dataLabels""defaultAttributes""transientAttributes""uniqueAttributes""TABLE_NAME""logger");
  145.     
  146.     /**
  147.      * An array of the uniquely-constained attributes of this persistent BO
  148.      * 
  149.      * @var array 
  150.      * @since 1.0
  151.      */
  152.     protected $uniqueAttributes = array();
  153.     
  154.     /**
  155.      * An array of the data labels used for displaying class attributes
  156.      * 
  157.      * @var array 
  158.      * @since 1.0
  159.      */
  160.     protected $dataLabels = array();
  161.     
  162.     /**
  163.      * Trace logger
  164.      * 
  165.      * @var Logger 
  166.      * @since 1.0
  167.      */
  168.     private static $logger null;
  169.     
  170.     /**
  171.      * Datebase connection
  172.      * 
  173.      * @var mysqli 
  174.      * @since 1.0
  175.      */
  176.     private static $connection;
  177.     
  178.     /**
  179.      * The constructor which sets up some housekeeping attributes
  180.      * 
  181.      * @since 1.0
  182.      */
  183.     public function __construct({
  184.         self::$logger new Logger('AlphaDAO');
  185.         self::$logger->debug('>>__construct()');
  186.         
  187.         $this->version_num = new Integer(0);
  188.         $this->created_ts = new Timestamp(date("Y-m-d H:i:s"));
  189.         $person_ID (isset($_SESSION['currentUser'])$_SESSION['currentUser']->getOID()0);
  190.         $this->created_by = new Integer($person_ID);
  191.         $this->updated_ts = new Timestamp(date("Y-m-d H:i:s"));
  192.         $this->updated_by = new Integer($person_ID);
  193.         
  194.         self::$logger->debug('<<__construct');
  195.     }
  196.     
  197.     /**
  198.      * Gets the current connection singleton, or creates a new one if none exists
  199.      *  
  200.      * @return mysqli 
  201.      * @since 1.0
  202.      */
  203.     public static function getConnection({
  204.         global $config;
  205.         
  206.         if (!isset(self::$connection)) {
  207.             self::$connection new mysqli($config->get('sysDBHost')$config->get('sysDBUsername')$config->get('sysDBPassword')$config->get('sysDB'));
  208.                 
  209.             if (mysqli_connect_error()) {
  210.                 self::$logger->fatal('Could not connect to database: ['.mysqli_connect_errno().'] '.mysqli_connect_error());
  211.             }
  212.         }
  213.         
  214.         return self::$connection;
  215.     }
  216.     
  217.     /**
  218.      * Disconnects the current database connection if one exists (self::$connection is set)
  219.      * 
  220.      * @since 1.0
  221.      */
  222.     public static function disconnect({
  223.         if (isset(self::$connection)) {
  224.             self::$connection->close();
  225.             self::$connection null;
  226.         }
  227.     }
  228.     
  229.     /**
  230.      * Populates the child object with the properties retrived from the database for the object $OID.
  231.      * 
  232.      * @param integer $OID The object ID of the business object to load.
  233.      * @since 1.0
  234.      * @throws BONotFoundException
  235.      */
  236.     public function load($OID{
  237.         self::$logger->debug('>>load(OID=['.$OID.'])');
  238.         
  239.         if(method_exists($this'before_load_callback'))
  240.             $this->before_load_callback();
  241.         
  242.         $this->OID = $OID;
  243.         
  244.         $attributes $this->getPersistentAttributes();
  245.         $fields '';
  246.         foreach($attributes as $att)
  247.             $fields .= $att.',';
  248.         $fields substr($fields0-1);
  249.         
  250.         $sqlQuery 'SELECT '.$fields.' FROM '.$this->getTableName().' WHERE OID = ? LIMIT 1;';                    
  251.         $this->lastQuery = $sqlQuery;
  252.         $stmt AlphaDAO::getConnection()->stmt_init();
  253.  
  254.         $row array();
  255.         
  256.         if($stmt->prepare($sqlQuery)) {
  257.             $stmt->bind_param('i'$OID);
  258.             $stmt->execute();
  259.             
  260.             $result $this->bindResult($stmt);
  261.             if(isset($result[0]))
  262.                 $row $result[0];
  263.             
  264.             $stmt->close();
  265.         }else{
  266.             self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
  267.             if(!$this->checkTableExists()) {
  268.                 $this->makeTable();
  269.                 throw new BONotFoundException('Failed to load object of OID ['.$OID.'], table ['.$this->getTableName().'] did not exist so had to create!');
  270.             }
  271.             return;
  272.         }
  273.         
  274.         if(!isset($row['OID']|| $row['OID'1{
  275.             throw new BONotFoundException('Failed to load object of OID ['.$OID.'] not found in database.');            
  276.             self::$logger->debug('<<load');
  277.             return;
  278.         }
  279.         
  280.         // get the class attributes
  281.         $reflection new ReflectionClass(get_class($this));
  282.         $properties $reflection->getProperties();
  283.  
  284.         try {
  285.             foreach($properties as $propObj{
  286.                 $propName $propObj->name;
  287.                                             
  288.                 // filter transient attributes
  289.                 if(!in_array($propName$this->transientAttributes)) {
  290.                     $this->set($propName$row[$propName]);
  291.                 }elseif(!$propObj->isPrivate(&& isset($this->$propName&& $this->$propName instanceof Relation{
  292.                     $prop $this->getPropObject($propName);
  293.                     
  294.                     // handle the setting of ONE-TO-MANY relation values
  295.                     if($prop->getRelationType(== 'ONE-TO-MANY'{
  296.                         $this->set($propObj->name$this->getOID());
  297.                     }
  298.                 }
  299.             }
  300.         }catch (IllegalArguementException $e{
  301.             self::$logger->warn('Bad data stored in the table ['.$this->getTableName().'], field ['.$propObj->name.'] bad value['.$row[$propObj->name].'], exception ['.$e->getMessage().']');
  302.         }catch (PHPException $e{
  303.             // it is possible that the load failed due to the table not being up-to-date
  304.             if($this->checkTableNeedsUpdate()) {                
  305.                 $missingFields $this->findMissingFields();
  306.             
  307.                 $count count($missingFields);
  308.                 
  309.                 for($i 0$i $count$i++)
  310.                     $this->addProperty($missingFields[$i]);
  311.                     
  312.                 throw new BONotFoundException('Failed to load object of OID ['.$OID.'], table ['.$this->getTableName().'] was out of sync with the database so had to be updated!');
  313.                 self::$logger->debug('<<load');
  314.                 return;
  315.             }
  316.         }
  317.         
  318.         $this->setEnumOptions();    
  319.         if(method_exists($this'after_load_callback'))
  320.             $this->after_load_callback();
  321.         
  322.         self::$logger->debug('<<load');
  323.     }
  324.     
  325.     /**
  326.      * Populates the child object from the database table by the given attribute value.
  327.      * 
  328.      * @param string $atribute The name of the attribute to load the object by.
  329.      * @param string $value The value of the attribute to load the object by.
  330.      * @param boolean $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
  331.      * @param array $attributes The attributes to load from the database to this object (leave blank to load all attributes)
  332.      * @since 1.0
  333.      * @throws BONotFoundException
  334.      */
  335.     public function loadByAttribute($attribute$value$ignoreClassType=false$attributes=array()) {
  336.         self::$logger->debug('>>loadByAttribute(attribute=['.$attribute.'], value=['.$value.'], ignoreClassType=['.$ignoreClassType.'], 
  337.             attributes=['.var_export($attributestrue).'])');
  338.         
  339.         if(method_exists($this'before_loadByAttribute_callback'))
  340.                 $this->before_loadByAttribute_callback();
  341.         
  342.         if(count($attributes== 0)
  343.             $attributes $this->getPersistentAttributes();
  344.         
  345.         $fields '';
  346.         foreach($attributes as $att)
  347.             $fields .= $att.',';
  348.         $fields substr($fields0-1);
  349.         
  350.         if(!$ignoreClassType && $this->isTableOverloaded())
  351.             $sqlQuery 'SELECT '.$fields.' FROM '.$this->getTableName().' WHERE '.$attribute.' = ? AND classname = ? LIMIT 1;';
  352.         else
  353.             $sqlQuery 'SELECT '.$fields.' FROM '.$this->getTableName().' WHERE '.$attribute.' = ? LIMIT 1;';
  354.         
  355.         self::$logger->debug('Query=['.$sqlQuery.']');    
  356.         
  357.         $this->lastQuery = $sqlQuery;
  358.         $stmt AlphaDAO::getConnection()->stmt_init();
  359.  
  360.         $row array();
  361.         
  362.         if($stmt->prepare($sqlQuery)) {
  363.             if($this->$attribute instanceof Integer{
  364.                 if(!$ignoreClassType && $this->isTableOverloaded()) {
  365.                     $stmt->bind_param('is'$valueget_class($this));
  366.                 }else{
  367.                     $stmt->bind_param('i'$value);
  368.                 }
  369.             }else{
  370.                 if(!$ignoreClassType && $this->isTableOverloaded()) {
  371.                     $stmt->bind_param('ss'$valueget_class($this));
  372.                 }else{
  373.                     $stmt->bind_param('s'$value);
  374.                 }
  375.             }
  376.             
  377.             $stmt->execute();
  378.             
  379.             $result $this->bindResult($stmt);
  380.             
  381.             if(isset($result[0]))
  382.                 $row $result[0];
  383.                 
  384.             $stmt->close();
  385.         }else{
  386.             self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
  387.             if(!$this->checkTableExists()) {
  388.                 $this->makeTable();
  389.                 throw new BONotFoundException('Failed to load object by attribute ['.$attribute.'] and value ['.$value.'], table did not exist so had to create!');
  390.             }
  391.             return;
  392.         }
  393.         
  394.         if(!isset($row['OID']|| $row['OID'1{
  395.             throw new BONotFoundException('Failed to load object by attribute ['.$attribute.'] and value ['.$value.'], not found in database.');            
  396.             self::$logger->debug('<<loadByAttribute');
  397.             return;
  398.         }
  399.         
  400.         $this->OID = $row['OID'];
  401.         
  402.         // get the class attributes
  403.         $reflection new ReflectionClass(get_class($this));
  404.         $properties $reflection->getProperties();
  405.  
  406.         try {
  407.             foreach($properties as $propObj{
  408.                 $propName $propObj->name;
  409.  
  410.                 if(isset($row[$propName])) {
  411.                     // filter transient attributes
  412.                     if(!in_array($propName$this->transientAttributes)) {
  413.                         $this->set($propName$row[$propName]);
  414.                     }elseif(!$propObj->isPrivate(&& isset($this->$propName&& $this->$propName instanceof Relation{
  415.                         $prop $this->getPropObject($propName);
  416.                         
  417.                         // handle the setting of ONE-TO-MANY relation values
  418.                         if($prop->getRelationType(== 'ONE-TO-MANY'{
  419.                             $this->set($propObj->name$this->getOID());
  420.                         }
  421.                     }
  422.                 }
  423.             }
  424.         }catch (IllegalArguementException $e{
  425.             self::$logger->warn('Bad data stored in the table ['.$this->getTableName().'], field ['.$propObj->name.'] bad value['.$row[$propObj->name].'], exception ['.$e->getMessage().']');
  426.         }catch (PHPException $e{
  427.             // it is possible that the load failed due to the table not being up-to-date
  428.             if($this->checkTableNeedsUpdate()) {                
  429.                 $missingFields $this->findMissingFields();
  430.             
  431.                 $count count($missingFields);
  432.                 
  433.                 for($i 0$i $count$i++)
  434.                     $this->addProperty($missingFields[$i]);
  435.                     
  436.                 throw new BONotFoundException('Failed to load object by attribute ['.$attribute.'] and value ['.$value.'], table ['.$this->getTableName().'] was out of sync with the database so had to be updated!');
  437.                 self::$logger->debug('<<loadByAttribute');
  438.                 return;
  439.             }
  440.         }
  441.         
  442.         $this->setEnumOptions();    
  443.         if(method_exists($this'after_loadByAttribute_callback'))
  444.             $this->after_loadByAttribute_callback();
  445.         
  446.         self::$logger->debug('<<loadByAttribute');
  447.     }
  448.     
  449.     /**
  450.      * Loads all of the objects of this class into an array which is returned.
  451.      * 
  452.      * @param integer $start The start of the SQL LIMIT clause, useful for pagination.
  453.      * @param integer $limit The amount (limit) of objects to load, useful for pagination.
  454.      * @param string $orderBy The name of the field to sort the objects by.
  455.      * @param string $order The order to sort the objects by.
  456.      * @param boolean $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
  457.      * @return array An array containing objects of this type of business object.
  458.      * @since 1.0
  459.      * @throws BONotFoundException
  460.      */
  461.     public function loadAll($start=0$limit=0$orderBy='OID'$order='ASC'$ignoreClassType=false{
  462.         self::$logger->debug('>>loadAll(start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
  463.         
  464.         if(method_exists($this'before_loadAll_callback'))
  465.             $this->before_loadAll_callback();
  466.         
  467.         global $config;
  468.         
  469.         // ensure that the field name provided in the orderBy param is legit
  470.         try {
  471.             $field $this->get($orderBy);
  472.         }catch(AlphaException $e{
  473.             throw new AlphaException('The field name ['.$orderBy.'] provided in the param orderBy does not exist on the class ['.get_class($this).']');
  474.         }
  475.         
  476.         if(!$ignoreClassType && $this->isTableOverloaded()) {
  477.             if($limit == 0{
  478.                 $sqlQuery 'SELECT OID FROM '.$this->getTableName().' WHERE classname=\''.get_class($this).'\' ORDER BY '.$orderBy.' '.$order.';';
  479.             }else{
  480.                 $sqlQuery 'SELECT OID FROM '.$this->getTableName().' WHERE classname=\''.get_class($this).'\' ORDER BY '.$orderBy.' '.$order.' LIMIT '.
  481.                     $start.', '.$limit.';';
  482.             }
  483.         }else{                
  484.             if($limit == 0)
  485.                 $sqlQuery 'SELECT OID FROM '.$this->getTableName().' ORDER BY '.$orderBy.' '.$order.';';
  486.             else
  487.                 $sqlQuery 'SELECT OID FROM '.$this->getTableName().' ORDER BY '.$orderBy.' '.$order.' LIMIT '.$start.', '.$limit.';';
  488.         }
  489.         
  490.         $this->lastQuery = $sqlQuery;
  491.         
  492.         if(!$result AlphaDAO::getConnection()->query($sqlQuery)) {
  493.             throw new BONotFoundException('Failed to load object OIDs, MySql error is ['.AlphaDAO::getConnection()->error.'], query ['.$this->lastQuery.']');
  494.             self::$logger->debug('<<loadAll [0]');
  495.             return array();
  496.         }
  497.         
  498.         // now build an array of objects to be returned
  499.         $objects array();
  500.         $count 0;
  501.         $BO_Class get_class($this);
  502.         
  503.         while($row $result->fetch_array(MYSQLI_ASSOC)) {
  504.             try {
  505.                 $obj new $BO_Class();
  506.                 $obj->load($row['OID']);
  507.                 $objects[$count$obj;
  508.                 $count++;
  509.             }catch(ResourceNotAllowedException $e{
  510.                 // the resource not allowed will be absent from the list
  511.             }
  512.         }
  513.         
  514.         if(method_exists($this'after_loadAll_callback'))
  515.             $this->after_loadAll_callback();
  516.         
  517.         self::$logger->debug('<<loadAll ['.count($objects).']');
  518.         return $objects;
  519.     }
  520.     
  521.     /**
  522.      * Loads all of the objects of this class by the specified attribute into an array which is returned.
  523.      * 
  524.      * @param string $atribute The attribute to load the objects by.
  525.      * @param string $value The value of the attribute to load the objects by.
  526.      * @param integer $start The start of the SQL LIMIT clause, useful for pagination.
  527.      * @param integer $limit The amount (limit) of objects to load, useful for pagination.
  528.      * @param string $orderBy The name of the field to sort the objects by.
  529.      * @param string $order The order to sort the objects by.
  530.      * @param boolean $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
  531.      * @return array An array containing objects of this type of business object.
  532.      * @since 1.0
  533.      * @throws BONotFoundException
  534.      */
  535.     public function loadAllByAttribute($attribute$value$start=0$limit=0$orderBy="OID"$order="ASC"$ignoreClassType=false{
  536.         self::$logger->debug('>>loadAllByAttribute(attribute=['.$attribute.'], value=['.$value.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
  537.         
  538.         if(method_exists($this'before_loadAllByAttribute_callback'))
  539.             $this->before_loadAllByAttribute_callback();
  540.         
  541.         global $config;
  542.         
  543.         if ($start != && $limit != 0)
  544.             $limit ' LIMIT '.$start.', '.$limit.';';
  545.         else
  546.             $limit ';';
  547.         
  548.         if(!$ignoreClassType && $this->isTableOverloaded())
  549.             $sqlQuery "SELECT OID FROM ".$this->getTableName()." WHERE $attribute = ? AND classname = ? ORDER BY ".$orderBy." ".$order.$limit;
  550.         else
  551.             $sqlQuery "SELECT OID FROM ".$this->getTableName()." WHERE $attribute = ? ORDER BY ".$orderBy." ".$order.$limit;            
  552.             
  553.         $this->lastQuery = $sqlQuery;
  554.         self::$logger->debug($sqlQuery);
  555.         
  556.         $stmt AlphaDAO::getConnection()->stmt_init();
  557.  
  558.         $row array();
  559.         
  560.         if($stmt->prepare($sqlQuery)) {
  561.             if($this->$attribute instanceof Integer{
  562.                 if($this->isTableOverloaded()) {
  563.                     $stmt->bind_param('is'$valueget_class($this));
  564.                 }else{
  565.                     $stmt->bind_param('i'$value);
  566.                 }
  567.             }else{
  568.                 if($this->isTableOverloaded()) {
  569.                     $stmt->bind_param('ss'$valueget_class($this));
  570.                 }else{
  571.                     $stmt->bind_param('s'$value);
  572.                 }
  573.             }
  574.             
  575.             $stmt->execute();
  576.             
  577.             $result $this->bindResult($stmt);
  578.                 
  579.             $stmt->close();
  580.         }else{
  581.             self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
  582.             if(!$this->checkTableExists()) {
  583.                 $this->makeTable();
  584.                 throw new BONotFoundException('Failed to load objects by attribute ['.$attribute.'] and value ['.$value.'], table did not exist so had to create!');
  585.             }
  586.             self::$logger->debug('<<loadAllByAttribute []');
  587.             return array();
  588.         }
  589.         
  590.         // now build an array of objects to be returned
  591.         $objects array();
  592.         $count 0;
  593.         $BO_Class get_class($this);
  594.         
  595.         foreach($result as $row{
  596.             try {
  597.                 $obj new $BO_Class();
  598.                 $obj->load($row['OID']);
  599.                 $objects[$count$obj;
  600.                 $count++;
  601.             }catch(ResourceNotAllowedException $e{
  602.                 // the resource not allowed will be absent from the list
  603.             }
  604.         }
  605.         
  606.         if(method_exists($this'after_loadAllByAttribute_callback'))
  607.             $this->after_loadAllByAttribute_callback();
  608.         
  609.         self::$logger->debug('<<loadAllByAttribute ['.count($objects).']');
  610.         return $objects;    
  611.     }
  612.     
  613.     /**
  614.      * Loads all of the objects of this class by the specified attributes into an array which is returned.
  615.      * 
  616.      * @param array $atributes The attributes to load the objects by.
  617.      * @param array $values The values of the attributes to load the objects by.
  618.      * @param integer $start The start of the SQL LIMIT clause, useful for pagination.
  619.      * @param integer $limit The amount (limit) of objects to load, useful for pagination.
  620.      * @param string $orderBy The name of the field to sort the objects by.
  621.      * @param string $order The order to sort the objects by.
  622.      * @param boolean $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
  623.      * @return array An array containing objects of this type of business object.
  624.      * @since 1.0
  625.      * @throws BONotFoundException
  626.      * @throws IllegalArguementException
  627.      */
  628.     public function loadAllByAttributes($attributes=array()$values=array()$start=0$limit=0$orderBy='OID'$order='ASC'$ignoreClassType=false{
  629.         self::$logger->debug('>>loadAllByAttributes(attributes=['.var_export($attributestrue).'], values=['.var_export($valuestrue).'], start=['.
  630.             $start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
  631.         
  632.         if(method_exists($this'before_loadAllByAttributes_callback'))
  633.             $this->before_loadAllByAttributes_callback();
  634.         
  635.         global $config;
  636.         
  637.         if(!is_array($attributes|| !is_array($values)) {
  638.             throw new IllegalArguementException('Illegal arrays attributes=['.var_export($attributestrue).'] and values=['.var_export($valuestrue).
  639.                 '] provided to loadAllByAttributes');
  640.         }
  641.         
  642.         $whereClause ' WHERE';
  643.         
  644.         $count count($attributes);
  645.         
  646.         for($i 0$i $count$i++{
  647.             $whereClause .= ' '.$attributes[$i].' = ? AND';
  648.             self::$logger->debug($whereClause);
  649.         }
  650.         
  651.         if(!$ignoreClassType && $this->isTableOverloaded())
  652.             $whereClause .= ' classname = ? AND';
  653.         
  654.         // remove the last " AND"
  655.         $whereClause substr($whereClause0-4);
  656.         
  657.         if ($limit != 0)
  658.             $limit ' LIMIT '.$start.', '.$limit.';';
  659.         else
  660.             $limit ';';
  661.         
  662.         $sqlQuery "SELECT OID FROM ".$this->getTableName().$whereClause." ORDER BY ".$orderBy." ".$order.$limit;
  663.             
  664.         $this->lastQuery = $sqlQuery;
  665.         self::$logger->debug($sqlQuery);
  666.         
  667.         $stmt AlphaDAO::getConnection()->stmt_init();
  668.         
  669.         if($stmt->prepare($sqlQuery)) {            
  670.             // bind params where required attributes are provided
  671.             if(count($attributes&& count($attributes== count($values)) {
  672.                 $stmt $this->bindParams($stmt$attributes$values);
  673.             }else{
  674.                 // we'll still need to bind the "classname" for overloaded BOs...
  675.                 if($this->isTableOverloaded())
  676.                     $stmt->bind_param('s'get_class($this));
  677.             }
  678.             $stmt->execute();
  679.             
  680.             $result $this->bindResult($stmt);
  681.                 
  682.             $stmt->close();
  683.         }else{
  684.             self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
  685.             
  686.             if(!$this->checkTableExists()) {
  687.                 $this->makeTable();
  688.                 throw new BONotFoundException('Failed to load objects by attributes ['.var_export($attributestrue).'] and values ['.
  689.                     var_export($valuestrue).'], table did not exist so had to create!');
  690.             }
  691.             
  692.             self::$logger->debug('<<loadAllByAttributes []');
  693.             return array();
  694.         }
  695.         
  696.         // now build an array of objects to be returned
  697.         $objects array();
  698.         $count 0;
  699.         $BO_Class get_class($this);
  700.         
  701.         foreach($result as $row{
  702.             try {
  703.                 $obj new $BO_Class();
  704.                 $obj->load($row['OID']);
  705.                 $objects[$count$obj;
  706.                 $count++;
  707.             }catch(ResourceNotAllowedException $e{
  708.                 // the resource not allowed will be absent from the list
  709.             }
  710.         }
  711.         
  712.         if(method_exists($this'after_loadAllByAttributes_callback'))
  713.             $this->after_loadAllByAttributes_callback();
  714.         
  715.         self::$logger->debug('<<loadAllByAttributes ['.count($objects).']');
  716.         return $objects;    
  717.     }
  718.     
  719.     /**
  720.      * Loads all of the objects of this class that where updated (updated_ts value) on the date indicated.
  721.      * 
  722.      * @param string $date The date for which to load the objects updated on, in the format 'YYYY-MM-DD'.
  723.      * @param integer $start The start of the SQL LIMIT clause, useful for pagination.
  724.      * @param integer $limit The amount (limit) of objects to load, useful for pagination.
  725.      * @param string $orderBy The name of the field to sort the objects by.
  726.      * @param string $order The order to sort the objects by.
  727.      * @param boolean $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
  728.      * @return array An array containing objects of this type of business object.
  729.      * @since 1.0
  730.      * @throws BONotFoundException
  731.      */
  732.     public function loadAllByDayUpdated($date$start=0$limit=0$orderBy="OID"$order="ASC"$ignoreClassType=false{
  733.         self::$logger->debug('>>loadAllByDayUpdated(date=['.$date.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
  734.         
  735.         if(method_exists($this'before_loadAllByDayUpdated_callback'))
  736.             $this->before_loadAllByDayUpdated_callback();
  737.         
  738.         global $config;
  739.         
  740.         if ($start != && $limit != 0)
  741.             $limit ' LIMIT '.$start.', '.$limit.';';
  742.         else
  743.             $limit ';';
  744.         
  745.         if(!$ignoreClassType && $this->isTableOverloaded())
  746.             $sqlQuery "SELECT OID FROM ".$this->getTableName()." WHERE updated_ts >= '".$date." 00:00:00' AND updated_ts <= '".$date." 23:59:59' AND classname='".get_class($this)."' ORDER BY ".$orderBy." ".$order.$limit;
  747.         else
  748.             $sqlQuery "SELECT OID FROM ".$this->getTableName()." WHERE updated_ts >= '".$date." 00:00:00' AND updated_ts <= '".$date." 23:59:59' ORDER BY ".$orderBy." ".$order.$limit;
  749.             
  750.         $this->lastQuery = $sqlQuery;
  751.  
  752.         if(!$result AlphaDAO::getConnection()->query($sqlQuery)) {
  753.             throw new BONotFoundException('Failed to load object OIDs, MySql error is ['.AlphaDAO::getConnection()->error.'], query ['.$this->lastQuery.']');
  754.             self::$logger->debug('<<loadAllByDayUpdated []');
  755.             return array();
  756.         }
  757.         
  758.         // now build an array of objects to be returned
  759.         $objects array();
  760.         $count 0;
  761.         $BO_Class get_class($this);
  762.         
  763.         while($row $result->fetch_array(MYSQLI_ASSOC)) {
  764.             $obj new $BO_Class();
  765.             $obj->load($row['OID']);
  766.             $objects[$count$obj;
  767.             $count++;
  768.         }
  769.         
  770.         if(method_exists($this'after_loadAllByDayUpdated_callback'))
  771.             $this->after_loadAllByDayUpdated_callback();
  772.         
  773.         self::$logger->debug('<<loadAllByDayUpdated ['.count($objects).']');
  774.         return $objects;    
  775.     }
  776.     
  777.     /**
  778.      * Loads all of the specified attribute values of this class by the specified attribute into an
  779.      * array which is returned.
  780.      * 
  781.      * @param string $attribute The attribute name to load the field values by.
  782.      * @param string $value The value of the attribute to load the field values by.
  783.      * @param string $returnAttribute The name of the attribute to return.
  784.      * @param string $order The order to sort the BOs by.
  785.      * @param boolean $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type.
  786.      * @return array An array of field values.
  787.      * @since 1.0
  788.      * @throws BONotFoundException
  789.      */
  790.     public function loadAllFieldValuesByAttribute($attribute$value$returnAttribute$order='ASC'$ignoreClassType=false{
  791.         self::$logger->debug('>>loadAllFieldValuesByAttribute(attribute=['.$attribute.'], value=['.$value.'], returnAttribute=['.$returnAttribute.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
  792.         
  793.         global $config;
  794.         
  795.         if(!$ignoreClassType && $this->isTableOverloaded())
  796.             $sqlQuery "SELECT ".$returnAttribute." FROM ".$this->getTableName()." WHERE $attribute = '$value' AND classname='".get_class($this)."' ORDER BY OID ".$order.";";
  797.         else
  798.             $sqlQuery "SELECT ".$returnAttribute." FROM ".$this->getTableName()." WHERE $attribute = '$value' ORDER BY OID ".$order.";";
  799.             
  800.             
  801.         $this->lastQuery = $sqlQuery;
  802.         self::$logger->debug('lastQuery ['.$sqlQuery.']');
  803.  
  804.         if(!$result AlphaDAO::getConnection()->query($sqlQuery)) {
  805.             throw new BONotFoundException('Failed to load field ['.$returnAttribute.'] values, MySql error is ['.AlphaDAO::getConnection()->error.'], query ['.$this->lastQuery.']');
  806.             self::$logger->debug('<<loadAllFieldValuesByAttribute []');
  807.             return array();
  808.         }
  809.         
  810.         // now build an array of attribute values to be returned
  811.         $values array();
  812.         $count 0;
  813.         $BO_Class get_class($this);
  814.         
  815.         while($row $result->fetch_array(MYSQLI_ASSOC)) {
  816.             $values[$count$row[$returnAttribute];
  817.             $count++;
  818.         }
  819.         
  820.         self::$logger->debug('<<loadAllFieldValuesByAttribute ['.count($values).']');
  821.         return $values;
  822.     }
  823.  
  824.     /**
  825.      * Saves the object.  If $this->OID is empty or null it will INSERT, otherwise UPDATE.
  826.      * 
  827.      * @since 1.0
  828.      * @throws FailedSaveException
  829.      * @throws LockingException
  830.      * @throws ValidationException
  831.      */
  832.     public function save({
  833.         self::$logger->debug('>>save()');
  834.             
  835.         if(method_exists($this'before_save_callback'))
  836.             $this->before_save_callback();
  837.         
  838.         // firstly we will validate the object before we try to save it
  839.         if(!$this->validate()) {
  840.             throw new FailedSaveException('Could not save due to a validation error.');
  841.             return;
  842.         }else{        
  843.             // get the class attributes
  844.             $reflection new ReflectionClass(get_class($this));
  845.             $properties $reflection->getProperties();
  846.             $sqlQuery '';
  847.             $stmt null;
  848.  
  849.             if($this->getVersion(!= $this->version_num->getValue()){
  850.                 throw new LockingException('Could not save the object as it has been updated by another user.  Please try saving again.');
  851.                 return;
  852.             }
  853.             
  854.             // set the "updated by" fields, we can only set the user id if someone is logged in
  855.             if(isset($_SESSION['currentUser']))
  856.                 $this->updated_by->setValue($_SESSION['currentUser']->getOID());
  857.             
  858.             $this->updated_ts = new Timestamp(date("Y-m-d H:i:s"));
  859.             
  860.             // check to see if it is a transient object that needs to be inserted
  861.             if($this->isTransient()) {
  862.                 $savedFieldsCount 0;
  863.                 $sqlQuery 'INSERT INTO '.$this->getTableName().' (';
  864.     
  865.                 foreach($properties as $propObj{
  866.                     $propName $propObj->name;
  867.                     if (!in_array($propName$this->transientAttributes)) {
  868.                         // Skip the OID, database auto number takes care of this.
  869.                         if($propName != 'OID' && $propName != 'version_num'{
  870.                             $sqlQuery .= "$propName,";
  871.                             $savedFieldsCount++;
  872.                         }
  873.                         
  874.                         if($propName == 'version_num'{
  875.                             $sqlQuery .= 'version_num,';
  876.                             $savedFieldsCount++;
  877.                         }
  878.                     }
  879.                 }
  880.                 if($this->isTableOverloaded())
  881.                     $sqlQuery .= 'classname,';
  882.     
  883.                 $sqlQuery rtrim($sqlQuery",");
  884.     
  885.                 $sqlQuery .= ') VALUES (';
  886.                 
  887.                 for($i 0$i $savedFieldsCount$i++)
  888.                     $sqlQuery.= '?,';
  889.                 
  890.                 if($this->isTableOverloaded())
  891.                     $sqlQuery.= '?,';
  892.                 
  893.                 $sqlQuery rtrim($sqlQuery',').')';
  894.                 
  895.                 $this->lastQuery = $sqlQuery;
  896.                 self::$logger->debug('Query ['.$sqlQuery.']');
  897.                 $stmt AlphaDAO::getConnection()->stmt_init();
  898.             
  899.                 if($stmt->prepare($sqlQuery)) {            
  900.                     $stmt $this->bindParams($stmt);                    
  901.                     $stmt->execute();                    
  902.                 }else{
  903.                     throw new FailedSaveException('Failed to save object, error is ['.$stmt->error.'], query ['.$this->lastQuery.']');
  904.                 }
  905.             }else{
  906.                 // assume that it is a persistent object that needs to be updated
  907.                 $savedFieldsCount 0;
  908.                 $sqlQuery 'UPDATE '.$this->getTableName().' SET ';
  909.     
  910.                 foreach($properties as $propObj{
  911.                     $propName $propObj->name;
  912.                     if (!in_array($propName$this->transientAttributes)) {
  913.                         // Skip the OID, database auto number takes care of this.
  914.                         if($propName != 'OID' && $propName != 'version_num'{                            
  915.                             $sqlQuery .= "$propName = ?,";
  916.                             $savedFieldsCount++;
  917.                         }
  918.                         
  919.                         if($propName == 'version_num'{
  920.                             $sqlQuery .= 'version_num = ?,';
  921.                             $savedFieldsCount++;
  922.                         }
  923.                     }
  924.                 }
  925.                 if($this->isTableOverloaded())
  926.                     $sqlQuery .= 'classname = ?,';
  927.     
  928.                 $sqlQuery rtrim($sqlQuery",");
  929.     
  930.                 $sqlQuery .= " WHERE OID=?;";
  931.                 
  932.                 $this->lastQuery = $sqlQuery;
  933.                 $stmt AlphaDAO::getConnection()->stmt_init();
  934.                 
  935.                 if($stmt->prepare($sqlQuery)) {                            
  936.                     $this->bindParams($stmt);                    
  937.                     $stmt->execute();                    
  938.                 }else{
  939.                     throw new FailedSaveException('Failed to save object, error is ['.$stmt->error.'], query ['.$this->lastQuery.']');                
  940.                 }                
  941.             }
  942.     
  943.             if ($stmt != null && $stmt->error == ''{
  944.                 // populate the updated OID in case we just done an insert                
  945.                 if($this->isTransient())
  946.                     $this->setOID(AlphaDAO::getConnection()->insert_id);
  947.  
  948.                 try {
  949.                     foreach($properties as $propObj{
  950.                         $propName $propObj->name;
  951.                         
  952.                         if(!$propObj->isPrivate(&& isset($this->$propName&& $this->$propName instanceof Relation{                
  953.                             $prop $this->getPropObject($propName);
  954.                             
  955.                             // handle the saving of MANY-TO-MANY relation values
  956.                             if($prop->getRelationType(== 'MANY-TO-MANY'{
  957.                                 try {
  958.                                     try{
  959.                                         // check to see if the rel is on this class
  960.                                         $side $prop->getSide(get_class($this));                                            
  961.                                     }catch (IllegalArguementException $iae{
  962.                                         $side $prop->getSide(ucfirst($this->getTableName()).'Object');
  963.                                     }
  964.                                         
  965.                                     $lookUp $prop->getLookup();                                    
  966.                                         
  967.                                     // first delete all of the old RelationLookup objects for this rel
  968.                                     try {
  969.                                         if($side == 'left')
  970.                                             $lookUp->deleteAllByAttribute('leftID'$this->getOID());
  971.                                         else
  972.                                             $lookUp->deleteAllByAttribute('rightID'$this->getOID());                            
  973.                                     }catch (Exception $e{
  974.                                         throw new FailedSaveException('Failed to delete old RelationLookup objects on the table ['.$prop->getLookup()->getTableName().'], error is ['.$e->getMessage().']');
  975.                                     }
  976.                                             
  977.                                     if(isset($_POST[$propName]&& $_POST[$propName!= '00000000000')
  978.                                         $OIDs explode(','$_POST[$propName]);
  979.                                                                                     
  980.                                     if(isset($OIDs&& !empty($OIDs[0])) {                                        
  981.                                         // now for each posted OID, create a new RelationLookup record and save
  982.                                         foreach ($OIDs as $oid{                                            
  983.                                             $newLookUp new RelationLookup($lookUp->get('leftClassName')$lookUp->get('rightClassName'));
  984.                                             if($side == 'left'{
  985.                                                 $newLookUp->set('leftID'$this->getOID());
  986.                                                 $newLookUp->set('rightID'$oid);
  987.                                             }else{
  988.                                                 $newLookUp->set('rightID'$this->getOID());
  989.                                                 $newLookUp->set('leftID'$oid);
  990.                                             }
  991.                                             $newLookUp->save();
  992.                                         }                                    
  993.                                     }
  994.                                 }catch (Exception $e{
  995.                                     throw new FailedSaveException('Failed to update a MANY-TO-MANY relation on the object, error is ['.$e->getMessage().']');
  996.                                     return;
  997.                                 }
  998.                             }
  999.                             
  1000.                             // handle the saving of ONE-TO-MANY relation values
  1001.                             if($prop->getRelationType(== 'ONE-TO-MANY'{
  1002.                                 $prop->setValue($this->getOID());
  1003.                             }
  1004.                         }                        
  1005.                     }
  1006.                 }catch (Exception $e{
  1007.                     throw new FailedSaveException('Failed to save object, error is ['.$e->getMessage().']');
  1008.                     return;
  1009.                 }
  1010.                 
  1011.                 $stmt->close();
  1012.                 
  1013.                 if(method_exists($this'after_save_callback'))
  1014.                     $this->after_save_callback();
  1015.             }else{
  1016.                 // there has been an error, so decrement the version number back
  1017.                 $temp $this->version_num->getValue();                    
  1018.                 $this->version_num->setValue($temp-1);    
  1019.                 
  1020.                 // check for unique violations            
  1021.                 if(AlphaDAO::getConnection()->errno == '1062'{                    
  1022.                     throw new ValidationException('Failed to save, the value '.$this->findOffendingValue(AlphaDAO::getConnection()->error).' is already in use!');
  1023.                     return;
  1024.                 }else{                    
  1025.                     throw new FailedSaveException('Failed to save object, MySql error is ['.AlphaDAO::getConnection()->error.'], query ['.$this->lastQuery.']');
  1026.                 }
  1027.             }
  1028.         }
  1029.     }
  1030.     
  1031.     /**
  1032.      * Saves the field specified with the value supplied.  Only works for persistent BOs.  Note that no Alpha type
  1033.      * validation is performed with this method!
  1034.      * 
  1035.      * @param string $attribute The name of the attribute to save.
  1036.      * @param mixed $value The value of the attribute to save.
  1037.      * @since 1.0
  1038.      * @throws IllegalArguementException
  1039.      * @throws FailedSaveException
  1040.      */
  1041.     public function saveAttribute($attribute$value{
  1042.         self::$logger->debug('>>saveAttribute(attribute=['.$attribute.'], value=['.$value.'])');
  1043.         
  1044.         if(method_exists($this'before_saveAttribute_callback'))
  1045.             $this->before_saveAttribute_callback();
  1046.         
  1047.         if(!isset($this->$attribute))
  1048.             throw new IllegalArguementException('Could not perform save, as the attribute ['.$attribute.'] is not present on the class['.get_class($this).']');
  1049.         
  1050.         if($this->isTransient())
  1051.             throw new FailedSaveException('Cannot perform saveAttribute method on transient BO!');
  1052.  
  1053.         // assume that it is a persistent object that needs to be updated
  1054.         $sqlQuery 'UPDATE '.$this->getTableName().' SET '.$attribute.'=? WHERE OID=?;';
  1055.                 
  1056.         $this->lastQuery = $sqlQuery;
  1057.         $stmt AlphaDAO::getConnection()->stmt_init();
  1058.                 
  1059.         if($stmt->prepare($sqlQuery)) {
  1060.             if($this->$attribute instanceof Integer)
  1061.                 $bindingsType 'i';
  1062.             else
  1063.                 $bindingsType 's';
  1064.             $stmt->bind_param($bindingsType.'i'$value$this->getOID());
  1065.             self::$logger->debug('Binding params ['.$bindingsType.'i, '.$value.', '.$this->getOID().']');
  1066.             $stmt->execute();                    
  1067.         }else{
  1068.             throw new FailedSaveException('Failed to save attribute, error is ['.$stmt->error.'], query ['.$this->lastQuery.']');                
  1069.         }
  1070.                 
  1071.         $stmt->close();
  1072.         
  1073.         $this->set($attribute$value);
  1074.                 
  1075.         if(method_exists($this'after_saveAttribute_callback'))
  1076.             $this->after_saveAttribute_callback();
  1077.             
  1078.         self::$logger->debug('<<saveAttribute');
  1079.     }
  1080.     
  1081.     /**
  1082.      * Validates the object to be saved.
  1083.      * 
  1084.      * @return boolean 
  1085.      * @since 1.0
  1086.      * @throws ValidationException
  1087.      */
  1088.     protected function validate({
  1089.         self::$logger->debug('>>validate()');
  1090.         
  1091.         if(method_exists($this'before_validate_callback'))
  1092.             $this->before_validate_callback();
  1093.         
  1094.         $valid true;
  1095.                 
  1096.         // get the class attributes
  1097.         $reflection new ReflectionClass(get_class($this));
  1098.         $properties $reflection->getProperties();
  1099.         
  1100.         foreach($properties as $propObj{
  1101.             $propName $propObj->name;            
  1102.             if(!in_array($propName$this->defaultAttributes&& !in_array($propName$this->transientAttributes)) {
  1103.                 $propClass get_class($this->getPropObject($propName));
  1104.                 if (strtoupper($propClass!= "ENUM" &&
  1105.                 strtoupper($propClass!= "DENUM" &&
  1106.                 strtoupper($propClass!= "DENUMITEM" && 
  1107.                 strtoupper($propClass!= "BOOLEAN"{
  1108.                     if ($this->getPropObject($propName!= false && !preg_match($this->getPropObject($propName)->getRule()$this->getPropObject($propName)->getValue())) {
  1109.                         throw new ValidationException('Failed to save, validation error is: '.$this->getPropObject($propName)->getHelper());
  1110.                         $valid false;
  1111.                     }
  1112.                 }
  1113.             }
  1114.         }
  1115.         
  1116.         if(method_exists($this'after_validate_callback'))
  1117.             $this->after_validate_callback();
  1118.             
  1119.         self::$logger->debug('<<validate ['.$valid.']');        
  1120.         return $valid;
  1121.     }
  1122.     
  1123.     /**
  1124.      * Deletes the current object from the database.
  1125.      * 
  1126.      * @since 1.0
  1127.      * @throws FailedDeleteException
  1128.      */
  1129.     public function delete({
  1130.         self::$logger->debug('>>delete()');
  1131.                 
  1132.         if(method_exists($this'before_delete_callback'))
  1133.                 $this->before_delete_callback();
  1134.         
  1135.         // get the class attributes
  1136.         $reflection new ReflectionClass(get_class($this));
  1137.         $properties $reflection->getProperties();
  1138.             
  1139.         // check for any relations on this object, then remove them to prevent orphaned data
  1140.         foreach($properties as $propObj{
  1141.             $propName $propObj->name;
  1142.                         
  1143.             if(!$propObj->isPrivate(&& isset($this->$propName&& $this->$propName instanceof Relation{                
  1144.                 $prop $this->getPropObject($propName);
  1145.                     
  1146.                 // Handle MANY-TO-MANY rels
  1147.                 if($prop->getRelationType(== 'MANY-TO-MANY'{
  1148.                     self::$logger->debug('Deleting MANY-TO-MANY lookup objects...');
  1149.                     
  1150.                     try{
  1151.                         // check to see if the rel is on this class
  1152.                         $side $prop->getSide(get_class($this));                                            
  1153.                     }catch (IllegalArguementException $iae{
  1154.                         $side $prop->getSide(ucfirst($this->getTableName()).'Object');
  1155.                     }
  1156.                                                                 
  1157.                     self::$logger->debug('Side is ['.$side.']'.$this->getOID());
  1158.                         
  1159.                     $lookUp $prop->getLookup();
  1160.                     self::$logger->debug('Lookup object['.var_export($lookUptrue).']');
  1161.                                         
  1162.                     // delete all of the old RelationLookup objects for this rel
  1163.                     if($side == 'left')
  1164.                         $lookUp->deleteAllByAttribute('leftID'$this->getOID());
  1165.                     else
  1166.                         $lookUp->deleteAllByAttribute('rightID'$this->getOID());
  1167.                     self::$logger->debug('...done deleting!');
  1168.                 }
  1169.                     
  1170.                 // should set related field values to null (MySQL is doing this for us as-is)
  1171.                 if($prop->getRelationType(== 'ONE-TO-MANY' && !$prop->getRelatedClass(== 'TagObject'{
  1172.                     $relatedObjects $prop->getRelatedObjects();
  1173.                     
  1174.                     foreach($relatedObjects as $object{
  1175.                         $object->set($prop->getRelatedClassField()null);
  1176.                         $object->save();
  1177.                     }
  1178.                 }
  1179.                 
  1180.                 // in the case of tags, we will always remove the related tags once the BO is deleted
  1181.                 if($prop->getRelationType(== 'ONE-TO-MANY' && $prop->getRelatedClass(== 'TagObject'{
  1182.                     // just making sure that the Relation is set to current OID as its transient
  1183.                     $prop->setValue($this->getOID());
  1184.                     $relatedObjects $prop->getRelatedObjects();                    
  1185.                     
  1186.                     foreach($relatedObjects as $object{                        
  1187.                         $object->delete();
  1188.                     }
  1189.                 }
  1190.             }
  1191.         }
  1192.         
  1193.         $sqlQuery "DELETE FROM ".$this->getTableName()." WHERE OID = ?;";
  1194.             
  1195.         $this->lastQuery = $sqlQuery;
  1196.         
  1197.         $stmt AlphaDAO::getConnection()->stmt_init();
  1198.                 
  1199.         if($stmt->prepare($sqlQuery)) {
  1200.             $stmt->bind_param('i'$this->getOID());            
  1201.             $stmt->execute();
  1202.             self::$logger->debug('Deleted the object ['.$this->getOID().'] of class ['.get_class($this).']');                    
  1203.         }else{
  1204.             throw new FailedDeleteException('Failed to delete object ['.$this->getOID().'], error is ['.$stmt->error.'], query ['.$this->lastQuery.']');                
  1205.         }
  1206.                 
  1207.         $stmt->close();
  1208.         
  1209.         if(method_exists($this'after_delete_callback'))
  1210.             $this->after_delete_callback();
  1211.             
  1212.         $this->clear();
  1213.         self::$logger->debug('<<delete');
  1214.     }
  1215.     
  1216.     /**
  1217.      * Delete all object instances from the database by the specified attribute matching the value provided.
  1218.      *
  1219.      * @param string $attribute The name of the field to delete the objects by.
  1220.      * @param mixed $value The value of the field to delete the objects by.
  1221.      * @return integer The number of rows deleted.
  1222.      * @since 1.0
  1223.      * @throws FailedDeleteException
  1224.      */
  1225.     public function deleteAllByAttribute($attribute$value{
  1226.         self::$logger->debug('>>deleteAllByAttribute(attribute=['.$attribute.'], value=['.$value.'])');
  1227.                 
  1228.         if(method_exists($this'before_deleteAllByAttribute_callback'))
  1229.             $this->before_deleteAllByAttribute_callback();
  1230.         
  1231.         try {
  1232.             $doomedObjects $this->loadAllByAttribute($attribute$value);
  1233.             $deletedRowCount 0;
  1234.             
  1235.             foreach ($doomedObjects as $object{
  1236.                 $object->delete();
  1237.                 $deletedRowCount++;
  1238.             }
  1239.         }catch (BONotFoundException $bonf{
  1240.             // nothing found to delete
  1241.             self::$logger->warn($bonf->getMessage());
  1242.             return 0;
  1243.         }catch (AlphaException $e{
  1244.             throw new FailedDeleteException('Failed to delete objects, error is ['.$e->getMessage().']');
  1245.             self::$logger->debug('<<deleteAllByAttribute [0]');
  1246.             return 0;
  1247.         }
  1248.         
  1249.         if(method_exists($this'after_deleteAllByAttribute_callback'))
  1250.             $this->after_deleteAllByAttribute_callback();
  1251.             
  1252.         self::$logger->debug('<<deleteAllByAttribute ['.$deletedRowCount.']');
  1253.         return $deletedRowCount;
  1254.     }
  1255.     
  1256.     /**
  1257.      * Gets the version_num of the object from the database (returns 0 if the BO is not saved yet).
  1258.      * 
  1259.      * @return integer 
  1260.      * @since 1.0
  1261.      * @throws BONotFoundException
  1262.      */
  1263.     public function getVersion({
  1264.         self::$logger->debug('>>getVersion()');
  1265.         
  1266.         if(method_exists($this'before_getVersion_callback'))
  1267.             $this->before_getVersion_callback();
  1268.         
  1269.         $sqlQuery 'SELECT version_num FROM '.$this->getTableName().' WHERE OID = ?;';
  1270.         $this->lastQuery = $sqlQuery;
  1271.         
  1272.         $stmt AlphaDAO::getConnection()->stmt_init();
  1273.         
  1274.         if($stmt->prepare($sqlQuery)) {
  1275.             $stmt->bind_param('i'$this->OID);
  1276.             
  1277.             $stmt->execute();
  1278.             
  1279.             $result $this->bindResult($stmt);
  1280.             if(isset($result[0]))
  1281.                 $row $result[0];
  1282.                 
  1283.             $stmt->close();
  1284.         }else{
  1285.             self::$logger->warn('The following query caused an unexpected result ['.$sqlQuery.']');
  1286.             if(!$this->checkTableExists()) {
  1287.                 $this->makeTable();
  1288.                 throw new BONotFoundException('Failed to get the version number, table did not exist so had to create!');
  1289.             }
  1290.             return;
  1291.         }
  1292.         
  1293.         if(!isset($row['version_num']|| $row['version_num'1{
  1294.             if(method_exists($this'after_getVersion_callback'))
  1295.                 $this->after_getVersion_callback();
  1296.                 
  1297.             self::$logger->debug('<<getVersion [0]');
  1298.             return 0;
  1299.         }else{
  1300.             if(method_exists($this'after_getVersion_callback'))
  1301.                 $this->after_getVersion_callback();
  1302.             
  1303.             $version_num $row['version_num'];
  1304.             
  1305.             self::$logger->debug('<<getVersion ['.$version_num.']');
  1306.             return $version_num;
  1307.         }
  1308.     }
  1309.  
  1310.     /**
  1311.      * Builds a new database table for the BO class.
  1312.      * 
  1313.      * @since 1.0
  1314.      * @throws AlphaException
  1315.      */    
  1316.     public function makeTable({
  1317.         self::$logger->debug('>>makeTable()');
  1318.         
  1319.         if(method_exists($this'before_makeTable_callback'))
  1320.             $this->before_makeTable_callback();
  1321.         
  1322.         $sqlQuery "CREATE TABLE ".$this->getTableName()." (OID INT(11) ZEROFILL NOT NULL AUTO_INCREMENT,";
  1323.         
  1324.         // get the class attributes
  1325.         $reflection new ReflectionClass(get_class($this));
  1326.         $properties $reflection->getProperties();
  1327.         
  1328.         foreach($properties as $propObj{
  1329.             $propName $propObj->name;
  1330.             
  1331.             if(!in_array($propName$this->transientAttributes&& $propName != "OID"{
  1332.                 $propClass get_class($this->getPropObject($propName));
  1333.  
  1334.                 switch (strtoupper($propClass)) {
  1335.                     case "INTEGER":
  1336.                         // special properties for RelationLookup OIDs
  1337.                         if($this instanceof RelationLookup && ($propName == 'leftID' || $propName == 'rightID'))
  1338.                             $sqlQuery .= "$propName INT(".$this->getPropObject($propName)->getSize().") ZEROFILL NOT NULL,";
  1339.                         else
  1340.                             $sqlQuery .= "$propName INT(".$this->getPropObject($propName)->getSize()."),";
  1341.                     break;
  1342.                     case "DOUBLE":
  1343.                         $sqlQuery .= "$propName DOUBLE(".$this->getPropObject($propName)->getSize(true)."),";
  1344.                     break;
  1345.                     case "STRING":
  1346.                         $sqlQuery .= "$propName VARCHAR(".$this->getPropObject($propName)->getSize()."),";
  1347.                     break;
  1348.                     case "TEXT":
  1349.                         $sqlQuery .= "$propName TEXT,";
  1350.                     break;
  1351.                     case "BOOLEAN":
  1352.                         $sqlQuery .= "$propName CHAR(1) DEFAULT '0',";
  1353.                     break;
  1354.                     case "DATE":
  1355.                         $sqlQuery .= "$propName DATE,";
  1356.                     break;
  1357.                     case "TIMESTAMP":
  1358.                         $sqlQuery .= "$propName DATETIME,";
  1359.                     break;
  1360.                     case "ENUM":
  1361.                         $sqlQuery .= "$propName ENUM(";
  1362.                         $enumVals $this->getPropObject($propName)->getOptions();
  1363.                         foreach($enumVals as $val{
  1364.                             $sqlQuery .= "'".$val."',";
  1365.                         }
  1366.                         $sqlQuery rtrim($sqlQuery",");
  1367.                         $sqlQuery .= "),";
  1368.                     break;
  1369.                     case "DENUM":
  1370.                         $tmp new DEnum(get_class($this).'::'.$propName);                        
  1371.                         $tmp->save();
  1372.                         $sqlQuery .= "$propName INT(11) ZEROFILL,";
  1373.                     break;
  1374.                     case "RELATION":                        
  1375.                         $sqlQuery .= "$propName INT(11) ZEROFILL UNSIGNED,";
  1376.                     break;
  1377.                     default:
  1378.                         $sqlQuery .= "";
  1379.                     break;
  1380.                 }
  1381.             }            
  1382.         }
  1383.         if($this->isTableOverloaded())
  1384.             $sqlQuery .= "classname VARCHAR(100),";
  1385.         
  1386.         $sqlQuery .= "PRIMARY KEY (OID)) TYPE=InnoDB;";
  1387.         
  1388.         $this->lastQuery = $sqlQuery;
  1389.         
  1390.         if(!$result AlphaDAO::getConnection()->query($sqlQuery)) {
  1391.             throw new AlphaException('Failed to create the table ['.$this->getTableName().'] for the class ['.get_class($this).'], query is ['.$this->lastQuery.']');
  1392.             self::$logger->debug('<<makeTable');
  1393.         }
  1394.                 
  1395.         // check the table indexes if any additional ones required
  1396.         $this->checkIndexes();
  1397.         
  1398.         if(method_exists($this'after_makeTable_callback'))
  1399.             $this->after_makeTable_callback();
  1400.         
  1401.         self::$logger->info('Successfully created the table ['.$this->getTableName().'] for the class ['.get_class($this).']');
  1402.         
  1403.         self::$logger->debug('<<makeTable');
  1404.     }
  1405.  
  1406.     /**
  1407.      * Re-builds the table if the model requirements have changed.  All data is lost!
  1408.      * 
  1409.      * @since 1.0
  1410.      * @throws AlphaException
  1411.      */
  1412.     public function rebuildTable({
  1413.         self::$logger->debug('>>rebuildTable()');
  1414.         
  1415.         if(method_exists($this'before_rebuildTable_callback'))
  1416.             $this->before_rebuildTable_callback();
  1417.         
  1418.         $sqlQuery 'DROP TABLE IF EXISTS '.$this->getTableName().';';
  1419.  
  1420.         $this->lastQuery = $sqlQuery;
  1421.  
  1422.         if(!$result AlphaDAO::getConnection()->query($sqlQuery)) {
  1423.             throw new AlphaException('Failed to drop the table ['.$this->getTableName().'] for the class ['.get_class($this).'], query is ['.$this->lastQuery.']');
  1424.             self::$logger->debug('<<rebuildTable');
  1425.         }
  1426.  
  1427.         $this->makeTable();
  1428.         
  1429.         if(method_exists($this'after_rebuildTable_callback'))
  1430.             $this->after_rebuildTable_callback();            
  1431.         
  1432.         self::$logger->debug('<<rebuildTable');
  1433.     }
  1434.     
  1435.     /**
  1436.      * Drops the table if the model requirements have changed.  All data is lost!
  1437.      * 
  1438.      * @since 1.0
  1439.      * @param string $tableName Optional table name, leave blank for the defined table for this class to be dropped
  1440.      * @throws AlphaException
  1441.      */
  1442.     public function dropTable($tableName=null{
  1443.         self::$logger->debug('>>dropTable()');
  1444.         
  1445.         if(method_exists($this'before_dropTable_callback'))
  1446.             $this->before_dropTable_callback();
  1447.         
  1448.         if($tableName == null)
  1449.             $tableName $this->getTableName();
  1450.             
  1451.         $sqlQuery 'DROP TABLE IF EXISTS '.$tableName.';';
  1452.  
  1453.         $this->lastQuery = $sqlQuery;
  1454.  
  1455.         if(!$result AlphaDAO::getConnection()->query($sqlQuery)) {
  1456.             throw new AlphaException('Failed to drop the table ['.$tableName.'] for the class ['.get_class($this).'], query is ['.$this->lastQuery.']');
  1457.             self::$logger->debug('<<dropTable');
  1458.         }
  1459.         
  1460.         if(method_exists($this'after_dropTable_callback'))
  1461.             $this->after_dropTable_callback();            
  1462.         
  1463.         self::$logger->debug('<<dropTable');
  1464.     }
  1465.  
  1466.     /**
  1467.      * Adds in a new class property without loosing existing data (does an ALTER TABLE query on the
  1468.      * database).
  1469.      * 
  1470.      * @param string $propName The name of the new field to add to the database table.
  1471.      * @since 1.0
  1472.      * @throws AlphaException
  1473.      */
  1474.     public function addProperty($propName{
  1475.         self::$logger->debug('>>addProperty(propName=['.$propName.'])');
  1476.         
  1477.         if(method_exists($this'before_addProperty_callback'))
  1478.             $this->before_addProperty_callback();
  1479.             
  1480.         $sqlQuery 'ALTER TABLE '.$this->getTableName().' ADD ';
  1481.         
  1482.         if($this->isTableOverloaded(&& $propName == 'classname'{
  1483.             $sqlQuery .= 'classname VARCHAR(100)';
  1484.         }else{
  1485.             if(!in_array($propName$this->defaultAttributes&& !in_array($propName$this->transientAttributes)) {
  1486.                 $propClass get_class($this->getPropObject($propName));
  1487.     
  1488.                 switch (strtoupper($propClass)) {
  1489.                     case 'INTEGER':
  1490.                         $sqlQuery .= "$propName INT(".$this->getPropObject($propName)->getSize().")";
  1491.                     break;
  1492.                     case 'DOUBLE':
  1493.                         $sqlQuery .= "$propName DOUBLE(".$this->getPropObject($propName)->getSize(true).")";
  1494.                     break;
  1495.                     case 'STRING':
  1496.                         $sqlQuery .= "$propName VARCHAR(".$this->getPropObject($propName)->getSize().")";
  1497.                     break;
  1498.                     case 'SEQUENCE':
  1499.                         $sqlQuery .= "$propName VARCHAR(".$this->getPropObject($propName)->getSize().")";
  1500.                     break;
  1501.                     case 'TEXT':
  1502.                         $sqlQuery .= "$propName TEXT";
  1503.                     break;
  1504.                     case 'BOOLEAN':
  1505.                         $sqlQuery .= "$propName CHAR(1) DEFAULT '0'";
  1506.                     break;
  1507.                     case 'DATE':
  1508.                         $sqlQuery .= "$propName DATE";
  1509.                     break;
  1510.                     case 'TIMESTAMP':
  1511.                         $sqlQuery .= "$propName DATETIME";
  1512.                     break;
  1513.                     case 'ENUM':
  1514.                         $sqlQuery .= "$propName ENUM(";
  1515.                         $enumVals $this->getPropObject($propName)->getOptions();
  1516.                         foreach($enumVals as $val{
  1517.                             $sqlQuery .= "'".$val."',";
  1518.                         }
  1519.                         $sqlQuery rtrim($sqlQuery",");
  1520.                         $sqlQuery .= ')';
  1521.                     break;
  1522.                     case 'DENUM':
  1523.                         $tmp new DEnum(get_class($this).'::'.$propName);                        
  1524.                         $tmp->save();
  1525.                         $sqlQuery .= "$propName INT(11) ZEROFILL";
  1526.                     break;
  1527.                     case 'RELATION':                        
  1528.                         $sqlQuery .= "$propName INT(11) ZEROFILL UNSIGNED";
  1529.                     break;
  1530.                     default:
  1531.                         $sqlQuery .= '';
  1532.                     break;
  1533.                 }
  1534.             }
  1535.         }
  1536.  
  1537.         $this->lastQuery = $sqlQuery;
  1538.  
  1539.         if(!$result AlphaDAO::getConnection()->query($sqlQuery)) {
  1540.             throw new AlphaException('Failed to add the new attribute ['.$propName.'] to the table ['.$this->getTableName().'], query is ['.$this->lastQuery.']');
  1541.             self::$logger->debug('<<addProperty');
  1542.         }
  1543.  
  1544.         if(method_exists($this'after_addProperty_callback'))
  1545.             $this->after_addProperty_callback();            
  1546.  
  1547.         self::$logger->debug('<<addProperty');
  1548.     }
  1549.  
  1550.     /**
  1551.      * Populates the current business object from global POST data.
  1552.      * 
  1553.      * @since 1.0
  1554.      */    
  1555.     public function populateFromPost({
  1556.         self::$logger->debug('>>populateFromPost()');
  1557.         
  1558.         if(method_exists($this'before_populateFromPost_callback'))
  1559.             $this->before_populateFromPost_callback();
  1560.         
  1561.         // get the class attributes
  1562.         $reflection new ReflectionClass(get_class($this));
  1563.         $properties $reflection->getProperties();
  1564.         
  1565.         foreach($properties as $propObj{
  1566.             $propName $propObj->name;
  1567.  
  1568.             if(!in_array($propName$this->defaultAttributes&& !in_array($propName$this->transientAttributes)) {
  1569.                 $propClass get_class($this->getPropObject($propName));
  1570.                 
  1571.                 if(isset($_POST[$propName])) {
  1572.                     if (strtoupper($propClass!= 'DATE' && strtoupper($propClass!= 'TIMESTAMP'{
  1573.                         if(strtoupper($propClass== 'TEXT' && !$this->getPropObject($propName)->getAllowHTML())
  1574.                             $this->getPropObject($propName)->setValue(InputFilter::encode($_POST[$propName]false));
  1575.                         else
  1576.                             $this->getPropObject($propName)->setValue(InputFilter::encode($_POST[$propName]true));
  1577.                     }else{                        
  1578.                         $this->getPropObject($propName)->populateFromString(InputFilter::encode($_POST[$propName]true));
  1579.                     }                    
  1580.                 }
  1581.             }
  1582.             if ($propName == 'version_num' && isset($_POST['version_num']))
  1583.                 $this->version_num->setValue($_POST['version_num']);
  1584.         }
  1585.         if(method_exists($this'after_populateFromPost_callback'))
  1586.             $this->after_populateFromPost_callback();
  1587.         
  1588.         self::$logger->debug('<<populateFromPost');    
  1589.     }
  1590.  
  1591.     /**
  1592.      * Gets the maximum OID value from the database for this class type.
  1593.      * 
  1594.      * @return integer The maximum OID value in the class table.
  1595.      * @since 1.0
  1596.      * @throws AlphaException
  1597.      */
  1598.     public function getMAX({
  1599.         self::$logger->debug('>>getMAX()');
  1600.         
  1601.         if(method_exists($this'before_getMAX_callback'))
  1602.             $this->before_getMAX_callback();
  1603.         
  1604.         $sqlQuery 'SELECT MAX(OID) AS max_OID FROM '.$this->getTableName();
  1605.  
  1606.         $this->lastQuery = $sqlQuery;
  1607.  
  1608.         $result AlphaDAO::getConnection()->query($sqlQuery);    
  1609.  
  1610.         $row $result->fetch_array(MYSQLI_ASSOC);
  1611.  
  1612.         if (isset($row['max_OID'])) {
  1613.             if(method_exists($this'after_getMAX_callback'))
  1614.                 $this->after_getMAX_callback();
  1615.  
  1616.             self::$logger->debug('<<getMAX ['.$row['max_OID'].']');
  1617.             return $row['max_OID'];
  1618.         }else{            
  1619.             throw new AlphaException('Failed to get the MAX ID for the class ['.get_class($this).'] from the table ['.$this->getTableName().'], query is ['.$this->lastQuery.']');
  1620.             self::$logger->debug('<<getMAX [0]');
  1621.             return 0;
  1622.         }
  1623.     }
  1624.     
  1625.     /**
  1626.      * Gets the count from the database for the amount of objects of this class.
  1627.      * 
  1628.      * @param array $atributes The attributes to count the objects by (optional).
  1629.      * @param array $values The values of the attributes to count the objects by (optional).
  1630.      * @return integer 
  1631.      * @since 1.0
  1632.      * @throws AlphaException
  1633.      */
  1634.     public function getCount($attributes=array()$values=array()) {
  1635.         self::$logger->debug('>>getCount(attributes=['.var_export($attributestrue).'], values=['.var_export($valuestrue).'])');
  1636.         
  1637.         if(method_exists($this'before_getCount_callback'))
  1638.             $this->before_getCount_callback();
  1639.             
  1640.         if(!is_array($attributes|| !is_array($values)) {
  1641.             throw new IllegalArguementException('Illegal arrays attributes=['.var_export($attributestrue).'] and values=['.var_export($valuestrue).'] provided to loadAllByAttributes');
  1642.         }
  1643.         
  1644.         if($this->isTableOverloaded())
  1645.             $whereClause ' WHERE classname = \''.get_class($this).'\' AND';
  1646.         else
  1647.             $whereClause ' WHERE';
  1648.             
  1649.         $count count($attributes);
  1650.         
  1651.         for($i 0$i $count$i++{
  1652.             $whereClause .= ' '.$attributes[$i].' = \''.$values[$i].'\' AND';
  1653.             self::$logger->debug($whereClause);
  1654.         }
  1655.         // remove the last " AND"
  1656.         $whereClause substr($whereClause0-4);
  1657.         
  1658.         if($whereClause != ' WHERE')
  1659.             $sqlQuery 'SELECT COUNT(OID) AS class_count FROM '.$this->getTableName().$whereClause;
  1660.         else
  1661.             $sqlQuery 'SELECT COUNT(OID) AS class_count FROM '.$this->getTableName();
  1662.  
  1663.         $this->lastQuery = $sqlQuery;
  1664.         
  1665.         $result AlphaDAO::getConnection()->query($sqlQuery);
  1666.  
  1667.         if ($result{
  1668.             if(method_exists($this'after_getCount_callback'))
  1669.                 $this->after_getCount_callback();
  1670.                 
  1671.             $row $result->fetch_array(MYSQLI_ASSOC);
  1672.  
  1673.             self::$logger->debug('<<getCount ['.$row['class_count'].']');
  1674.             return $row['class_count'];
  1675.         }else{
  1676.             throw new AlphaException('Failed to get the count for the class ['.get_class($this).'] from the table ['.$this->getTableName().'], query is ['.$this->lastQuery.']');            
  1677.             self::$logger->debug('<<getCount [0]');
  1678.             return 0;
  1679.         }
  1680.     }
  1681.  
  1682.     /**
  1683.      * Gets the OID for the object in zero-padded format (same as getOID()).  This version is designed
  1684.      * to be overridden in case you want to do something different with the ID of your objects outside
  1685.      * of the standard 11 digit OID sequence used internally in Alpha.
  1686.      * 
  1687.      * @return integer 11 digit zero-padded OID value.
  1688.      * @since 1.0
  1689.      */
  1690.     public function getID({
  1691.         self::$logger->debug('>>getID()');
  1692.         $oid str_pad($this->OID11'0'STR_PAD_LEFT);
  1693.         self::$logger->debug('<<getID ['.$oid.']');
  1694.         return $oid;
  1695.     }
  1696.     
  1697.     /**
  1698.      * Gets the OID for the object in zero-padded format (same as getID()).  This version is final so cannot
  1699.      * be overridden.
  1700.      * 
  1701.      * @return integer 11 digit zero-padded OID value.
  1702.      * @since 1.0
  1703.      */
  1704.     public final function getOID({
  1705.         self::$logger->debug('>>getOID()');
  1706.         $oid str_pad($this->OID11'0'STR_PAD_LEFT);
  1707.         self::$logger->debug('<<getOID ['.$oid.']');
  1708.         return $oid;
  1709.     }
  1710.     
  1711.     /**
  1712.      * Method for getting version number of the object.
  1713.      * 
  1714.      * @return Integer The object version number.
  1715.      * @since 1.0
  1716.      */
  1717.     public function getVersionNumber({
  1718.         self::$logger->debug('>>getVersionNumber()');
  1719.         self::$logger->debug('<<getVersionNumber ['.$this->version_num.']');
  1720.         return $this->version_num;
  1721.     }
  1722.     
  1723.     /**
  1724.      * Populate all of the enum options for this object from the database.
  1725.      * 
  1726.      * @since 1.0
  1727.      * @throws AlphaException
  1728.      */
  1729.     protected function setEnumOptions({
  1730.         self::$logger->debug('>>setEnumOptions()');
  1731.         
  1732.         if(method_exists($this'before_setEnumOptions_callback'))
  1733.             $this->before_setEnumOptions_callback();
  1734.         
  1735.         // get the class attributes
  1736.         $reflection new ReflectionClass(get_class($this));
  1737.         $properties $reflection->getProperties();
  1738.         
  1739.         // flag for any database errors
  1740.         $dbError false;
  1741.         
  1742.         foreach($properties as $propObj{
  1743.             $propName $propObj->name;
  1744.             if(!in_array($propName$this->defaultAttributes&& !in_array($propName$this->transientAttributes)) {
  1745.                 $propClass get_class($this->getPropObject($propName));
  1746.                 if ($propClass == 'Enum'{
  1747.                     $sqlQuery "SHOW COLUMNS FROM ".$this->getTableName()." LIKE '$propName'";
  1748.                     
  1749.                     $this->lastQuery = $sqlQuery;
  1750.                     
  1751.                     $result AlphaDAO::getConnection()->query($sqlQuery);
  1752.                     
  1753.                     if ($result{                            
  1754.                         $row $result->fetch_array(MYSQLI_NUM);
  1755.                         $options explode("','",preg_replace("/(enum|set)\('(.+?)'\)/","\\2",$row[1]));
  1756.                         
  1757.                         $this->getPropObject($propName)->setOptions($options);                        
  1758.                     }else{
  1759.                         $dbError true;
  1760.                         break;
  1761.                     }
  1762.                 }
  1763.             }
  1764.         }
  1765.         
  1766.         if (!$dbError{
  1767.             if(method_exists($this'after_setEnumOptions_callback'))
  1768.                 $this->after_setEnumOptions_callback();
  1769.         }else{            
  1770.             throw new AlphaException('Failed to load enum options correctly for object instance of class ['.get_class($this).']');
  1771.         }
  1772.         self::$logger->debug('<<setEnumOptions');
  1773.     }
  1774.     
  1775.     /**
  1776.      * Generic getter method for accessing class properties.  Will use the method get.ucfirst($prop) instead if that
  1777.      * method exists at a child level (by default).  Set $noChildMethods to true if you don't want to use any
  1778.      * get.ucfirst($prop) method even if it exists, false otherwise (default).
  1779.      * 
  1780.      * @param string $prop The name of the object property to get.
  1781.      * @param boolean $noChildMethods Set to true if you do not want to use getters in the child object, defaults
  1782.      *  to false.
  1783.      * @return mixed The property value.
  1784.      * @since 1.0
  1785.      * @throws IllegalArguementException
  1786.      * @throws AlphaException
  1787.      */
  1788.     public function get($prop$noChildMethods false{
  1789.         if(self::$logger == null)
  1790.             self::$logger new Logger('AlphaDAO');
  1791.             
  1792.         self::$logger->debug('>>get(prop=['.$prop.'], noChildMethods=['.$noChildMethods.'])');
  1793.         
  1794.         if(method_exists($this'before_get_callback'))
  1795.             $this->before_get_callback();
  1796.             
  1797.         if(empty($prop))
  1798.             throw new IllegalArguementException('Cannot call get with empty $prop arguement!');
  1799.         
  1800.         // handle attributes with a get.ucfirst($prop) method
  1801.         if(!$noChildMethods && method_exists($this'get'.ucfirst($prop))) {
  1802.             if(method_exists($this'after_get_callback'))
  1803.                 $this->after_get_callback();
  1804.  
  1805.             self::$logger->debug('<<get ['.eval('return $this->get'.ucfirst($prop).'();').'])');
  1806.             return eval('return $this->get'.ucfirst($prop).'();');
  1807.         }else{
  1808.             // handle attributes with no dedicated child get.ucfirst($prop) method
  1809.             if(isset($this->$prop&& method_exists($this->$prop'getValue')) {
  1810.                 if(method_exists($this'after_get_callback'))
  1811.                     $this->after_get_callback();
  1812.  
  1813.                 // complex types will have a getValue() method, return the value of that
  1814.                 self::$logger->debug('<<get ['.$this->$prop->getValue().'])');
  1815.                 return $this->$prop->getValue();
  1816.             }elseif(isset($this->$prop)) {
  1817.                 if(method_exists($this'after_get_callback'))
  1818.                     $this->after_get_callback();
  1819.  
  1820.                 // simple types returned as-is
  1821.                 self::$logger->debug('<<get ['.$this->$prop.'])');
  1822.                 return $this->$prop;
  1823.             }else{
  1824.                 throw new AlphaException('Could not access the property ['.$prop.'] on the object of class ['.get_class($this).']');
  1825.                 self::$logger->debug('<<get [false])');
  1826.                 return false;
  1827.             }
  1828.         }
  1829.     }
  1830.     
  1831.     /**
  1832.      * Generic setter method for setting class properties.  Will use the method set.ucfirst($prop) instead if that
  1833.      * method exists at a child level (by default).  Set $noChildMethods to true if you don't want to use
  1834.      * any get.ucfirst($prop) method even if it exists, false otherwise (default).
  1835.      * 
  1836.      * @param string $prop The name of the property to set.
  1837.      * @param mixed $value The value of the property to set.
  1838.      * @param boolean $noChildMethods Set to true if you do not want to use setters in the child object, defaults
  1839.      *  to false.
  1840.      * @since 1.0
  1841.      * @throws AlphaException
  1842.      */
  1843.     public function set($prop$value$noChildMethods false{
  1844.         self::$logger->debug('>>set(prop=['.$prop.'], $value=['.$value.'], noChildMethods=['.$noChildMethods.'])');
  1845.         
  1846.         if(method_exists($this'before_set_callback'))
  1847.             $this->before_set_callback();
  1848.         
  1849.         // handle attributes with a set.ucfirst($prop) method
  1850.         if(!$noChildMethods && method_exists($this'set'.ucfirst($prop))) {
  1851.             if(method_exists($this'after_set_callback'))
  1852.                 $this->after_set_callback();
  1853.             
  1854.             eval('$this->set'.ucfirst($prop)."('$value');");
  1855.         }else{
  1856.             // handle attributes with no dedicated child set.ucfirst($prop) method
  1857.             if(isset($this->$prop)) {
  1858.                 if(method_exists($this'after_set_callback'))
  1859.                     $this->after_set_callback();
  1860.                 
  1861.                 // complex types will have a setValue() method to call
  1862.                 if (get_class($this->$prop!= false{                
  1863.                     if (strtoupper(get_class($this->$prop)) != 'DATE' && strtoupper(get_class($this->$prop)) != 'TIMESTAMP'{                    
  1864.                         $this->$prop->setValue($value);
  1865.                     }else{
  1866.                         // Date and Timestamp objects have a special setter accepting a string
  1867.                         $this->$prop->populateFromString($value);
  1868.                     }
  1869.                 }else{
  1870.                     // simple types set directly
  1871.                     $this->$prop $value;
  1872.                 }                
  1873.             }else{
  1874.                 throw new AlphaException('Could not set the property ['.$prop.'] on the object of the class ['.get_class($this).'].  Property may not exist, or else does not have a setValue() method and is private or protected.');
  1875.             }
  1876.         }
  1877.         self::$logger->debug('<<set');
  1878.     }
  1879.     
  1880.     /**
  1881.      * Gets the property object rather than the value for complex attributes.  Returns false if
  1882.      * the property exists but is private.
  1883.      * 
  1884.      * @param string $prop The name of the property we are getting.
  1885.      * @return AlphaType The complex type object found.
  1886.      * @since 1.0
  1887.      * @throws IllegalArguementException
  1888.      */
  1889.     public function getPropObject($prop{
  1890.         self::$logger->debug('>>getPropObject(prop=['.$prop.'])');
  1891.         
  1892.         if(method_exists($this'before_getPropObject_callback'))
  1893.             $this->before_getPropObject_callback();
  1894.             
  1895.         // get the class attributes
  1896.         $reflection new ReflectionClass(get_class($this));
  1897.         $properties $reflection->getProperties();
  1898.         
  1899.         // firstly, check for private
  1900.         $attribute new ReflectionProperty(get_class($this)$prop);
  1901.         
  1902.         if($attribute->isPrivate()) {
  1903.             if(method_exists($this'after_getPropObject_callback'))
  1904.                 $this->after_getPropObject_callback();
  1905.                 
  1906.             self::$logger->debug('<<getPropObject [false]');
  1907.             return false;
  1908.         }
  1909.         
  1910.         foreach($properties as $propObj{
  1911.             $propName $propObj->name;
  1912.         
  1913.             if($prop == $propName{
  1914.                 if(method_exists($this'after_getPropObject_callback'))
  1915.                     $this->after_getPropObject_callback();
  1916.                 
  1917.                 self::$logger->debug('<<getPropObject ['.var_export($this->$proptrue).']');
  1918.                 return $this->$prop;                
  1919.             }
  1920.         }        
  1921.         throw new IllegalArguementException('Could not access the property object ['.$prop.'] on the object of class ['.get_class($this).']');
  1922.         self::$logger->debug('<<getPropObject [false]');
  1923.         return false;
  1924.     }
  1925.     
  1926.     /**
  1927.      * Checks to see if the table exists in the database for the current business class.
  1928.      * 
  1929.      * @return boolean 
  1930.      * @since 1.0
  1931.      * @throws AlphaException
  1932.      */
  1933.     public function checkTableExists({
  1934.         self::$logger->debug('>>checkTableExists()');
  1935.         
  1936.         if(method_exists($this'before_checkTableExists_callback'))
  1937.             $this->before_checkTableExists_callback();
  1938.             
  1939.         global $config;
  1940.                 
  1941.         $tableExists false;
  1942.         
  1943.         $sqlQuery 'SHOW TABLES;';
  1944.         $this->lastQuery = $sqlQuery;
  1945.         
  1946.         $result AlphaDAO::getConnection()->query($sqlQuery);
  1947.         
  1948.         while ($row $result->fetch_array(MYSQLI_NUM)) {
  1949.             if (strtolower($row[0]== strtolower($this->getTableName()))
  1950.                 $tableExists true;
  1951.         }        
  1952.         
  1953.         if ($result{
  1954.             if(method_exists($this'after_checkTableExists_callback'))
  1955.                 $this->after_checkTableExists_callback();
  1956.             
  1957.             self::$logger->debug('<<checkTableExists ['.$tableExists.']');
  1958.             return $tableExists;
  1959.         }else{            
  1960.             throw new AlphaException('Failed to access the system database correctly, error is ['.AlphaDAO::getConnection()->error.']');
  1961.             self::$logger->debug('<<checkTableExists [false]');
  1962.             return false;
  1963.         }
  1964.     }
  1965.     
  1966.     /**
  1967.      * Static method to check the database and see if the table for the indicated BO class name
  1968.      * exists (assumes table name will be $BOClassName less "Object").
  1969.      * 
  1970.      * @param string $BOClassName The name of the business object class we are checking.
  1971.      * @return boolean 
  1972.      * @since 1.0
  1973.      * @throws AlphaException
  1974.      */
  1975.     public static function checkBOTableExists($BOClassName{
  1976.         if(self::$logger == null)
  1977.             self::$logger new Logger('AlphaDAO');
  1978.         self::$logger->debug('>>checkBOTableExists(BOClassName=['.$BOClassName.'])');
  1979.         
  1980.         global $config;
  1981.         
  1982.         eval('$tableName = '.$BOClassName.'::TABLE_NAME;');
  1983.         
  1984.         if(empty($tableName))
  1985.             $tableName substr($BOClassName0strpos($BOClassName'_'));        
  1986.                 
  1987.         $tableExists false;
  1988.         
  1989.         $sqlQuery 'SHOW TABLES;';
  1990.         
  1991.         $result AlphaDAO::getConnection()->query($sqlQuery);
  1992.         
  1993.         while ($row $result->fetch_array(MYSQLI_NUM)) {            
  1994.             if ($row[0== $tableName)
  1995.                 $tableExists true;
  1996.         }        
  1997.         
  1998.         if ($result{            
  1999.             self::$logger->debug('<<checkBOTableExists ['.($tableExists 'true' 'false').']');
  2000.             return $tableExists;
  2001.         }else{            
  2002.             throw new AlphaException('Failed to access the system database correctly, error is ['.AlphaDAO::getConnection()->error.']');
  2003.             self::$logger->debug('<<checkBOTableExists [false]');
  2004.             return false;
  2005.         }
  2006.     }
  2007.     
  2008.     /**
  2009.      * Checks to see if the table in the database matches (for fields) the business class definition, i.e. if the
  2010.      * database table is in sync with the class definition.
  2011.      * 
  2012.      * @return boolean 
  2013.      * @since 1.0
  2014.      * @throws AlphaException
  2015.      */
  2016.     public function checkTableNeedsUpdate({
  2017.         self::$logger->debug('>>checkTableNeedsUpdate()');
  2018.                 
  2019.         if(method_exists($this'before_checkTableNeedsUpdate_callback'))
  2020.             $this->before_checkTableNeedsUpdate_callback();
  2021.         
  2022.         $tableExists $this->checkTableExists();
  2023.         
  2024.         if (!$tableExists{
  2025.             self::$logger->debug('<<checkTableNeedsUpdate [true]');
  2026.             return true;
  2027.         }else{
  2028.             $updateRequired false;
  2029.             
  2030.             $matchCount 0;
  2031.             
  2032.             $query 'SHOW COLUMNS FROM '.$this->getTableName();
  2033.             $result AlphaDAO::getConnection()->query($query);
  2034.             $this->lastQuery = $query;
  2035.             
  2036.             // get the class attributes
  2037.             $reflection new ReflectionClass(get_class($this));
  2038.             $properties $reflection->getProperties();
  2039.             
  2040.             foreach($properties as $propObj{
  2041.                 $propName $propObj->name;
  2042.                 if (!in_array($propName$this->transientAttributes)) {
  2043.                 
  2044.                     $foundMatch false;    
  2045.                     
  2046.                     while ($row $result->fetch_array(MYSQLI_ASSOC)) {                        
  2047.                         if ($propName == $row['Field']{
  2048.                             $foundMatch true;
  2049.                             break;
  2050.                         }
  2051.                     }
  2052.                     
  2053.                     if(!$foundMatch)
  2054.                         $matchCount--;                        
  2055.                     
  2056.                     $result->data_seek(0);                    
  2057.                 }
  2058.             }
  2059.             
  2060.             // check for the "classname" field in overloaded tables
  2061.             if($this->isTableOverloaded()) {
  2062.                 $foundMatch false;
  2063.             
  2064.                 while ($row $result->fetch_array(MYSQLI_ASSOC)) {
  2065.                     if ('classname' == $row['Field']{
  2066.                         $foundMatch true;
  2067.                         break;
  2068.                     }
  2069.                 }
  2070.                 if(!$foundMatch)                        
  2071.                     $matchCount--;
  2072.             }
  2073.             
  2074.             if ($matchCount != 0)
  2075.                 $updateRequired true;
  2076.             
  2077.             if ($result{
  2078.                 if(method_exists($this'before_checkTableNeedsUpdate_callback'))
  2079.                     $this->before_checkTableNeedsUpdate_callback();
  2080.                 
  2081.                 // check the table indexes
  2082.                 try {
  2083.                     $this->checkIndexes();
  2084.                 }catch (AlphaException $ae{
  2085.                     self::$logger->warn("Error while checking database indexes:\n\n".$ae->getMessage());
  2086.                 }
  2087.                 
  2088.                 self::$logger->debug('<<checkTableNeedsUpdate ['.$updateRequired.']');
  2089.                 return $updateRequired;
  2090.             }else{                
  2091.                 throw new AlphaException('Failed to access the system database correctly, error is ['.AlphaDAO::getConnection()->error.']');
  2092.                 self::$logger->debug('<<checkTableNeedsUpdate [false]');
  2093.                 return false;
  2094.             }
  2095.         }
  2096.     }
  2097.     
  2098.     /**
  2099.      * Returns an array containing any properties on the class which have not been created on the database
  2100.      * table yet.
  2101.      * 
  2102.      * @return array An array of missing fields in the database table.
  2103.      * @since 1.0
  2104.      * @throws AlphaException
  2105.      */
  2106.     public function findMissingFields({
  2107.         self::$logger->debug('>>findMissingFields()');
  2108.         
  2109.         if(method_exists($this'before_findMissingFields_callback'))
  2110.             $this->before_findMissingFields_callback();
  2111.         
  2112.         $missingFields array();            
  2113.         $matchCount 0;
  2114.             
  2115.         $sqlQuery 'SHOW COLUMNS FROM '.$this->getTableName();
  2116.         
  2117.         $result AlphaDAO::getConnection()->query($sqlQuery);
  2118.         
  2119.         $this->lastQuery = $sqlQuery;
  2120.             
  2121.         // get the class attributes
  2122.         $reflection new ReflectionClass(get_class($this));
  2123.         $properties $reflection->getProperties();
  2124.         
  2125.         foreach($properties as $propObj{
  2126.             $propName $propObj->name;
  2127.             if (!in_array($propName$this->transientAttributes)) {
  2128.                 while ($row $result->fetch_array(MYSQLI_ASSOC)) {
  2129.                     if ($propName == $row['Field']{                        
  2130.                         $matchCount++;                        
  2131.                         break;
  2132.                     }                
  2133.                 }                
  2134.                 $result->data_seek(0);
  2135.             }else{
  2136.                 $matchCount++;
  2137.             }
  2138.             
  2139.             if($matchCount==0{                    
  2140.                 array_push($missingFields$propName);
  2141.             }else{
  2142.                 $matchCount 0;
  2143.             }
  2144.         }
  2145.         
  2146.         // check for the "classname" field in overloaded tables
  2147.         if($this->isTableOverloaded()) {
  2148.             $foundMatch false;
  2149.             
  2150.             while ($row $result->fetch_array(MYSQLI_ASSOC)) {
  2151.                 if ('classname' == $row['Field']{
  2152.                     $foundMatch true;
  2153.                     break;
  2154.                 }
  2155.             }
  2156.             if(!$foundMatch)                        
  2157.                 array_push($missingFields'classname');
  2158.         }
  2159.         
  2160.         if (!$result{            
  2161.             throw new AlphaException('Failed to access the system database correctly, error is ['.AlphaDAO::getConnection()->error.']');
  2162.         }
  2163.         
  2164.         if(method_exists($this'after_findMissingFields_callback'))
  2165.             $this->after_findMissingFields_callback();
  2166.         
  2167.         self::$logger->debug('<<findMissingFields ['.var_export($missingFieldstrue).']');
  2168.         return $missingFields;    
  2169.     }
  2170.     
  2171.     /**
  2172.      * Getter for the TABLE_NAME, which should be set by a child of this class.
  2173.      * 
  2174.      * @return string The table name in the database.
  2175.      * @since 1.0
  2176.      * @throws AlphaException
  2177.      */
  2178.     public function getTableName({
  2179.         self::$logger->debug('>>getTableName()');
  2180.         
  2181.         eval('$TABLE_NAME = '.get_class($this).'::TABLE_NAME;');
  2182.         
  2183.         if(!empty($TABLE_NAME)) {
  2184.             self::$logger->debug('<<getTableName ['.$TABLE_NAME.']');
  2185.             return $TABLE_NAME;        
  2186.         }else{
  2187.             throw new AlphaException('Error: no TABLE_NAME constant set for the class '.get_class($this));
  2188.             self::$logger->debug('<<getTableName []');        
  2189.             return '';
  2190.         }
  2191.     }
  2192.     
  2193.     /**
  2194.      * Method for getting the OID of the person who created this BO.
  2195.      * 
  2196.      * @return Integer The OID of the creator.
  2197.      * @since 1.0
  2198.      */
  2199.     public function getCreatorId({
  2200.         self::$logger->debug('>>getCreatorId()');
  2201.         self::$logger->debug('<<getCreatorId ['.$this->created_by.']');
  2202.         return $this->created_by;
  2203.     }
  2204.     
  2205.     /**
  2206.      * Method for getting the OID of the person who updated this BO.
  2207.      * 
  2208.      * @return Integer The OID of the updator.
  2209.      * @since 1.0
  2210.      */
  2211.     public function getUpdatorId({
  2212.         self::$logger->debug('>>getUpdatorId()');
  2213.         self::$logger->debug('<<getUpdatorId ['.$this->updated_by.']');
  2214.         return $this->updated_by;
  2215.     }
  2216.     
  2217.     /**
  2218.      * Method for getting the date/time of when the BO was created.
  2219.      * 
  2220.      * @return Timestamp 
  2221.      * @since 1.0
  2222.      */
  2223.     public function getCreateTS({
  2224.         self::$logger->debug('>>getCreateTS()');
  2225.         self::$logger->debug('<<getCreateTS ['.$this->created_ts.']');
  2226.         return $this->created_ts;
  2227.     }
  2228.     
  2229.     /**
  2230.      * Method for getting the date/time of when the BO was last updated.
  2231.      * 
  2232.      * @return Timestamp 
  2233.      * @since 1.0
  2234.      */
  2235.     public function getUpdateTS({
  2236.         self::$logger->debug('>>getUpdateTS()');
  2237.         self::$logger->debug('<<getUpdateTS ['.$this->updated_ts.']');
  2238.         return $this->updated_ts;
  2239.     }
  2240.     
  2241.     /**
  2242.      * Adds the name of the attribute provided to the list of transient (non-saved) attributes for this BO.
  2243.      * 
  2244.      * @param string $attributeName The name of the attribute to not save.
  2245.      * @since 1.0
  2246.      */
  2247.     public function markTransient($attributeName{
  2248.         self::$logger->debug('>>markTransient(attributeName=['.$attributeName.'])');
  2249.         self::$logger->debug('<<markTransient');
  2250.         array_push($this->transientAttributes$attributeName);        
  2251.     }
  2252.     
  2253.     /**
  2254.      * Removes the name of the attribute provided from the list of transient (non-saved) attributes for this BO,
  2255.      * ensuring that it will be saved on the next attempt.
  2256.      * 
  2257.      * @param string $attributeName The name of the attribute to save.
  2258.      * @since 1.0
  2259.      */
  2260.     public function markPersistent($attributeName{
  2261.         self::$logger->debug('>>markPersistent(attributeName=['.$attributeName.'])');
  2262.         self::$logger->debug('<<markPersistent');
  2263.         $this->transientAttributes = array_diff($this->transientAttributesarray($attributeName));
  2264.     }
  2265.     
  2266.     /**
  2267.      * Adds the name of the attribute(s) provided to the list of unique (constrained) attributes for this BO.
  2268.      * 
  2269.      * @param string $attribute1Name The first attribute to mark unique in the database.
  2270.      * @param string $attribute2Name The second attribute to mark unique in the databse (optional, use only for composite keys).
  2271.      * @since 1.0
  2272.      */
  2273.     protected function markUnique($attribute1Name$attribute2Name=''{
  2274.         self::$logger->debug('>>markUnique(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'])');
  2275.         if(empty($attribute2Name)) {
  2276.             array_push($this->uniqueAttributes$attribute1Name);
  2277.         }else{
  2278.             // Process composite unique keys: add them seperated by a + sign
  2279.             $attributes $attribute1Name.'+'.$attribute2Name;
  2280.             array_push($this->uniqueAttributes$attributes);
  2281.         }
  2282.         self::$logger->debug('<<markUnique');
  2283.     }
  2284.     
  2285.     /**
  2286.      * Gets an array of all of the names of the active database indexes for this class.
  2287.      *
  2288.      * @return array An array of database indexes on this table.
  2289.      * @since 1.0
  2290.      * @throws AlphaException
  2291.      */
  2292.     protected function getIndexes({
  2293.         self::$logger->debug('>>getIndexes()');
  2294.         
  2295.         $query 'SHOW INDEX FROM '.$this->getTableName();
  2296.         
  2297.         $result AlphaDAO::getConnection()->query($query);
  2298.         
  2299.         $this->lastQuery = $query;
  2300.         
  2301.         $indexNames array();
  2302.         
  2303.         if (!$result{
  2304.             throw new AlphaException('Failed to access the system database correctly, error is ['.AlphaDAO::getConnection()->error.']');
  2305.         }else{            
  2306.             while ($row $result->fetch_array(MYSQLI_ASSOC)) {                
  2307.                 array_push($indexNames$row['Key_name']);
  2308.             }
  2309.         }
  2310.         
  2311.         self::$logger->debug('<<getIndexes');
  2312.         return $indexNames;
  2313.     }
  2314.     
  2315.     /**
  2316.      * Checks to see if all of the indexes are in place for the BO's table, creates those that are missing.
  2317.      * 
  2318.      * @since 1.0
  2319.      */
  2320.     protected function checkIndexes({
  2321.         self::$logger->debug('>>checkIndexes()');        
  2322.         
  2323.         if(method_exists($this'before_checkIndexes_callback'))
  2324.             $this->before_checkIndexes_callback();
  2325.         
  2326.         $indexNames $this->getIndexes();        
  2327.         
  2328.         // process unique keys
  2329.         foreach($this->uniqueAttributes as $prop{
  2330.             // check for composite indexes
  2331.             if(strpos($prop'+')) {
  2332.                 $attributes explode('+'$prop);
  2333.                 
  2334.                 $index_exists false;
  2335.                 foreach ($indexNames as $index{
  2336.                     if ($attributes[0].'_'.$attributes[1].'_unq_idx' == $index{
  2337.                         $index_exists true;
  2338.                     }
  2339.                 }
  2340.     
  2341.                 if(!$index_exists)
  2342.                     $this->createUniqueIndex($attributes[0]$attributes[1]);
  2343.             }else{
  2344.                 $index_exists false;
  2345.                 foreach ($indexNames as $index{                    
  2346.                     if ($prop.'_unq_idx' == $index{
  2347.                         $index_exists true;
  2348.                     }
  2349.                 }
  2350.     
  2351.                 if(!$index_exists)
  2352.                     $this->createUniqueIndex($prop);
  2353.             }
  2354.         }        
  2355.         
  2356.         // process foreign-key indexes
  2357.         // get the class attributes
  2358.         $reflection new ReflectionClass(get_class($this));
  2359.         $properties $reflection->getProperties();
  2360.         
  2361.         foreach($properties as $propObj{
  2362.             $propName $propObj->name;            
  2363.             $prop $this->getPropObject($propName);
  2364.             if(get_class($prop== 'Relation'{
  2365.                                 
  2366.                 if($prop->getRelationType(== 'MANY-TO-ONE'{
  2367.                     $indexExists false;
  2368.                     foreach ($indexNames as $index{
  2369.                         if ($propName.'_fk_idx' == $index{
  2370.                             $indexExists true;
  2371.                         }
  2372.                     }
  2373.         
  2374.                     if(!$indexExists{
  2375.                         $this->createForeignIndex($propName$prop->getRelatedClass()$prop->getRelatedClassField());
  2376.                     }
  2377.                 }
  2378.                 
  2379.                 if($prop->getRelationType(== 'MANY-TO-MANY'{                    
  2380.                     $lookup $prop->getLookup();
  2381.                     
  2382.                     if($lookup != null{
  2383.                         try {                    
  2384.                             $lookupIndexNames $lookup->getIndexes();
  2385.                             
  2386.                             // handle index check/creation on left side of Relation
  2387.                             $indexExists false;
  2388.                             foreach ($lookupIndexNames as $index{
  2389.                                 if ('leftID_fk_idx' == $index{
  2390.                                     $indexExists true;
  2391.                                 }
  2392.                             }
  2393.                             
  2394.                             if(!$indexExists{
  2395.                                 $lookup->createForeignIndex('leftID'$prop->getRelatedClass('left')'OID');
  2396.                             }
  2397.                             
  2398.                             // handle index check/creation on right side of Relation
  2399.                             $indexExists false;
  2400.                             foreach ($lookupIndexNames as $index{
  2401.                                 if ('rightID_fk_idx' == $index{
  2402.                                     $indexExists true;
  2403.                                 }
  2404.                             }
  2405.                             
  2406.                             if(!$indexExists{
  2407.                                 $lookup->createForeignIndex('rightID'$prop->getRelatedClass('right')'OID');
  2408.                             }
  2409.                         }catch(AlphaException $e{
  2410.                             self::$logger->error($e->getMessage());
  2411.                         }
  2412.                     }
  2413.                 }
  2414.                 
  2415.             }
  2416.         }
  2417.         
  2418.         if(method_exists($this'after_checkIndexes_callback'))
  2419.             $this->after_checkIndexes_callback();
  2420.         
  2421.         self::$logger->debug('<<checkIndexes');
  2422.     }
  2423.     
  2424.     /**
  2425.      * Creates a foreign key constraint (index) in the database on the given attribute.
  2426.      * 
  2427.      * @param string $attributeName The name of the attribute to apply the index on.
  2428.      * @param string $relatedClass The name of the related class in the format "NameObject".
  2429.      * @param string $relatedClassAttribute The name of the field to relate to on the related class.
  2430.      * @param bool $allowNullValues For foreign key indexes that don't allow null values, set this to false (default is true).
  2431.      * @since 1.0
  2432.      * @throws FailedIndexCreateException
  2433.      */
  2434.     protected function createForeignIndex($attributeName$relatedClass$relatedClassAttribute{
  2435.         self::$logger->debug('>>createForeignIndex(attributeName=['.$attributeName.'], relatedClass=['.$relatedClass.'], relatedClassAttribute=['.$relatedClassAttribute.']');
  2436.         
  2437.         if(method_exists($this'before_createForeignIndex_callback'))
  2438.             $this->before_createForeignIndex_callback();
  2439.         
  2440.         AlphaDAO::loadClassDef($relatedClass);        
  2441.         $relatedBO new $relatedClass;        
  2442.         $tableName $relatedBO->getTableName();
  2443.  
  2444.         // if the relation is on itself (table-wise), exist without attempting to create the foreign keys
  2445.         if($this->getTableName(== $tableName{
  2446.             self::$logger->debug('<<createForeignIndex');
  2447.             return;
  2448.         }
  2449.  
  2450.         $result false;
  2451.         
  2452.         if(AlphaDAO::checkBOTableExists(ucfirst($tableName).'Object')) {
  2453.             $sqlQuery '';
  2454.             if($attributeName == 'leftID')
  2455.                 $sqlQuery 'ALTER TABLE '.$this->getTableName().' ADD INDEX leftID_fk_idx (leftID);';
  2456.             if($attributeName == 'rightID')
  2457.                 $sqlQuery 'ALTER TABLE '.$this->getTableName().' ADD INDEX rightID_fk_idx (rightID);';
  2458.                 
  2459.             if(!empty($sqlQuery)) {
  2460.                 $this->lastQuery = $sqlQuery;
  2461.     
  2462.                 $result AlphaDAO::getConnection()->query($sqlQuery);
  2463.     
  2464.                 if (!$result{
  2465.                     throw new AlphaException('Failed to create an index on ['.$this->getTableName().'], error is ['.AlphaDAO::getConnection()->error.'], query ['.$this->lastQuery.']');
  2466.                 }
  2467.             }
  2468.  
  2469.             $sqlQuery 'ALTER TABLE '.$this->getTableName().' ADD FOREIGN KEY '.$attributeName.'_fk_idx ('.$attributeName.') REFERENCES '.$tableName.' ('.$relatedClassAttribute.') ON DELETE SET NULL;';
  2470.             
  2471.             $this->lastQuery = $sqlQuery;    
  2472.             $result AlphaDAO::getConnection()->query($sqlQuery);
  2473.         }
  2474.     
  2475.         if ($result{
  2476.             if(method_exists($this'after_createForeignIndex_callback'))
  2477.                 $this->after_createForeignIndex_callback();
  2478.             self::$logger->debug('Successfully created the foreign key index ['.$attributeName.'_fk_idx]');
  2479.         }else{            
  2480.             throw new FailedIndexCreateException('Failed to create the index ['.$attributeName.'_fk_idx] on ['.$this->getTableName().'], error is ['.AlphaDAO::getConnection()->error.'], query ['.$this->lastQuery.']');
  2481.         }        
  2482.         
  2483.         self::$logger->debug('<<createForeignIndex');
  2484.     }
  2485.     
  2486.     /**
  2487.      * Creates a unique index in the database on the given attribute(s).
  2488.      * 
  2489.      * @param string $attribute1Name The first attribute to mark unique in the database.
  2490.      * @param string $attribute2Name The second attribute to mark unique in the databse (optional, use only for composite keys).
  2491.      * @since 1.0
  2492.      * @throws FailedIndexCreateException
  2493.      */
  2494.     protected function createUniqueIndex($attribute1Name$attribute2Name ''{
  2495.         self::$logger->debug('>>createUniqueIndex(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'])');
  2496.         
  2497.         if(method_exists($this'before_createUniqueIndex_callback'))
  2498.             $this->before_createUniqueIndex_callback();
  2499.         
  2500.         if(empty($attribute2Name)) {
  2501.             $sqlQuery 'CREATE UNIQUE INDEX '.$attribute1Name.'_unq_idx ON '.$this->getTableName().' ('.$attribute1Name.');';
  2502.             
  2503.             $this->lastQuery = $sqlQuery;
  2504.     
  2505.             $result AlphaDAO::getConnection()->query($sqlQuery);
  2506.     
  2507.             if ($result{
  2508.                 if(method_exists($this'after_createUniqueIndex_callback'))
  2509.                     $this->after_createUniqueIndex_callback();
  2510.             }else{            
  2511.                 throw new FailedIndexCreateException('Failed to create the index ['.$attribute1Name.'_unq_idx] on ['.$this->getTableName().'], error is ['.AlphaDAO::getConnection()->error.']');
  2512.             }
  2513.         }else{
  2514.             // process composite unique keys
  2515.             $sqlQuery 'CREATE UNIQUE INDEX '.$attribute1Name.'_'.$attribute2Name.'_unq_idx ON '.$this->getTableName().' ('.$attribute1Name.','.$attribute2Name.');';
  2516.             
  2517.             $this->lastQuery = $sqlQuery;
  2518.     
  2519.             $result AlphaDAO::getConnection()->query($sqlQuery);
  2520.     
  2521.             if ($result{
  2522.                 if(method_exists($this'after_create_unique_index_callback'))
  2523.                     $this->after_createUniqueIndex_callback();
  2524.             }else{            
  2525.                 throw new FailedIndexCreateException('Failed to create the index ['.$attribute1Name.'_'.$attribute2Name.'_unq_idx] on ['.$this->getTableName().'], error is ['.AlphaDAO::getConnection()->error.']');
  2526.             }
  2527.         }
  2528.         
  2529.         self::$logger->debug('<<createUniqueIndex');
  2530.     }
  2531.     
  2532.     /**
  2533.      * Parses a MySQL error for the value that violated a unique constraint.
  2534.      * 
  2535.      * @param string $error The MySQL error string.
  2536.      * @since 1.0
  2537.      */
  2538.     protected function findOffendingValue($error{
  2539.         self::$logger->debug('>>findOffendingValue(error=['.$error.'])');
  2540.                 
  2541.         $singleQuote1 strpos($error,"'");
  2542.         $singleQuote2 strrpos($error,"'");
  2543.         
  2544.         $value substr($error$singleQuote1($singleQuote2-$singleQuote1)+1);
  2545.         self::$logger->debug('<<findOffendingValue ['.$value.'])');
  2546.         return $value;
  2547.     }
  2548.     
  2549.     /**
  2550.      * Gets the data labels array.
  2551.      * 
  2552.      * @return array An array of attribute labels.
  2553.      * @since 1.0
  2554.      */
  2555.     public function getDataLabels({
  2556.         self::$logger->debug('>>getDataLabels()');
  2557.         self::$logger->debug('<<getDataLabels() ['.var_export($this->dataLabelstrue).'])');        
  2558.         return $this->dataLabels;
  2559.     }
  2560.     
  2561.     /**
  2562.      * Gets the data label for the given attribute name.
  2563.      * 
  2564.      * @param $att The attribute name to get the label for.
  2565.      * @return string 
  2566.      * @since 1.0
  2567.      * @throws IllegalArguementException
  2568.      */
  2569.     public function getDataLabel($att{
  2570.         self::$logger->debug('>>getDataLabel(att=['.$att.'])');
  2571.         
  2572.         if(in_array($attarray_keys($this->dataLabels))) {
  2573.             self::$logger->debug('<<getDataLabel ['.$this->dataLabels[$att].'])');        
  2574.             return $this->dataLabels[$att];
  2575.         }else{
  2576.             throw new IllegalArguementException('No data label found on the class ['.get_class($this).'] for the attribute ['.$att.']');
  2577.             self::$logger->debug('<<getDataLabel [])');
  2578.             return '';
  2579.         }
  2580.     }
  2581.     
  2582.     /**
  2583.      * Loops over the core and custom BO directories and builds an array of all of the BO class names in the system.
  2584.      *
  2585.      * @return array An array of business object class names.
  2586.      * @since 1.0
  2587.      */
  2588.     public static function getBOClassNames({
  2589.         if(self::$logger == null)
  2590.             self::$logger new Logger('AlphaDAO');
  2591.         self::$logger->debug('>>getBOClassNames()');
  2592.         
  2593.         global $config;
  2594.         
  2595.         $classNameArray array();
  2596.         
  2597.         
  2598.         if(file_exists($config->get('sysRoot').'model')) // it is possible it has not been created yet...
  2599.             // first get any custom BOs
  2600.             $handle opendir($config->get('sysRoot').'model');
  2601.                
  2602.             // loop over the business object directory
  2603.             while (false !== ($file readdir($handle))) {
  2604.                    if (preg_match("/Object.inc/"$file)) {
  2605.                        $classname substr($file0-4);
  2606.                         
  2607.                        array_push($classNameArray$classname);
  2608.                    }
  2609.             }
  2610.         }
  2611.         
  2612.         // now loop over the core BOs provided with Alpha
  2613.         
  2614.         $handle opendir($config->get('sysRoot').'alpha/model');
  2615.            
  2616.         // loop over the business object directory
  2617.         while (false !== ($file readdir($handle))) {
  2618.             if (preg_match("/Object.inc/"$file)) {
  2619.                 $classname substr($file0-4);                
  2620.                 
  2621.                 array_push($classNameArray$classname);
  2622.             }
  2623.         }
  2624.  
  2625.         asort($classNameArray);
  2626.         self::$logger->debug('<<getBOClassNames ['.var_export($classNameArraytrue).']');
  2627.         return $classNameArray;
  2628.     }
  2629.     
  2630.     /**
  2631.      * Get the array of default attribute names.
  2632.      *
  2633.      * @return array An array of attribute names.
  2634.      * @since 1.0
  2635.      */
  2636.     public function getDefaultAttributes({
  2637.         self::$logger->debug('>>getDefaultAttributes()');
  2638.         self::$logger->debug('<<getDefaultAttributes ['.var_export($this->defaultAttributestrue).']');
  2639.         return $this->defaultAttributes;
  2640.     }
  2641.     
  2642.     /**
  2643.      * Get the array of transient attribute names.
  2644.      *
  2645.      * @return array An array of attribute names.
  2646.      * @since 1.0
  2647.      */
  2648.     public function getTransientAttributes({
  2649.         self::$logger->debug('>>getTransientAttributes()');
  2650.         self::$logger->debug('<<getTransientAttributes ['.var_export($this->transientAttributestrue).']');
  2651.         return $this->transientAttributes;
  2652.     }
  2653.     
  2654.     /**
  2655.      * Get the array of persistent attribute names, i.e. those that are saved in the database.
  2656.      * 
  2657.      * @return array An array of attribute names.
  2658.      * @since 1.0
  2659.      */
  2660.     public function getPersistentAttributes({
  2661.         self::$logger->debug('>>getPersistentAttributes()');
  2662.         
  2663.         $attributes array();
  2664.         
  2665.         // get the class attributes
  2666.         $reflection new ReflectionClass(get_class($this));
  2667.         $properties $reflection->getProperties();
  2668.     
  2669.         foreach($properties as $propObj{
  2670.             $propName $propObj->name;
  2671.                                             
  2672.             // filter transient attributes
  2673.             if(!in_array($propName$this->transientAttributes)) {
  2674.                 array_push($attributes$propName);
  2675.             }
  2676.         }
  2677.         
  2678.         self::$logger->debug('<<getPersistentAttributes ['.var_export($attributestrue).']');
  2679.         return $attributes;
  2680.     }
  2681.     
  2682.     /**
  2683.      * Private setter for the object ID, used from load methods.
  2684.      *
  2685.      * @param integer $OID The Object ID.
  2686.      * @since 1.0
  2687.      */
  2688.     private function setOID($OID{
  2689.         self::$logger->debug('>>setOID(OID=['.$OID.'])');
  2690.         self::$logger->debug('<<setOID');
  2691.         $this->OID = $OID;
  2692.     }
  2693.     
  2694.     /**
  2695.      * Inspector to see if the business object is transient (not presently stored in the database).
  2696.      * 
  2697.      * @return boolean 
  2698.      * @since 1.0
  2699.      */
  2700.     public function isTransient({
  2701.         self::$logger->debug('>>isTransient()');
  2702.         
  2703.         if(empty($this->OID|| !isset($this->OID|| $this->OID == '00000000000'{
  2704.             self::$logger->debug('<<isTransient [true]');
  2705.             return true;
  2706.         }else{
  2707.             self::$logger->debug('<<isTransient [false]');
  2708.             return false;
  2709.         }
  2710.     }
  2711.     
  2712.     /**
  2713.      * Get the last database query run on this object.
  2714.      *
  2715.      * @return string An SQL query string.
  2716.      * @since 1.0
  2717.      */
  2718.     public function getLastQuery({
  2719.         self::$logger->debug('>>getLastQuery()');
  2720.         self::$logger->debug('<<getLastQuery ['.$this->lastQuery.']');
  2721.         return $this->lastQuery;
  2722.     }
  2723.     
  2724.     /**
  2725.      * Unsets all of the attributes of this object to null.
  2726.      * 
  2727.      * @since 1.0
  2728.      */
  2729.     private function clear({
  2730.         self::$logger->debug('>>clear()');
  2731.         
  2732.         // get the class attributes
  2733.         $reflection new ReflectionClass(get_class($this));
  2734.         $properties $reflection->getProperties();
  2735.         
  2736.         foreach($properties as $propObj{
  2737.             $propName $propObj->name;
  2738.             if(!$propObj->isPrivate())
  2739.                 unset($this->$propName);
  2740.         }
  2741.         
  2742.         self::$logger->debug('<<clear');
  2743.     }
  2744.     
  2745.     /**
  2746.      * Reloads the object from the database, overwritting any attribute values in memory.
  2747.      * 
  2748.      * @since 1.0
  2749.      * @throws AlphaException
  2750.      */
  2751.     public function reload({
  2752.         self::$logger->debug('>>reload()');
  2753.         
  2754.         if(!$this->isTransient()) {
  2755.             $this->load($this->getOID());            
  2756.         }else{
  2757.             throw new AlphaException('Cannot reload transient object from database!');
  2758.         }        
  2759.         self::$logger->debug('<<reload');
  2760.     }
  2761.     
  2762.     /**
  2763.      * Loads the definition from the file system for the BO class name provided.
  2764.      * 
  2765.      * @param string $classname The name of the business object class name.
  2766.      * @since 1.0
  2767.      * @throws IllegalArguementException
  2768.      */
  2769.     public static function loadClassDef($classname{
  2770.         if(self::$logger == null)
  2771.             self::$logger new Logger('AlphaDAO');
  2772.         self::$logger->debug('>>loadClassDef(classname=['.$classname.'])');
  2773.         
  2774.         global $config;
  2775.         
  2776.         if(file_exists($config->get('sysRoot').'model/'.$classname.'.inc'))
  2777.             require_once $config->get('sysRoot').'model/'.$classname.'.inc';
  2778.         elseif(file_exists($config->get('sysRoot').'alpha/model/'.$classname.'.inc'))
  2779.             require_once $config->get('sysRoot').'alpha/model/'.$classname.'.inc';
  2780.         elseif(file_exists($config->get('sysRoot').'alpha/model/types/'.$classname.'.inc'))
  2781.             require_once $config->get('sysRoot').'alpha/model/types/'.$classname.'.inc';
  2782.         else
  2783.             throw new IllegalArguementException('The class ['.$classname.'] is not defined anywhere!');
  2784.         
  2785.         self::$logger->debug('<<loadClassDef');
  2786.     }
  2787.     
  2788.     /**
  2789.      * Checks if the definition for the BO class name provided exists on the file system.
  2790.      * 
  2791.      * @param string $classname The name of the business object class name.
  2792.      * @return boolean 
  2793.      * @since 1.0
  2794.      */
  2795.     public static function checkClassDefExists($classname{
  2796.         if(self::$logger == null)
  2797.             self::$logger new Logger('AlphaDAO');
  2798.         self::$logger->debug('>>checkClassDefExists(classname=['.$classname.'])');
  2799.         
  2800.         global $config;
  2801.         
  2802.         $exists false;
  2803.         
  2804.         if(file_exists($config->get('sysRoot').'model/'.$classname.'.inc'))
  2805.             $exists true;
  2806.         if(file_exists($config->get('sysRoot').'alpha/model/'.$classname.'.inc'))
  2807.             $exists true;
  2808.         if(file_exists($config->get('sysRoot').'alpha/model/types/'.$classname.'.inc'))
  2809.             $exists true;
  2810.         
  2811.         self::$logger->debug('<<checkClassDefExists ['.$exists.']');
  2812.         return $exists;
  2813.     }
  2814.     
  2815.     /**
  2816.      * Checks that a record exists for the BO in the database.
  2817.      * 
  2818.      * @param int $OID The Object ID of the object we want to see whether it exists or not.
  2819.      * @return boolean 
  2820.      * @since 1.0
  2821.      * @throws AlphaException
  2822.      */
  2823.     public function checkRecordExists($OID{
  2824.         self::$logger->debug('>>checkRecordExists(OID=['.$OID.'])');
  2825.         
  2826.         if(method_exists($this'before_checkRecordExists_callback'))
  2827.             $this->before_checkRecordExists_callback();
  2828.         
  2829.         $sqlQuery 'SELECT OID FROM '.$this->getTableName().' WHERE OID = ?;';
  2830.  
  2831.         $this->lastQuery = $sqlQuery;
  2832.         
  2833.         $stmt AlphaDAO::getConnection()->stmt_init();
  2834.         
  2835.         if($stmt->prepare($sqlQuery)) {
  2836.             $stmt->bind_param('i'$OID);
  2837.             
  2838.             $stmt->execute();
  2839.             
  2840.             $result $this->bindResult($stmt);
  2841.                 
  2842.             $stmt->close();
  2843.  
  2844.             if ($result{
  2845.                 if(method_exists($this'after_checkRecordExists_callback'))
  2846.                     $this->after_checkRecordExists_callback();
  2847.                     
  2848.                 if(count($result0{
  2849.                     self::$logger->debug('<<checkRecordExists [true]');
  2850.                     return true;
  2851.                 }else{
  2852.                     self::$logger->debug('<<checkRecordExists [false]');
  2853.                     return false;
  2854.                 }
  2855.             }else{
  2856.                 throw new AlphaException('Failed to check for the record ['.$OID.'] on the class ['.get_class($this).'] from the table ['.$this->getTableName().'], query is ['.$this->lastQuery.']');            
  2857.                 self::$logger->debug('<<checkRecordExists [false]');
  2858.                 return false;
  2859.             }
  2860.         }else{
  2861.             throw new AlphaException('Failed to check for the record ['.$OID.'] on the class ['.get_class($this).'] from the table ['.$this->getTableName().'], query is ['.$this->lastQuery.']');            
  2862.             self::$logger->debug('<<checkRecordExists [false]');
  2863.             return false;
  2864.         }
  2865.     }
  2866.     
  2867.     /**
  2868.      * Checks to see if the table name matches the classname, and if not if the table
  2869.      * name matches the classname name of another BO, i.e. the table is used to store
  2870.      * multiple types of BOs.
  2871.      * 
  2872.      * @return bool 
  2873.      * @since 1.0
  2874.      * @throws BadBOTableNameException
  2875.      */
  2876.     public function isTableOverloaded({
  2877.         self::$logger->debug('>>isTableOverloaded()');
  2878.         
  2879.         $classname get_class($this);
  2880.         $tablename ucfirst($this->getTableName()).'Object';
  2881.         
  2882.         // use reflection to check to see if we are dealing with a persistent type (e.g. DEnum) which are never overloaded
  2883.         $reflection new ReflectionClass($classname);
  2884.         $implementedInterfaces $reflection->getInterfaces();
  2885.         
  2886.         foreach ($implementedInterfaces as $interface{
  2887.             if ($interface->name == 'AlphaTypeInterface'{
  2888.                 self::$logger->debug('<<isTableOverloaded [false]');
  2889.                 return false;
  2890.             }
  2891.         }
  2892.         
  2893.         if($classname != $tablename{
  2894.             // loop over all BOs to see if there is one using the same table as this BO
  2895.             
  2896.             $BOclasses self::getBOClassNames();
  2897.             
  2898.             foreach($BOclasses as $BOclassName{
  2899.                 if($tablename == $BOclassName{
  2900.                     self::$logger->debug('<<isTableOverloaded [true]');
  2901.                     return true;
  2902.                 }
  2903.             }
  2904.             throw new BadBOTableNameException('The table name ['.$tablename.'] for the class ['.$classname.'] is invalid as it does not match a BO definition in the system!');
  2905.             self::$logger->debug('<<isTableOverloaded [false]');
  2906.             return false;
  2907.         }else{
  2908.             // check to see if there is already a "classname" column in the database for this BO
  2909.             
  2910.             $query 'SHOW COLUMNS FROM '.$this->getTableName();
  2911.             
  2912.             $result AlphaDAO::getConnection()->query($query);
  2913.             
  2914.             if($result{
  2915.                 while ($row $result->fetch_array(MYSQLI_ASSOC)) {                        
  2916.                     if ('classname' == $row['Field']{
  2917.                         self::$logger->debug('<<isTableOverloaded [true]');
  2918.                         return true;
  2919.                     }
  2920.                 }
  2921.             }else{
  2922.                 self::$logger->warn('Error during show columns ['.AlphaDAO::getConnection()->error.']');
  2923.             }
  2924.             
  2925.             self::$logger->debug('<<isTableOverloaded [false]');
  2926.             return false;
  2927.         }
  2928.     }
  2929.     
  2930.     /**
  2931.      * Starts a new database transaction.
  2932.      * 
  2933.      * @since 1.0
  2934.      * @throws AlphaException
  2935.      */
  2936.     public static function begin({
  2937.         if(self::$logger == null)
  2938.             self::$logger new Logger('AlphaDAO');
  2939.         self::$logger->debug('>>begin()');         
  2940.          
  2941.          if (!AlphaDAO::getConnection()->autocommit(false))
  2942.              throw new AlphaException('Error beginning a new transaction, error is ['.AlphaDAO::getConnection()->error.']');
  2943.          
  2944.          self::$logger->debug('<<begin');
  2945.     }
  2946.     
  2947.     /**
  2948.      * Commits the current database transaction.
  2949.      * 
  2950.      * @since 1.0
  2951.      * @throws FailedSaveException
  2952.      */
  2953.     public static function commit({
  2954.         if(self::$logger == null)
  2955.             self::$logger new Logger('AlphaDAO');
  2956.         self::$logger->debug('>>commit()');
  2957.         
  2958.         if (!AlphaDAO::getConnection()->commit())
  2959.              throw new FailedSaveException('Error commiting a transaction, error is ['.AlphaDAO::getConnection()->error.']');
  2960.         
  2961.         self::$logger->debug('<<commit');
  2962.       }
  2963.       
  2964.     /**
  2965.      * Aborts the current database transaction.
  2966.      * 
  2967.      * @since 1.0
  2968.      * @throws AlphaException
  2969.      */
  2970.     public static function rollback({
  2971.         if(self::$logger == null)
  2972.             self::$logger new Logger('AlphaDAO');
  2973.         self::$logger->debug('>>rollback()');
  2974.         
  2975.         if (!AlphaDAO::getConnection()->rollback())
  2976.              throw new AlphaException('Error aborting a transaction, error is ['.AlphaDAO::getConnection()->error.']');
  2977.         
  2978.         self::$logger->debug('<<rollback');
  2979.       }
  2980.  
  2981.       /**
  2982.        * Static method that tries to determine if the system database has been installed or not.
  2983.        *
  2984.        * @return boolean 
  2985.        * @since 1.0
  2986.        */
  2987.       public static function isInstalled({
  2988.           if(self::$logger == null)
  2989.             self::$logger new Logger('AlphaDAO');
  2990.         self::$logger->debug('>>isInstalled()');
  2991.         
  2992.         global $config;
  2993.         
  2994.           /*
  2995.            * Install conditions are:
  2996.            * 
  2997.            * 1. person table exists
  2998.            * 2. rights table exists
  2999.            */
  3000.         if(AlphaDAO::checkBOTableExists('PersonObject'&& AlphaDAO::checkBOTableExists('RightsObject')) {
  3001.             self::$logger->debug('<<isInstalled [true]');
  3002.             return true;
  3003.         }else{
  3004.             self::$logger->debug('<<isInstalled [false]');
  3005.             return false;
  3006.         }
  3007.       }
  3008.       
  3009.       /**
  3010.        * Returns true if the BO has a Relation property called tags, false otherwise.
  3011.        * 
  3012.        * @return boolean 
  3013.        * @since 1.0
  3014.        */
  3015.       public function isTagged({
  3016.           if(isset($this->taggedAttributes&& isset($this->tags&& $this->tags instanceof Relation)
  3017.               return true;
  3018.           else
  3019.               return false;
  3020.       }
  3021.  
  3022.       /**
  3023.        * Setter for the BO version number.
  3024.        * 
  3025.        * @param integer $versionNumber The version number.
  3026.        * @since 1.0
  3027.        */
  3028.       private function setVersion($versionNumber{
  3029.           $this->version_num->setValue($versionNumber);
  3030.       }
  3031.       
  3032.       /**
  3033.        * Cast a BO to another type of BO.  A new BO will be returned with the same OID and
  3034.        * version_num as the old BO, so this is NOT a true cast but is a copy.  All attribute
  3035.        * values will be copied accross.
  3036.        * 
  3037.        * @param string $targetClassName The name of the target BO class.
  3038.        * @param AlphaDAO $originalBO The original business object.
  3039.        * @return AlphaDAO The new business object resulting from the cast.
  3040.        * @since 1.0
  3041.        */
  3042.       public function cast($targetClassName$originalBO{          
  3043.           AlphaDAO::loadClassDef($targetClassName);
  3044.           
  3045.           $BO new $targetClassName;
  3046.           $BO->setOID($originalBO->getOID());
  3047.           $BO->setVersion($originalBO->getVersion());
  3048.           
  3049.           // get the class attributes
  3050.         $originalBOreflection new ReflectionClass(get_class($originalBO));
  3051.         $originalBOproperties $originalBOreflection->getProperties();
  3052.         $newBOreflection new ReflectionClass($targetClassName);
  3053.         $newBOproperties $newBOreflection->getProperties();
  3054.         
  3055.         // copy the property values from the old BO to the new BO
  3056.         
  3057.         if(count($originalBOpropertiescount($newBOproperties)) {
  3058.             // the original BO is smaller, so loop over its properties
  3059.             foreach($originalBOproperties as $propObj{
  3060.                 $propName $propObj->name;
  3061.                 if(!in_array($propName$this->transientAttributes))
  3062.                     $BO->set($propName$originalBO->get($propName));
  3063.             }
  3064.         }else{
  3065.             // the new BO is smaller, so loop over its properties
  3066.             foreach($newBOproperties as $propObj{
  3067.                 $propName $propObj->name;
  3068.                 if(!in_array($propName$this->transientAttributes))
  3069.                     $BO->set($propName$originalBO->get($propName));
  3070.             }
  3071.         }        
  3072.           
  3073.           return $BO;
  3074.       }
  3075.       
  3076.       /**
  3077.        * Converts "BusinessObject" to "Business" and returns.
  3078.        *  
  3079.        * @return string 
  3080.        * @since 1.0
  3081.        */
  3082.       public function getFriendlyClassName({
  3083.           $name substr(get_class($this)0-6);
  3084.           
  3085.           return $name;
  3086.       }
  3087.       
  3088.       /**
  3089.        * Dynamically binds all of the attributes for the current BO to the supplied prepared statement
  3090.        * parameters.  If arrays of attribute names and values are provided, only those will be bound to
  3091.        * the supplied statement.
  3092.        *  
  3093.        * @param mysqli_stmt $stmt The SQL statement to bind to.
  3094.        * @param array Optional array of BO attributes.
  3095.        * @param array Optional array of BO values.
  3096.        * @return mysqli_stmt 
  3097.        * @since 1.0
  3098.        */
  3099.     protected function bindParams($stmt$attributes=array()$values=array()) {
  3100.         self::$logger->debug('>>bindParams(stmt=['.var_export($stmttrue).'])');
  3101.         
  3102.         $bindingsTypes '';
  3103.         $params array();
  3104.         
  3105.         // here we are only binding the supplied attributes
  3106.         if(count($attributes&& count($attributes== count($values)) {
  3107.     
  3108.             $count count($values);
  3109.             
  3110.             for($i 0$i $count$i++{
  3111.                 if (AlphaValidator::isInteger($values[$i]))
  3112.                     $bindingsTypes .= 'i';
  3113.                 else
  3114.                     $bindingsTypes .= 's';
  3115.                 array_push($params$values[$i]);
  3116.             }
  3117.                 
  3118.             if($this->isTableOverloaded()) {
  3119.                 if(isset($this->classname)) {
  3120.                     $bindingsTypes .= 's';
  3121.                     array_push($params$this->classname);                    
  3122.                 }else{                    
  3123.                     $bindingsTypes .= 's';                    
  3124.                     array_push($paramsget_class($this));                    
  3125.                 }
  3126.             }
  3127.         }else// bind all attributes on the business object
  3128.         
  3129.             // get the class attributes
  3130.             $reflection new ReflectionClass(get_class($this));
  3131.             $properties $reflection->getProperties();
  3132.     
  3133.             foreach($properties as $propObj{
  3134.                 $propName $propObj->name;
  3135.                 if (!in_array($propName$this->transientAttributes)) {                
  3136.                     // Skip the OID, database auto number takes care of this.
  3137.                     if($propName != 'OID' && $propName != 'version_num'{
  3138.                         if($this->$propName instanceof Integer)
  3139.                             $bindingsTypes .= 'i';
  3140.                         else
  3141.                             $bindingsTypes .= 's';
  3142.                         array_push($params$this->get($propName));
  3143.                     }
  3144.                                 
  3145.                     if($propName == 'version_num'{
  3146.                         $temp $this->version_num->getValue();
  3147.                         $this->version_num->setValue($temp+1);
  3148.                         $bindingsTypes .= 'i';
  3149.                         array_push($params$this->version_num->getValue());
  3150.                     }
  3151.                 }
  3152.             }
  3153.                 
  3154.             if($this->isTableOverloaded()) {
  3155.                 if(isset($this->classname)) {
  3156.                     $bindingsTypes .= 's';
  3157.                     array_push($params$this->classname);                    
  3158.                 }else{                    
  3159.                     $bindingsTypes .= 's';                    
  3160.                     array_push($paramsget_class($this));                    
  3161.                 }
  3162.             }
  3163.             
  3164.             // the OID may be on the WHERE clause for UPDATEs and DELETEs
  3165.             if(!$this->isTransient()) {                    
  3166.                 $bindingsTypes .= 'i';
  3167.                 array_push($params$this->OID);
  3168.             }
  3169.         }
  3170.         
  3171.         self::$logger->debug('bindingsTypes=['.$bindingsTypes.'], count: ['.strlen($bindingsTypes).']');
  3172.         self::$logger->debug('params ['.var_export($paramstrue).']');
  3173.                     
  3174.         if ($params != null{         
  3175.             $bind_names[$bindingsTypes;
  3176.  
  3177.             $count count($params);
  3178.             
  3179.             for ($i 0$i $count$i++{
  3180.                 $bind_name 'bind'.$i;
  3181.                 $$bind_name $params[$i];
  3182.                 $bind_names[&$$bind_name;
  3183.             }         
  3184.  
  3185.             call_user_func_array(array($stmt,'bind_param')$bind_names);
  3186.            }
  3187.         
  3188.            self::$logger->debug('<<bindParams ['.var_export($stmttrue).']');
  3189.         return $stmt;
  3190.     }
  3191.     
  3192.     /**
  3193.      * Dynamically binds the result of the supplied prepared statement to a 2d array, where each element in the array is another array
  3194.      * representing a database row.
  3195.      * 
  3196.      * @param mysqli_stmt $stmt 
  3197.      * @return array A 2D array containing the query result.
  3198.      * @since 1.0
  3199.      */
  3200.     protected function bindResult($stmt{
  3201.         $result array();
  3202.      
  3203.         $metadata $stmt->result_metadata();
  3204.           $fields $metadata->fetch_fields();
  3205.  
  3206.           while(true{
  3207.             $pointers array();
  3208.             $row array();
  3209.        
  3210.             $pointers[$stmt;
  3211.             foreach ($fields as $field{
  3212.                   $fieldname $field->name;
  3213.                   $pointers[&$row[$fieldname];
  3214.             }
  3215.        
  3216.             call_user_func_array('mysqli_stmt_bind_result'$pointers);
  3217.        
  3218.             if (!$stmt->fetch())
  3219.                   break;
  3220.        
  3221.                $result[$row;
  3222.           }
  3223.      
  3224.           $metadata->free();
  3225.      
  3226.           return $result;
  3227.     }
  3228.     
  3229.     /**
  3230.      * Check to see if an attribute exists on the BO.
  3231.      * 
  3232.      * @param $attribute The attribute name.
  3233.      * @return boolean 
  3234.      * @since 1.0
  3235.      */
  3236.     public function hasAttribute($attribute{
  3237.         try{
  3238.             $exists $this->$attribute;
  3239.             return true;
  3240.         }catch(Exception $e{
  3241.             return false;
  3242.         }
  3243.     }
  3244. }
  3245.  
  3246. ?>

Documentation generated on Thu, 17 Mar 2011 16:43:23 +0000 by phpDocumentor 1.4.3