       1: <?php
       2:
       3: /**
       4:  * Base business object class definition providing DAO storage via the configured provider.
       5:  *
       6:  * @package alpha::model
       7:  * @since 1.0
       8:  * @author John Collins <dev@alphaframework.org>
       9:  * @version $Id: AlphaDAO.inc 1572 2012-10-07 16:10:56Z alphadevx $
      10:  * @license http://www.opensource.org/licenses/bsd-license.php The BSD License
      11:  * @copyright Copyright (c) 2012, John Collins (founder of Alpha Framework).
      12:  * All rights reserved.
      13:  *
      14:  * <pre>
      15:  * Redistribution and use in source and binary forms, with or
      16:  * without modification, are permitted provided that the
      17:  * following conditions are met:
      18:  *
      19:  * * Redistributions of source code must retain the above
      20:  *   copyright notice, this list of conditions and the
      21:  *   following disclaimer.
      22:  * * Redistributions in binary form must reproduce the above
      23:  *   copyright notice, this list of conditions and the
      24:  *   following disclaimer in the documentation and/or other
      25:  *   materials provided with the distribution.
      26:  * * Neither the name of the Alpha Framework nor the names
      27:  *   of its contributors may be used to endorse or promote
      28:  *   products derived from this software without specific
      29:  *   prior written permission.
      30:  *
      31:  * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
      32:  * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
      33:  * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
      34:  * MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
      35:  * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
      36:  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
      37:  * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
      38:  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
      39:  * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
      40:  * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
      41:  * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
      42:  * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
      43:  * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
      44:  * </pre>
      45:  *
      46:  */
      47: abstract class AlphaDAO {
      48:     /**
      49:      * The object ID
      50:      *
      51:      * @var integer
      52:      * @since 1.0
      53:      */
      54:     protected $OID;
      55:
      56:     /**
      57:      * The last database query run by this object.  Useful for tracing an error.
      58:      *
      59:      * @var string
      60:      * @since 1.0
      61:      */
      62:     protected $lastQuery;
      63:
      64:     /**
      65:      * The version number of the object, used for locking mechanism
      66:      *
      67:      * @var Integer
      68:      * @since 1.0
      69:      */
      70:     protected $version_num;
      71:
      72:     /**
      73:      * The timestamp of creation
      74:      *
      75:      * @var Timestamp
      76:      * @since 1.0
      77:      */
      78:     protected $created_ts;
      79:
      80:     /**
      81:      * The OID of the person who created this BO
      82:      *
      83:      * @var Integer
      84:      * @since 1.0
      85:      */
      86:     protected $created_by;
      87:
      88:     /**
      89:      * The timestamp of the last update
      90:      *
      91:      * @var Timestamp
      92:      * @since 1.0
      93:      */
      94:     protected $updated_ts;
      95:
      96:     /**
      97:      * The OID of the person who last updated this BO
      98:      *
      99:      * @var Integer
     100:      * @since 1.0
     101:      */
     102:     protected $updated_by;
     103:
     104:     /**
     105:      * An array of the names of all of the default attributes of a persistent BO defined in this class
     106:      *
     107:      * @var array
     108:      * @since 1.0
     109:      */
     110:     protected $defaultAttributes = array("OID", "lastQuery", "version_num", "dataLabels", "created_ts", "created_by", "updated_ts", "updated_by", "defaultAttributes", "transientAttributes", "uniqueAttributes", "TABLE_NAME", "logger");
     111:
     112:     /**
     113:      * An array of the names of all of the transient attributes of a persistent BO which are not saved to the DB
     114:      *
     115:      * @var array
     116:      * @since 1.0
     117:      */
     118:     protected $transientAttributes = array("lastQuery", "dataLabels", "defaultAttributes", "transientAttributes", "uniqueAttributes", "TABLE_NAME", "logger");
     119:
     120:     /**
     121:      * An array of the uniquely-constained attributes of this persistent BO
     122:      *
     123:      * @var array
     124:      * @since 1.0
     125:      */
     126:     protected $uniqueAttributes = array();
     127:
     128:     /**
     129:      * An array of the data labels used for displaying class attributes
     130:      *
     131:      * @var array
     132:      * @since 1.0
     133:      */
     134:     protected $dataLabels = array();
     135:
     136:     /**
     137:      * Trace logger
     138:      *
     139:      * @var Logger
     140:      * @since 1.0
     141:      */
     142:     private static $logger = null;
     143:
     144:     /**
     145:          * Determines if we will maintain a _history table for this DAO (default is false).
     146:          *
     147:          * @var boolean
     148:          * @since 1.2
     149:          */
     150:     private $maintainHistory = false;
     151:
     152:     /**
     153:      * The constructor which sets up some housekeeping attributes
     154:      *
     155:      * @since 1.0
     156:      */
     157:     public function __construct() {
     158:         self::$logger = new Logger('AlphaDAO');
     159:         self::$logger->debug('>>__construct()');
     160:
     161:         $this->version_num = new Integer(0);
     162:         $this->created_ts = new Timestamp(date("Y-m-d H:i:s"));
     163:         $person_ID = (isset($_SESSION['currentUser'])? $_SESSION['currentUser']->getOID(): 0);
     164:         $this->created_by = new Integer($person_ID);
     165:         $this->updated_ts = new Timestamp(date("Y-m-d H:i:s"));
     166:         $this->updated_by = new Integer($person_ID);
     167:
     168:         self::$logger->debug('<<__construct');
     169:     }
     170:
     171:     /**
     172:      * Gets the current connection singleton, or creates a new one if none exists
     173:      *
     174:      * @return mysqli
     175:      * @since 1.0
     176:      * @throws AlphaException
     177:      * @deprecated
     178:      */
     179:     public static function getConnection() {
     180:         throw new AlphaException('The static getConnection() method is not longer supported!  Please update your code to instantiate a AlphaDAOProviderInterface '.
     181:         'instance, then invoke the query() method on that instance to run a custom query.');
     182:     }
     183:
     184:     /**
     185:      * Disconnects the current database connection if one exists
     186:      *
     187:      * @since 1.0
     188:      */
     189:     public static function disconnect() {
     190:         global $config;
     191:
     192:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), new PersonObject());
     193:         $provider->disconnect();
     194:     }
     195:
     196:     /**
     197:      * Returns a 2d array, where each element in the array is another array representing a database row.
     198:      *
     199:      * @param string $sqlQuery
     200:      * @return array
     201:      * @since 1.1
     202:      * @throws CustomQueryException
     203:      */
     204:     public function query($sqlQuery) {
     205:         self::$logger->debug('>>query(sqlQuery=['.$sqlQuery.'])');
     206:
     207:         global $config;
     208:
     209:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     210:         $result = $provider->query($sqlQuery);
     211:
     212:         self::$logger->debug('<<query ['.print_r($result, true).']');
     213:         return $result;
     214:     }
     215:
     216:     /**
     217:      * Populates the child object with the properties retrived from the database for the object $OID.
     218:      *
     219:      * @param integer $OID The object ID of the business object to load.
     220:      * @since 1.0
     221:      * @throws BONotFoundException
     222:      */
     223:     public function load($OID) {
     224:         self::$logger->debug('>>load(OID=['.$OID.'])');
     225:
     226:         if(method_exists($this, 'before_load_callback'))
     227:             $this->before_load_callback();
     228:
     229:         global $config;
     230:
     231:         $this->OID = $OID;
     232:
     233:         if($config->get('cache.provider.name') != '' && $this->loadFromCache()) {
     234:             // BO was found in cache
     235:         }else{
     236:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     237:             $provider->load($OID);
     238:
     239:             if($config->get('cache.provider.name') != '')
     240:                 $this->addToCache();
     241:         }
     242:
     243:         $this->setEnumOptions();
     244:
     245:         if(method_exists($this, 'after_load_callback'))
     246:             $this->after_load_callback();
     247:
     248:         self::$logger->debug('<<load');
     249:     }
     250:
     251:     /**
     252:      * Populates the child object from the database table by the given attribute value.
     253:      *
     254:      * @param string $atribute The name of the attribute to load the object by.
     255:      * @param string $value The value of the attribute to load the object by.
     256:      * @param boolean $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
     257:      * @param array $loadAttributes The attributes to load from the database to this object (leave blank to load all attributes)
     258:      * @since 1.0
     259:      * @throws BONotFoundException
     260:      */
     261:     public function loadByAttribute($attribute, $value, $ignoreClassType=false, $loadAttributes=array()) {
     262:         self::$logger->debug('>>loadByAttribute(attribute=['.$attribute.'], value=['.$value.'], ignoreClassType=['.$ignoreClassType.'],
     263:             loadAttributes=['.var_export($loadAttributes, true).'])');
     264:
     265:         if(method_exists($this, 'before_loadByAttribute_callback'))
     266:                 $this->before_loadByAttribute_callback();
     267:
     268:         global $config;
     269:
     270:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     271:         $provider->loadByAttribute($attribute, $value, $ignoreClassType, $loadAttributes);
     272:
     273:         $this->setEnumOptions();
     274:
     275:         if($config->get('cache.provider.name') != '' && count($loadAttributes) == 0) // we will only cache fully-populated BOs
     276:             $this->addToCache();
     277:
     278:         if(method_exists($this, 'after_loadByAttribute_callback'))
     279:             $this->after_loadByAttribute_callback();
     280:
     281:         self::$logger->debug('<<loadByAttribute');
     282:     }
     283:
     284:     /**
     285:      * Loads all of the objects of this class into an array which is returned.
     286:      *
     287:      * @param integer $start The start of the SQL LIMIT clause, useful for pagination.
     288:      * @param integer $limit The amount (limit) of objects to load, useful for pagination.
     289:      * @param string $orderBy The name of the field to sort the objects by.
     290:      * @param string $order The order to sort the objects by.
     291:      * @param boolean $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
     292:      * @return array An array containing objects of this type of business object.
     293:      * @since 1.0
     294:      * @throws BONotFoundException
     295:      */
     296:     public function loadAll($start=0, $limit=0, $orderBy='OID', $order='ASC', $ignoreClassType=false) {
     297:         self::$logger->debug('>>loadAll(start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
     298:
     299:         if(method_exists($this, 'before_loadAll_callback'))
     300:             $this->before_loadAll_callback();
     301:
     302:         global $config;
     303:
     304:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     305:         $objects = $provider->loadAll($start, $limit, $orderBy, $order, $ignoreClassType);
     306:
     307:         if(method_exists($this, 'after_loadAll_callback'))
     308:             $this->after_loadAll_callback();
     309:
     310:         self::$logger->debug('<<loadAll ['.count($objects).']');
     311:         return $objects;
     312:     }
     313:
     314:     /**
     315:      * Loads all of the objects of this class by the specified attribute into an array which is returned.
     316:      *
     317:      * @param string $atribute The attribute to load the objects by.
     318:      * @param string $value The value of the attribute to load the objects by.
     319:      * @param integer $start The start of the SQL LIMIT clause, useful for pagination.
     320:      * @param integer $limit The amount (limit) of objects to load, useful for pagination.
     321:      * @param string $orderBy The name of the field to sort the objects by.
     322:      * @param string $order The order to sort the objects by.
     323:      * @param boolean $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type.
     324:      * @param array $constructorArgs An optional array of contructor arguements to pass to the BOs that will be generated and returned.  Supports a maximum of 5 arguements.
     325:      * @return array An array containing objects of this type of business object.
     326:      * @since 1.0
     327:      * @throws BONotFoundException
     328:      * @throws IllegalArguementException
     329:      */
     330:     public function loadAllByAttribute($attribute, $value, $start=0, $limit=0, $orderBy="OID", $order="ASC", $ignoreClassType=false, $constructorArgs=array()) {
     331:         self::$logger->debug('>>loadAllByAttribute(attribute=['.$attribute.'], value=['.$value.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.'], constructorArgs=['.print_r($constructorArgs, true).']');
     332:
     333:         if(method_exists($this, 'before_loadAllByAttribute_callback'))
     334:             $this->before_loadAllByAttribute_callback();
     335:
     336:         global $config;
     337:
     338:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     339:         $objects = $provider->loadAllByAttribute($attribute, $value, $start, $limit, $orderBy, $order, $ignoreClassType);
     340:
     341:         if(method_exists($this, 'after_loadAllByAttribute_callback'))
     342:             $this->after_loadAllByAttribute_callback();
     343:
     344:         self::$logger->debug('<<loadAllByAttribute ['.count($objects).']');
     345:         return $objects;
     346:     }
     347:
     348:     /**
     349:      * Loads all of the objects of this class by the specified attributes into an array which is returned.
     350:      *
     351:      * @param array $atributes The attributes to load the objects by.
     352:      * @param array $values The values of the attributes to load the objects by.
     353:      * @param integer $start The start of the SQL LIMIT clause, useful for pagination.
     354:      * @param integer $limit The amount (limit) of objects to load, useful for pagination.
     355:      * @param string $orderBy The name of the field to sort the objects by.
     356:      * @param string $order The order to sort the objects by.
     357:      * @param boolean $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
     358:      * @return array An array containing objects of this type of business object.
     359:      * @since 1.0
     360:      * @throws BONotFoundException
     361:      * @throws IllegalArguementException
     362:      */
     363:     public function loadAllByAttributes($attributes=array(), $values=array(), $start=0, $limit=0, $orderBy='OID', $order='ASC', $ignoreClassType=false) {
     364:         self::$logger->debug('>>loadAllByAttributes(attributes=['.var_export($attributes, true).'], values=['.var_export($values, true).'], start=['.
     365:             $start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
     366:
     367:         if(method_exists($this, 'before_loadAllByAttributes_callback'))
     368:             $this->before_loadAllByAttributes_callback();
     369:
     370:         global $config;
     371:
     372:         if(!is_array($attributes) || !is_array($values)) {
     373:             throw new IllegalArguementException('Illegal arrays attributes=['.var_export($attributes, true).'] and values=['.var_export($values, true).
     374:                 '] provided to loadAllByAttributes');
     375:         }
     376:
     377:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     378:         $objects = $provider->loadAllByAttributes($attributes, $values, $start, $limit, $orderBy, $order, $ignoreClassType);
     379:
     380:         if(method_exists($this, 'after_loadAllByAttributes_callback'))
     381:             $this->after_loadAllByAttributes_callback();
     382:
     383:         self::$logger->debug('<<loadAllByAttributes ['.count($objects).']');
     384:         return $objects;
     385:     }
     386:
     387:     /**
     388:      * Loads all of the objects of this class that where updated (updated_ts value) on the date indicated.
     389:      *
     390:      * @param string $date The date for which to load the objects updated on, in the format 'YYYY-MM-DD'.
     391:      * @param integer $start The start of the SQL LIMIT clause, useful for pagination.
     392:      * @param integer $limit The amount (limit) of objects to load, useful for pagination.
     393:      * @param string $orderBy The name of the field to sort the objects by.
     394:      * @param string $order The order to sort the objects by.
     395:      * @param boolean $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type
     396:      * @return array An array containing objects of this type of business object.
     397:      * @since 1.0
     398:      * @throws BONotFoundException
     399:      */
     400:     public function loadAllByDayUpdated($date, $start=0, $limit=0, $orderBy="OID", $order="ASC", $ignoreClassType=false) {
     401:         self::$logger->debug('>>loadAllByDayUpdated(date=['.$date.'], start=['.$start.'], limit=['.$limit.'], orderBy=['.$orderBy.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
     402:
     403:         if(method_exists($this, 'before_loadAllByDayUpdated_callback'))
     404:             $this->before_loadAllByDayUpdated_callback();
     405:
     406:         global $config;
     407:
     408:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     409:         $objects = $provider->loadAllByDayUpdated($date, $start, $limit, $orderBy, $order, $ignoreClassType);
     410:
     411:         if(method_exists($this, 'after_loadAllByDayUpdated_callback'))
     412:             $this->after_loadAllByDayUpdated_callback();
     413:
     414:         self::$logger->debug('<<loadAllByDayUpdated ['.count($objects).']');
     415:         return $objects;
     416:     }
     417:
     418:     /**
     419:      * Loads all of the specified attribute values of this class by the specified attribute into an
     420:      * array which is returned.
     421:      *
     422:      * @param string $attribute The attribute name to load the field values by.
     423:      * @param string $value The value of the attribute to load the field values by.
     424:      * @param string $returnAttribute The name of the attribute to return.
     425:      * @param string $order The order to sort the BOs by.
     426:      * @param boolean $ignoreClassType Default is false, set to true if you want to load from overloaded tables and ignore the class type.
     427:      * @return array An array of field values.
     428:      * @since 1.0
     429:      * @throws BONotFoundException
     430:      */
     431:     public function loadAllFieldValuesByAttribute($attribute, $value, $returnAttribute, $order='ASC', $ignoreClassType=false) {
     432:         self::$logger->debug('>>loadAllFieldValuesByAttribute(attribute=['.$attribute.'], value=['.$value.'], returnAttribute=['.$returnAttribute.'], order=['.$order.'], ignoreClassType=['.$ignoreClassType.']');
     433:
     434:         global $config;
     435:
     436:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     437:         $values = $provider->loadAllFieldValuesByAttribute($attribute, $value, $returnAttribute, $order, $ignoreClassType);
     438:
     439:         self::$logger->debug('<<loadAllFieldValuesByAttribute ['.count($values).']');
     440:         return $values;
     441:     }
     442:
     443:     /**
     444:      * Saves the object.  If $this->OID is empty or null it will INSERT, otherwise UPDATE.
     445:      *
     446:      * @since 1.0
     447:      * @throws FailedSaveException
     448:      * @throws LockingException
     449:      * @throws ValidationException
     450:      */
     451:     public function save() {
     452:         self::$logger->debug('>>save()');
     453:
     454:         if(method_exists($this, 'before_save_callback'))
     455:             $this->before_save_callback();
     456:
     457:         global $config;
     458:
     459:         // firstly we will validate the object before we try to save it
     460:         if(!$this->validate()) {
     461:             throw new FailedSaveException('Could not save due to a validation error.');
     462:             return;
     463:         }else{
     464:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     465:             $provider->save();
     466:
     467:             if($config->get('cache.provider.name') != '')
     468:                 $this->removeFromCache();
     469:
     470:             if(method_exists($this, 'after_save_callback'))
     471:                 $this->after_save_callback();
     472:         }
     473:     }
     474:
     475:     /**
     476:      * Saves the field specified with the value supplied.  Only works for persistent BOs.  Note that no Alpha type
     477:      * validation is performed with this method!
     478:      *
     479:      * @param string $attribute The name of the attribute to save.
     480:      * @param mixed $value The value of the attribute to save.
     481:      * @since 1.0
     482:      * @throws IllegalArguementException
     483:      * @throws FailedSaveException
     484:      */
     485:     public function saveAttribute($attribute, $value) {
     486:         self::$logger->debug('>>saveAttribute(attribute=['.$attribute.'], value=['.$value.'])');
     487:
     488:         if(method_exists($this, 'before_saveAttribute_callback'))
     489:             $this->before_saveAttribute_callback();
     490:
     491:         global $config;
     492:
     493:         if(!isset($this->$attribute))
     494:             throw new IllegalArguementException('Could not perform save, as the attribute ['.$attribute.'] is not present on the class['.get_class($this).']');
     495:
     496:         if($this->isTransient())
     497:             throw new FailedSaveException('Cannot perform saveAttribute method on transient BO!');
     498:
     499:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     500:         $provider->saveAttribute($attribute, $value);
     501:
     502:         $this->set($attribute, $value);
     503:
     504:         if($config->get('cache.provider.name') != '')
     505:             $this->removeFromCache();
     506:
     507:         if(method_exists($this, 'after_saveAttribute_callback'))
     508:             $this->after_saveAttribute_callback();
     509:
     510:         self::$logger->debug('<<saveAttribute');
     511:     }
     512:
     513:     /**
     514:      * Saves the history of the object in the [tablename]_history table. It will always perform an insert.
     515:      *
     516:      * @since 1.2
     517:      * @throws FailedSaveException
     518:      */
     519:     public function saveHistory() {
     520:         self::$logger->debug('>>saveHistory()');
     521:
     522:         if(method_exists($this, 'before_saveHistory_callback'))
     523:             $this->before_saveHistory_callback();
     524:
     525:         global $config;
     526:
     527:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     528:         $provider->saveHistory();
     529:
     530:         if(method_exists($this, 'after_saveHistory_callback'))
     531:             $this->after_saveHistory_callback();
     532:     }
     533:
     534:     /**
     535:      * Validates the object to be saved.
     536:      *
     537:      * @return boolean
     538:      * @since 1.0
     539:      * @throws ValidationException
     540:      */
     541:     protected function validate() {
     542:         self::$logger->debug('>>validate()');
     543:
     544:         if(method_exists($this, 'before_validate_callback'))
     545:             $this->before_validate_callback();
     546:
     547:         $valid = true;
     548:
     549:         // get the class attributes
     550:         $reflection = new ReflectionClass(get_class($this));
     551:         $properties = $reflection->getProperties();
     552:
     553:         foreach($properties as $propObj) {
     554:             $propName = $propObj->name;
     555:             if(!in_array($propName, $this->defaultAttributes) && !in_array($propName, $this->transientAttributes)) {
     556:                 $propClass = get_class($this->getPropObject($propName));
     557:                 if (strtoupper($propClass) != "ENUM" &&
     558:                 strtoupper($propClass) != "DENUM" &&
     559:                 strtoupper($propClass) != "DENUMITEM" &&
     560:                 strtoupper($propClass) != "BOOLEAN") {
     561:                     if ($this->getPropObject($propName) != false && !preg_match($this->getPropObject($propName)->getRule(), $this->getPropObject($propName)->getValue())) {
     562:                         throw new ValidationException('Failed to save, validation error is: '.$this->getPropObject($propName)->getHelper());
     563:                         $valid = false;
     564:                     }
     565:                 }
     566:             }
     567:         }
     568:
     569:         if(method_exists($this, 'after_validate_callback'))
     570:             $this->after_validate_callback();
     571:
     572:         self::$logger->debug('<<validate ['.$valid.']');
     573:         return $valid;
     574:     }
     575:
     576:     /**
     577:      * Deletes the current object from the database.
     578:      *
     579:      * @since 1.0
     580:      * @throws FailedDeleteException
     581:      */
     582:     public function delete() {
     583:         self::$logger->debug('>>delete()');
     584:
     585:         if(method_exists($this, 'before_delete_callback'))
     586:             $this->before_delete_callback();
     587:
     588:         global $config;
     589:
     590:         // get the class attributes
     591:         $reflection = new ReflectionClass(get_class($this));
     592:         $properties = $reflection->getProperties();
     593:
     594:         // check for any relations on this object, then remove them to prevent orphaned data
     595:         foreach($properties as $propObj) {
     596:             $propName = $propObj->name;
     597:
     598:             if(!$propObj->isPrivate() && isset($this->$propName) && $this->$propName instanceof Relation) {
     599:                 $prop = $this->getPropObject($propName);
     600:
     601:                 // Handle MANY-TO-MANY rels
     602:                 if($prop->getRelationType() == 'MANY-TO-MANY') {
     603:                     self::$logger->debug('Deleting MANY-TO-MANY lookup objects...');
     604:
     605:                     try{
     606:                         // check to see if the rel is on this class
     607:                         $side = $prop->getSide(get_class($this));
     608:                     }catch (IllegalArguementException $iae) {
     609:                         $side = $prop->getSide(ucfirst($this->getTableName()).'Object');
     610:                     }
     611:
     612:                     self::$logger->debug('Side is ['.$side.']'.$this->getOID());
     613:
     614:                     $lookUp = $prop->getLookup();
     615:                     self::$logger->debug('Lookup object['.var_export($lookUp, true).']');
     616:
     617:                     // delete all of the old RelationLookup objects for this rel
     618:                     if($side == 'left')
     619:                         $lookUp->deleteAllByAttribute('leftID', $this->getOID());
     620:                     else
     621:                         $lookUp->deleteAllByAttribute('rightID', $this->getOID());
     622:                     self::$logger->debug('...done deleting!');
     623:                 }
     624:
     625:                 // should set related field values to null (MySQL is doing this for us as-is)
     626:                 if($prop->getRelationType() == 'ONE-TO-MANY' && !$prop->getRelatedClass() == 'TagObject') {
     627:                     $relatedObjects = $prop->getRelatedObjects();
     628:
     629:                     foreach($relatedObjects as $object) {
     630:                         $object->set($prop->getRelatedClassField(), null);
     631:                         $object->save();
     632:                     }
     633:                 }
     634:
     635:                 // in the case of tags, we will always remove the related tags once the BO is deleted
     636:                 if($prop->getRelationType() == 'ONE-TO-MANY' && $prop->getRelatedClass() == 'TagObject') {
     637:                     // just making sure that the Relation is set to current OID as its transient
     638:                     $prop->setValue($this->getOID());
     639:                     $relatedObjects = $prop->getRelatedObjects();
     640:
     641:                     foreach($relatedObjects as $object) {
     642:                         $object->delete();
     643:                     }
     644:                 }
     645:             }
     646:         }
     647:
     648:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     649:         $provider->delete();
     650:
     651:         if($config->get('cache.provider.name') != '')
     652:             $this->removeFromCache();
     653:
     654:         if(method_exists($this, 'after_delete_callback'))
     655:             $this->after_delete_callback();
     656:
     657:         $this->clear();
     658:         self::$logger->debug('<<delete');
     659:     }
     660:
     661:     /**
     662:      * Delete all object instances from the database by the specified attribute matching the value provided.
     663:      *
     664:      * @param string $attribute The name of the field to delete the objects by.
     665:      * @param mixed $value The value of the field to delete the objects by.
     666:      * @return integer The number of rows deleted.
     667:      * @since 1.0
     668:      * @throws FailedDeleteException
     669:      */
     670:     public function deleteAllByAttribute($attribute, $value) {
     671:         self::$logger->debug('>>deleteAllByAttribute(attribute=['.$attribute.'], value=['.$value.'])');
     672:
     673:         if(method_exists($this, 'before_deleteAllByAttribute_callback'))
     674:             $this->before_deleteAllByAttribute_callback();
     675:
     676:         try {
     677:             $doomedObjects = $this->loadAllByAttribute($attribute, $value);
     678:             $deletedRowCount = 0;
     679:
     680:             foreach ($doomedObjects as $object) {
     681:                 $object->delete();
     682:                 $deletedRowCount++;
     683:             }
     684:         }catch (BONotFoundException $bonf) {
     685:             // nothing found to delete
     686:             self::$logger->warn($bonf->getMessage());
     687:             return 0;
     688:         }catch (AlphaException $e) {
     689:             throw new FailedDeleteException('Failed to delete objects, error is ['.$e->getMessage().']');
     690:             self::$logger->debug('<<deleteAllByAttribute [0]');
     691:             return 0;
     692:         }
     693:
     694:         if(method_exists($this, 'after_deleteAllByAttribute_callback'))
     695:             $this->after_deleteAllByAttribute_callback();
     696:
     697:         self::$logger->debug('<<deleteAllByAttribute ['.$deletedRowCount.']');
     698:         return $deletedRowCount;
     699:     }
     700:
     701:     /**
     702:      * Gets the version_num of the object from the database (returns 0 if the BO is not saved yet).
     703:      *
     704:      * @return integer
     705:      * @since 1.0
     706:      * @throws BONotFoundException
     707:      */
     708:     public function getVersion() {
     709:         self::$logger->debug('>>getVersion()');
     710:
     711:         if(method_exists($this, 'before_getVersion_callback'))
     712:             $this->before_getVersion_callback();
     713:
     714:         global $config;
     715:
     716:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     717:         $ver = $provider->getVersion();
     718:
     719:         if(method_exists($this, 'after_getVersion_callback'))
     720:             $this->after_getVersion_callback();
     721:
     722:         self::$logger->debug('<<getVersion ['.$ver.']');
     723:         return $ver;
     724:     }
     725:
     726:     /**
     727:      * Builds a new database table for the BO class.
     728:      *
     729:      * @since 1.0
     730:      * @throws AlphaException
     731:      */
     732:     public function makeTable() {
     733:         self::$logger->debug('>>makeTable()');
     734:
     735:         if(method_exists($this, 'before_makeTable_callback'))
     736:             $this->before_makeTable_callback();
     737:
     738:         global $config;
     739:
     740:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     741:         $provider->makeTable();
     742:
     743:         if(method_exists($this, 'after_makeTable_callback'))
     744:             $this->after_makeTable_callback();
     745:
     746:         self::$logger->info('Successfully created the table ['.$this->getTableName().'] for the class ['.get_class($this).']');
     747:
     748:         self::$logger->debug('<<makeTable');
     749:     }
     750:
     751:     /**
     752:      * Builds a new database table for the BO class to story it's history of changes
     753:      *
     754:      * @since 1.2
     755:      * @throws AlphaException
     756:      */
     757:     public function makeHistoryTable() {
     758:         self::$logger->debug('>>makeHistoryTable()');
     759:
     760:         if(method_exists($this, 'before_makeHistoryTable_callback'))
     761:             $this->before_makeHistoryTable_callback();
     762:
     763:         global $config;
     764:
     765:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     766:         $provider->makeHistoryTable();
     767:
     768:         if(method_exists($this, 'after_makeHistoryTable_callback'))
     769:             $this->after_makeHistoryTable_callback();
     770:
     771:         self::$logger->info('Successfully created the table ['.$this->getTableName().'_history] for the class ['.get_class($this).']');
     772:
     773:         self::$logger->debug('<<makeHistoryTable');
     774:     }
     775:
     776:     /**
     777:      * Re-builds the table if the model requirements have changed.  All data is lost!
     778:      *
     779:      * @since 1.0
     780:      * @throws AlphaException
     781:      */
     782:     public function rebuildTable() {
     783:         self::$logger->debug('>>rebuildTable()');
     784:
     785:         if(method_exists($this, 'before_rebuildTable_callback'))
     786:             $this->before_rebuildTable_callback();
     787:
     788:         global $config;
     789:
     790:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     791:         $provider->rebuildTable();
     792:
     793:         if(method_exists($this, 'after_rebuildTable_callback'))
     794:             $this->after_rebuildTable_callback();
     795:
     796:         self::$logger->debug('<<rebuildTable');
     797:     }
     798:
     799:     /**
     800:      * Drops the table if the model requirements have changed.  All data is lost!
     801:      *
     802:      * @since 1.0
     803:      * @param string $tableName Optional table name, leave blank for the defined table for this class to be dropped
     804:      * @throws AlphaException
     805:      */
     806:     public function dropTable($tableName=null) {
     807:         self::$logger->debug('>>dropTable()');
     808:
     809:         if(method_exists($this, 'before_dropTable_callback'))
     810:             $this->before_dropTable_callback();
     811:
     812:         global $config;
     813:
     814:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     815:         $provider->dropTable($tableName);
     816:
     817:         if(method_exists($this, 'after_dropTable_callback'))
     818:             $this->after_dropTable_callback();
     819:
     820:         self::$logger->debug('<<dropTable');
     821:     }
     822:
     823:     /**
     824:      * Adds in a new class property without loosing existing data (does an ALTER TABLE query on the
     825:      * database).
     826:      *
     827:      * @param string $propName The name of the new field to add to the database table.
     828:      * @since 1.0
     829:      * @throws AlphaException
     830:      */
     831:     public function addProperty($propName) {
     832:         self::$logger->debug('>>addProperty(propName=['.$propName.'])');
     833:
     834:         global $config;
     835:
     836:         if(method_exists($this, 'before_addProperty_callback'))
     837:             $this->before_addProperty_callback();
     838:
     839:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     840:         $provider->addProperty($propName);
     841:
     842:         if(method_exists($this, 'after_addProperty_callback'))
     843:             $this->after_addProperty_callback();
     844:
     845:         self::$logger->debug('<<addProperty');
     846:     }
     847:
     848:     /**
     849:      * Populates the current business object from global POST data.
     850:      *
     851:      * @param boolean $filterAll Defaults to false, set to true if you want to strictly filter all POSTed user input using InputFilter::encode.
     852:      * @since 1.0
     853:      */
     854:     public function populateFromPost($filterAll=false) {
     855:         self::$logger->debug('>>populateFromPost(filterAll=['.$filterAll.'])');
     856:
     857:         if(method_exists($this, 'before_populateFromPost_callback'))
     858:             $this->before_populateFromPost_callback();
     859:
     860:         // get the class attributes
     861:         $reflection = new ReflectionClass(get_class($this));
     862:         $properties = $reflection->getProperties();
     863:
     864:         foreach($properties as $propObj) {
     865:             $propName = $propObj->name;
     866:
     867:             if(!in_array($propName, $this->defaultAttributes) && !in_array($propName, $this->transientAttributes)) {
     868:                 $propClass = get_class($this->getPropObject($propName));
     869:
     870:                 if(isset($_POST[$propName])) {
     871:                     // we will accept "on" as a valid Boolean when dealing with checkboxes
     872:                     if(strtoupper($propClass) == 'BOOLEAN' && $_POST[$propName] == 'on') {
     873:                         $_POST[$propName] = 1;
     874:                     }
     875:
     876:                     if (strtoupper($propClass) != 'DATE' && strtoupper($propClass) != 'TIMESTAMP') {
     877:                         if($filterAll) {
     878:                             $this->getPropObject($propName)->setValue(InputFilter::encode($_POST[$propName], false));
     879:                         }else{
     880:                             if(strtoupper($propClass) == 'TEXT' && !$this->getPropObject($propName)->getAllowHTML())
     881:                                 $this->getPropObject($propName)->setValue(InputFilter::encode($_POST[$propName], false));
     882:                             else
     883:                                 $this->getPropObject($propName)->setValue(InputFilter::encode($_POST[$propName], true));
     884:                         }
     885:                     }else{
     886:                         if($filterAll)
     887:                             $this->getPropObject($propName)->populateFromString(InputFilter::encode($_POST[$propName], false));
     888:                         else
     889:                             $this->getPropObject($propName)->populateFromString(InputFilter::encode($_POST[$propName], true));
     890:                     }
     891:                 }
     892:             }
     893:             if ($propName == 'version_num' && isset($_POST['version_num']))
     894:                 $this->version_num->setValue($_POST['version_num']);
     895:         }
     896:         if(method_exists($this, 'after_populateFromPost_callback'))
     897:             $this->after_populateFromPost_callback();
     898:
     899:         self::$logger->debug('<<populateFromPost');
     900:     }
     901:
     902:     /**
     903:      * Gets the maximum OID value from the database for this class type.
     904:      *
     905:      * @return integer The maximum OID value in the class table.
     906:      * @since 1.0
     907:      * @throws AlphaException
     908:      */
     909:     public function getMAX() {
     910:         self::$logger->debug('>>getMAX()');
     911:
     912:         if(method_exists($this, 'before_getMAX_callback'))
     913:             $this->before_getMAX_callback();
     914:
     915:         global $config;
     916:
     917:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     918:         $max = $provider->getMAX();
     919:
     920:         if(method_exists($this, 'after_getMAX_callback'))
     921:             $this->after_getMAX_callback();
     922:
     923:         self::$logger->debug('<<getMAX ['.$max.']');
     924:         return $max;
     925:     }
     926:
     927:     /**
     928:      * Gets the count from the database for the amount of objects of this class.
     929:      *
     930:      * @param array $atributes The attributes to count the objects by (optional).
     931:      * @param array $values The values of the attributes to count the objects by (optional).
     932:      * @return integer
     933:      * @since 1.0
     934:      * @throws AlphaException
     935:      */
     936:     public function getCount($attributes=array(), $values=array()) {
     937:         self::$logger->debug('>>getCount(attributes=['.var_export($attributes, true).'], values=['.var_export($values, true).'])');
     938:
     939:         if(method_exists($this, 'before_getCount_callback'))
     940:             $this->before_getCount_callback();
     941:
     942:         global $config;
     943:
     944:         if(!is_array($attributes) || !is_array($values)) {
     945:             throw new IllegalArguementException('Illegal arrays attributes=['.var_export($attributes, true).'] and values=['.var_export($values, true).'] provided to loadAllByAttributes');
     946:         }
     947:
     948:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     949:         $count = $provider->getCount($attributes, $values);
     950:
     951:         if(method_exists($this, 'after_getCount_callback'))
     952:             $this->after_getCount_callback();
     953:
     954:         self::$logger->debug('<<getCount ['.$count.']');
     955:         return $count;
     956:     }
     957:
     958:     /**
     959:      * Gets the count from the database for the amount of entries in the [tableName]_history table for this business object.  Only call
     960:      * this method on classes where maintainHistory = true, otherwise an exception will be thrown.
     961:      *
     962:      * @return integer
     963:      * @since 1.2
     964:      * @throws AlphaException
     965:      */
     966:     public function getHistoryCount() {
     967:         self::$logger->debug('>>getHistoryCount()');
     968:
     969:         if(method_exists($this, 'before_getHistoryCount_callback'))
     970:             $this->before_getHistoryCount_callback();
     971:
     972:         global $config;
     973:
     974:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     975:         $count = $provider->getHistoryCount();
     976:
     977:         if(method_exists($this, 'after_getHistoryCount_callback'))
     978:             $this->after_getHistoryCount_callback();
     979:
     980:         self::$logger->debug('<<getHistoryCount ['.$count.']');
     981:         return $count;
     982:     }
     983:
     984:     /**
     985:      * Gets the OID for the object in zero-padded format (same as getOID()).  This version is designed
     986:      * to be overridden in case you want to do something different with the ID of your objects outside
     987:      * of the standard 11 digit OID sequence used internally in Alpha.
     988:      *
     989:      * @return integer 11 digit zero-padded OID value.
     990:      * @since 1.0
     991:      */
     992:     public function getID() {
     993:         self::$logger->debug('>>getID()');
     994:         $oid = str_pad($this->OID, 11, '0', STR_PAD_LEFT);
     995:         self::$logger->debug('<<getID ['.$oid.']');
     996:         return $oid;
     997:     }
     998:
     999:     /**
    1000:      * Gets the OID for the object in zero-padded format (same as getID()).  This version is final so cannot
    1001:      * be overridden.
    1002:      *
    1003:      * @return integer 11 digit zero-padded OID value.
    1004:      * @since 1.0
    1005:      */
    1006:     public final function getOID() {
    1007:         if(self::$logger == null)
    1008:             self::$logger = new Logger('AlphaDAO');
    1009:         self::$logger->debug('>>getOID()');
    1010:         $oid = str_pad($this->OID, 11, '0', STR_PAD_LEFT);
    1011:         self::$logger->debug('<<getOID ['.$oid.']');
    1012:         return $oid;
    1013:     }
    1014:
    1015:     /**
    1016:      * Method for getting version number of the object.
    1017:      *
    1018:      * @return Integer The object version number.
    1019:      * @since 1.0
    1020:      */
    1021:     public function getVersionNumber() {
    1022:         self::$logger->debug('>>getVersionNumber()');
    1023:         self::$logger->debug('<<getVersionNumber ['.$this->version_num.']');
    1024:         return $this->version_num;
    1025:     }
    1026:
    1027:     /**
    1028:      * Populate all of the enum options for this object from the database.
    1029:      *
    1030:      * @since 1.0
    1031:      * @throws AlphaException
    1032:      */
    1033:     protected function setEnumOptions() {
    1034:         self::$logger->debug('>>setEnumOptions()');
    1035:
    1036:         if(method_exists($this, 'before_setEnumOptions_callback'))
    1037:             $this->before_setEnumOptions_callback();
    1038:
    1039:         global $config;
    1040:
    1041:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1042:         try {
    1043:             $provider->setEnumOptions();
    1044:         }catch (NotImplementedException $e) {
    1045:             self::$logger->info($e->getMessage());
    1046:         }
    1047:
    1048:         self::$logger->debug('<<setEnumOptions');
    1049:     }
    1050:
    1051:     /**
    1052:      * Generic getter method for accessing class properties.  Will use the method get.ucfirst($prop) instead if that
    1053:      * method exists at a child level (by default).  Set $noChildMethods to true if you don't want to use any
    1054:      * get.ucfirst($prop) method even if it exists, false otherwise (default).
    1055:      *
    1056:      * @param string $prop The name of the object property to get.
    1057:      * @param boolean $noChildMethods Set to true if you do not want to use getters in the child object, defaults to false.
    1058:      * @return mixed The property value.
    1059:      * @since 1.0
    1060:      * @throws IllegalArguementException
    1061:      * @throws AlphaException
    1062:      */
    1063:     public function get($prop, $noChildMethods = false) {
    1064:         if(self::$logger == null)
    1065:             self::$logger = new Logger('AlphaDAO');
    1066:
    1067:         self::$logger->debug('>>get(prop=['.$prop.'], noChildMethods=['.$noChildMethods.'])');
    1068:
    1069:         if(method_exists($this, 'before_get_callback'))
    1070:             $this->before_get_callback();
    1071:
    1072:         if(empty($prop))
    1073:             throw new IllegalArguementException('Cannot call get with empty $prop arguement!');
    1074:
    1075:         // handle attributes with a get.ucfirst($prop) method
    1076:         if(!$noChildMethods && method_exists($this, 'get'.ucfirst($prop))) {
    1077:             if(method_exists($this, 'after_get_callback'))
    1078:                 $this->after_get_callback();
    1079:
    1080:             self::$logger->debug('<<get ['.eval('return print_r($this->get'.ucfirst($prop).'(), true);').'])');
    1081:             return eval('return $this->get'.ucfirst($prop).'();');
    1082:         }else{
    1083:             // handle attributes with no dedicated child get.ucfirst($prop) method
    1084:             if(isset($this->$prop) && is_object($this->$prop) && method_exists($this->$prop, 'getValue')) {
    1085:                 if(method_exists($this, 'after_get_callback'))
    1086:                     $this->after_get_callback();
    1087:
    1088:                 // complex types will have a getValue() method, return the value of that
    1089:                 self::$logger->debug('<<get ['.$this->$prop->getValue().'])');
    1090:                 return $this->$prop->getValue();
    1091:             }elseif(isset($this->$prop)) {
    1092:                 if(method_exists($this, 'after_get_callback'))
    1093:                     $this->after_get_callback();
    1094:
    1095:                 // simple types returned as-is
    1096:                 self::$logger->debug('<<get ['.print_r($this->$prop, true).'])');
    1097:                 return $this->$prop;
    1098:             }else{
    1099:                 throw new AlphaException('Could not access the property ['.$prop.'] on the object of class ['.get_class($this).']');
    1100:                 self::$logger->debug('<<get [false])');
    1101:                 return false;
    1102:             }
    1103:         }
    1104:     }
    1105:
    1106:     /**
    1107:      * Generic setter method for setting class properties.  Will use the method set.ucfirst($prop) instead if that
    1108:      * method exists at a child level (by default).  Set $noChildMethods to true if you don't want to use
    1109:      * any get.ucfirst($prop) method even if it exists, false otherwise (default).
    1110:      *
    1111:      * @param string $prop The name of the property to set.
    1112:      * @param mixed $value The value of the property to set.
    1113:      * @param boolean $noChildMethods Set to true if you do not want to use setters in the child object, defaults
    1114:      * to false.
    1115:      * @since 1.0
    1116:      * @throws AlphaException
    1117:      */
    1118:     public function set($prop, $value, $noChildMethods = false) {
    1119:         self::$logger->debug('>>set(prop=['.$prop.'], $value=['.print_r($value, true).'], noChildMethods=['.$noChildMethods.'])');
    1120:
    1121:         if(method_exists($this, 'before_set_callback'))
    1122:             $this->before_set_callback();
    1123:
    1124:         // handle attributes with a set.ucfirst($prop) method
    1125:         if(!$noChildMethods && method_exists($this, 'set'.ucfirst($prop))) {
    1126:             if(method_exists($this, 'after_set_callback'))
    1127:                 $this->after_set_callback();
    1128:
    1129:             eval('$this->set'.ucfirst($prop).'($value);');
    1130:         }else{
    1131:             // handle attributes with no dedicated child set.ucfirst($prop) method
    1132:             if(isset($this->$prop)) {
    1133:                 if(method_exists($this, 'after_set_callback'))
    1134:                     $this->after_set_callback();
    1135:
    1136:                 // complex types will have a setValue() method to call
    1137:                 if (is_object($this->$prop) && get_class($this->$prop) != false) {
    1138:                     if (strtoupper(get_class($this->$prop)) != 'DATE' && strtoupper(get_class($this->$prop)) != 'TIMESTAMP') {
    1139:                         $this->$prop->setValue($value);
    1140:                     }else{
    1141:                         // Date and Timestamp objects have a special setter accepting a string
    1142:                         $this->$prop->populateFromString($value);
    1143:                     }
    1144:                 }else{
    1145:                     // simple types set directly
    1146:                     $this->$prop = $value;
    1147:                 }
    1148:             }else{
    1149:                 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.');
    1150:             }
    1151:         }
    1152:         self::$logger->debug('<<set');
    1153:     }
    1154:
    1155:     /**
    1156:      * Gets the property object rather than the value for complex attributes.  Returns false if
    1157:      * the property exists but is private.
    1158:      *
    1159:      * @param string $prop The name of the property we are getting.
    1160:      * @return AlphaType The complex type object found.
    1161:      * @since 1.0
    1162:      * @throws IllegalArguementException
    1163:      */
    1164:     public function getPropObject($prop) {
    1165:         self::$logger->debug('>>getPropObject(prop=['.$prop.'])');
    1166:
    1167:         if(method_exists($this, 'before_getPropObject_callback'))
    1168:             $this->before_getPropObject_callback();
    1169:
    1170:         // get the class attributes
    1171:         $reflection = new ReflectionClass(get_class($this));
    1172:         $properties = $reflection->getProperties();
    1173:
    1174:         // firstly, check for private
    1175:         $attribute = new ReflectionProperty(get_class($this), $prop);
    1176:
    1177:         if($attribute->isPrivate()) {
    1178:             if(method_exists($this, 'after_getPropObject_callback'))
    1179:                 $this->after_getPropObject_callback();
    1180:
    1181:             self::$logger->debug('<<getPropObject [false]');
    1182:             return false;
    1183:         }
    1184:
    1185:         foreach($properties as $propObj) {
    1186:             $propName = $propObj->name;
    1187:
    1188:             if($prop == $propName) {
    1189:                 if(method_exists($this, 'after_getPropObject_callback'))
    1190:                     $this->after_getPropObject_callback();
    1191:
    1192:                 self::$logger->debug('<<getPropObject ['.var_export($this->$prop, true).']');
    1193:                 return $this->$prop;
    1194:             }
    1195:         }
    1196:         throw new IllegalArguementException('Could not access the property object ['.$prop.'] on the object of class ['.get_class($this).']');
    1197:         self::$logger->debug('<<getPropObject [false]');
    1198:         return false;
    1199:     }
    1200:
    1201:     /**
    1202:      * Checks to see if the table exists in the database for the current business class.
    1203:      *
    1204:      * @param boolean $checkHistoryTable Set to true if you want to check for the existance of the _history table for this DAO.
    1205:      * @return boolean
    1206:      * @since 1.0
    1207:      * @throws AlphaException
    1208:      */
    1209:     public function checkTableExists($checkHistoryTable = false) {
    1210:         self::$logger->debug('>>checkTableExists()');
    1211:
    1212:         if(method_exists($this, 'before_checkTableExists_callback'))
    1213:             $this->before_checkTableExists_callback();
    1214:
    1215:         global $config;
    1216:
    1217:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1218:         $tableExists = $provider->checkTableExists($checkHistoryTable);
    1219:
    1220:         if(method_exists($this, 'after_checkTableExists_callback'))
    1221:             $this->after_checkTableExists_callback();
    1222:
    1223:         self::$logger->debug('<<checkTableExists ['.$tableExists.']');
    1224:         return $tableExists;
    1225:     }
    1226:
    1227:     /**
    1228:      * Static method to check the database and see if the table for the indicated BO class name
    1229:      * exists (assumes table name will be $BOClassName less "Object").
    1230:      *
    1231:      * @param string $BOClassName The name of the business object class we are checking.
    1232:      * @param boolean $checkHistoryTable Set to true if you want to check for the existance of the _history table for this DAO.
    1233:      * @return boolean
    1234:      * @since 1.0
    1235:      * @throws AlphaException
    1236:      */
    1237:     public static function checkBOTableExists($BOClassName, $checkHistoryTable = false) {
    1238:         if(self::$logger == null)
    1239:             self::$logger = new Logger('AlphaDAO');
    1240:         self::$logger->debug('>>checkBOTableExists(BOClassName=['.$BOClassName.'])');
    1241:
    1242:         global $config;
    1243:
    1244:         require_once $config->get('app.root').'alpha/model/'.$config->get('db.provider.name').'.inc';
    1245:         eval('$tableExists = '.$config->get('db.provider.name').'::checkBOTableExists($BOClassName, $checkHistoryTable);');
    1246:
    1247:         self::$logger->debug('<<checkBOTableExists ['.($tableExists ? 'true' : 'false').']');
    1248:         return $tableExists;
    1249:     }
    1250:
    1251:     /**
    1252:      * Checks to see if the table in the database matches (for fields) the business class definition, i.e. if the
    1253:      * database table is in sync with the class definition.
    1254:      *
    1255:      * @return boolean
    1256:      * @since 1.0
    1257:      * @throws AlphaException
    1258:      */
    1259:     public function checkTableNeedsUpdate() {
    1260:         self::$logger->debug('>>checkTableNeedsUpdate()');
    1261:
    1262:         global $config;
    1263:
    1264:         if(method_exists($this, 'before_checkTableNeedsUpdate_callback'))
    1265:             $this->before_checkTableNeedsUpdate_callback();
    1266:
    1267:         $tableExists = $this->checkTableExists();
    1268:
    1269:         if (!$tableExists) {
    1270:             self::$logger->debug('<<checkTableNeedsUpdate [true]');
    1271:             return true;
    1272:         }else{
    1273:
    1274:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1275:             $updateRequired = $provider->checkTableNeedsUpdate();
    1276:
    1277:             if(method_exists($this, 'after_checkTableNeedsUpdate_callback'))
    1278:                 $this->after_checkTableNeedsUpdate_callback();
    1279:
    1280:             self::$logger->debug('<<checkTableNeedsUpdate ['.$updateRequired.']');
    1281:             return $updateRequired;
    1282:         }
    1283:     }
    1284:
    1285:     /**
    1286:      * Returns an array containing any properties on the class which have not been created on the database
    1287:      * table yet.
    1288:      *
    1289:      * @return array An array of missing fields in the database table.
    1290:      * @since 1.0
    1291:      * @throws AlphaException
    1292:      */
    1293:     public function findMissingFields() {
    1294:         self::$logger->debug('>>findMissingFields()');
    1295:
    1296:         global $config;
    1297:
    1298:         if(method_exists($this, 'before_findMissingFields_callback'))
    1299:             $this->before_findMissingFields_callback();
    1300:
    1301:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1302:         $missingFields = $provider->findMissingFields();
    1303:
    1304:         if(method_exists($this, 'after_findMissingFields_callback'))
    1305:             $this->after_findMissingFields_callback();
    1306:
    1307:         self::$logger->debug('<<findMissingFields ['.var_export($missingFields, true).']');
    1308:         return $missingFields;
    1309:     }
    1310:
    1311:     /**
    1312:      * Getter for the TABLE_NAME, which should be set by a child of this class.
    1313:      *
    1314:      * @return string The table name in the database.
    1315:      * @since 1.0
    1316:      * @throws AlphaException
    1317:      */
    1318:     public function getTableName() {
    1319:         self::$logger->debug('>>getTableName()');
    1320:
    1321:         eval('$TABLE_NAME = '.get_class($this).'::TABLE_NAME;');
    1322:
    1323:         if(!empty($TABLE_NAME)) {
    1324:             self::$logger->debug('<<getTableName ['.$TABLE_NAME.']');
    1325:             return $TABLE_NAME;
    1326:         }else{
    1327:             throw new AlphaException('Error: no TABLE_NAME constant set for the class '.get_class($this));
    1328:             self::$logger->debug('<<getTableName []');
    1329:             return '';
    1330:         }
    1331:     }
    1332:
    1333:     /**
    1334:      * Method for getting the OID of the person who created this BO.
    1335:      *
    1336:      * @return Integer The OID of the creator.
    1337:      * @since 1.0
    1338:      */
    1339:     public function getCreatorId() {
    1340:         self::$logger->debug('>>getCreatorId()');
    1341:         self::$logger->debug('<<getCreatorId ['.$this->created_by.']');
    1342:         return $this->created_by;
    1343:     }
    1344:
    1345:     /**
    1346:      * Method for getting the OID of the person who updated this BO.
    1347:      *
    1348:      * @return Integer The OID of the updator.
    1349:      * @since 1.0
    1350:      */
    1351:     public function getUpdatorId() {
    1352:         self::$logger->debug('>>getUpdatorId()');
    1353:         self::$logger->debug('<<getUpdatorId ['.$this->updated_by.']');
    1354:         return $this->updated_by;
    1355:     }
    1356:
    1357:     /**
    1358:      * Method for getting the date/time of when the BO was created.
    1359:      *
    1360:      * @return Timestamp
    1361:      * @since 1.0
    1362:      */
    1363:     public function getCreateTS() {
    1364:         self::$logger->debug('>>getCreateTS()');
    1365:         self::$logger->debug('<<getCreateTS ['.$this->created_ts.']');
    1366:         return $this->created_ts;
    1367:     }
    1368:
    1369:     /**
    1370:      * Method for getting the date/time of when the BO was last updated.
    1371:      *
    1372:      * @return Timestamp
    1373:      * @since 1.0
    1374:      */
    1375:     public function getUpdateTS() {
    1376:         self::$logger->debug('>>getUpdateTS()');
    1377:         self::$logger->debug('<<getUpdateTS ['.$this->updated_ts.']');
    1378:         return $this->updated_ts;
    1379:     }
    1380:
    1381:     /**
    1382:      * Adds the name of the attribute provided to the list of transient (non-saved) attributes for this BO.
    1383:      *
    1384:      * @param string $attributeName The name of the attribute to not save.
    1385:      * @since 1.0
    1386:      */
    1387:     public function markTransient($attributeName) {
    1388:         self::$logger->debug('>>markTransient(attributeName=['.$attributeName.'])');
    1389:         self::$logger->debug('<<markTransient');
    1390:         array_push($this->transientAttributes, $attributeName);
    1391:     }
    1392:
    1393:     /**
    1394:      * Removes the name of the attribute provided from the list of transient (non-saved) attributes for this BO,
    1395:      * ensuring that it will be saved on the next attempt.
    1396:      *
    1397:      * @param string $attributeName The name of the attribute to save.
    1398:      * @since 1.0
    1399:      */
    1400:     public function markPersistent($attributeName) {
    1401:         self::$logger->debug('>>markPersistent(attributeName=['.$attributeName.'])');
    1402:         self::$logger->debug('<<markPersistent');
    1403:         $this->transientAttributes = array_diff($this->transientAttributes, array($attributeName));
    1404:     }
    1405:
    1406:     /**
    1407:      * Adds the name of the attribute(s) provided to the list of unique (constrained) attributes for this BO.
    1408:      *
    1409:      * @param string $attribute1Name The first attribute to mark unique in the database.
    1410:      * @param string $attribute2Name The second attribute to mark unique in the databse (optional, use only for composite keys).
    1411:      * @param string $attribute3Name The third attribute to mark unique in the databse (optional, use only for composite keys).
    1412:      * @since 1.0
    1413:      */
    1414:     protected function markUnique($attribute1Name, $attribute2Name='', $attribute3Name='') {
    1415:         self::$logger->debug('>>markUnique(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'], attribute3Name=['.$attribute3Name.'])');
    1416:
    1417:         if(empty($attribute2Name)) {
    1418:             array_push($this->uniqueAttributes, $attribute1Name);
    1419:         }else{
    1420:             // Process composite unique keys: add them seperated by a + sign
    1421:             if($attribute3Name == '')
    1422:                 $attributes = $attribute1Name.'+'.$attribute2Name;
    1423:             else
    1424:                 $attributes = $attribute1Name.'+'.$attribute2Name.'+'.$attribute3Name;
    1425:
    1426:             array_push($this->uniqueAttributes, $attributes);
    1427:         }
    1428:
    1429:         self::$logger->debug('<<markUnique');
    1430:     }
    1431:
    1432:     /**
    1433:      * Returns the array of names of unique attributes on this BO
    1434:      *
    1435:      * @return array
    1436:      * @since 1.1
    1437:      */
    1438:     public function getUniqueAttributes() {
    1439:         self::$logger->debug('>>getUniqueAttributes()');
    1440:         self::$logger->debug('<<getUniqueAttributes: ['.print_r($this->uniqueAttributes, true).']');
    1441:         return $this->uniqueAttributes;
    1442:     }
    1443:
    1444:     /**
    1445:      * Gets an array of all of the names of the active database indexes for this class.
    1446:      *
    1447:      * @return array An array of database indexes on this table.
    1448:      * @since 1.0
    1449:      * @throws AlphaException
    1450:      */
    1451:     public function getIndexes() {
    1452:         self::$logger->debug('>>getIndexes()');
    1453:
    1454:         global $config;
    1455:
    1456:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1457:         $indexNames = $provider->getIndexes();
    1458:
    1459:         self::$logger->debug('<<getIndexes ['.print_r($indexNames, true).']');
    1460:         return $indexNames;
    1461:     }
    1462:
    1463:     /**
    1464:      * Creates a foreign key constraint (index) in the database on the given attribute.
    1465:      *
    1466:      * @param string $attributeName The name of the attribute to apply the index on.
    1467:      * @param string $relatedClass The name of the related class in the format "NameObject".
    1468:      * @param string $relatedClassAttribute The name of the field to relate to on the related class.
    1469:      * @param bool $allowNullValues For foreign key indexes that don't allow null values, set this to false (default is true).
    1470:      * @since 1.0
    1471:      * @throws FailedIndexCreateException
    1472:      */
    1473:     public function createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute) {
    1474:         self::$logger->debug('>>createForeignIndex(attributeName=['.$attributeName.'], relatedClass=['.$relatedClass.'], relatedClassAttribute=['.$relatedClassAttribute.']');
    1475:
    1476:         global $config;
    1477:
    1478:         if(method_exists($this, 'before_createForeignIndex_callback'))
    1479:             $this->before_createForeignIndex_callback();
    1480:
    1481:         AlphaDAO::loadClassDef($relatedClass);
    1482:         $relatedBO = new $relatedClass;
    1483:         $tableName = $relatedBO->getTableName();
    1484:
    1485:         // if the relation is on itself (table-wise), exit without attempting to create the foreign keys
    1486:         if($this->getTableName() == $tableName) {
    1487:             self::$logger->debug('<<createForeignIndex');
    1488:             return;
    1489:         }
    1490:
    1491:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1492:         $provider->createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute);
    1493:
    1494:         if(method_exists($this, 'after_createForeignIndex_callback'))
    1495:             $this->after_createForeignIndex_callback();
    1496:
    1497:         self::$logger->debug('<<createForeignIndex');
    1498:     }
    1499:
    1500:     /**
    1501:      * Creates a unique index in the database on the given attribute(s).
    1502:      *
    1503:      * @param string $attribute1Name The first attribute to mark unique in the database.
    1504:      * @param string $attribute2Name The second attribute to mark unique in the databse (optional, use only for composite keys).
    1505:      * @param string $attribute3Name The third attribute to mark unique in the databse (optional, use only for composite keys).
    1506:      * @since 1.0
    1507:      * @throws FailedIndexCreateException
    1508:      */
    1509:     public function createUniqueIndex($attribute1Name, $attribute2Name = '', $attribute3Name = '') {
    1510:         self::$logger->debug('>>createUniqueIndex(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'], attribute3Name=['.$attribute3Name.'])');
    1511:
    1512:         if(method_exists($this, 'before_createUniqueIndex_callback'))
    1513:             $this->before_createUniqueIndex_callback();
    1514:
    1515:         global $config;
    1516:
    1517:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1518:         $provider->createUniqueIndex($attribute1Name, $attribute2Name, $attribute3Name);
    1519:
    1520:         if(method_exists($this, 'after_createUniqueIndex_callback'))
    1521:             $this->before_createUniqueIndex_callback();
    1522:
    1523:         self::$logger->debug('<<createUniqueIndex');
    1524:     }
    1525:
    1526:     /**
    1527:      * Gets the data labels array.
    1528:      *
    1529:      * @return array An array of attribute labels.
    1530:      * @since 1.0
    1531:      */
    1532:     public function getDataLabels() {
    1533:         self::$logger->debug('>>getDataLabels()');
    1534:         self::$logger->debug('<<getDataLabels() ['.var_export($this->dataLabels, true).'])');
    1535:         return $this->dataLabels;
    1536:     }
    1537:
    1538:     /**
    1539:      * Sets the data labels array.
    1540:      *
    1541:      * @param array $labels
    1542:      * @throws IllegalArguementException
    1543:      * @since 1.2
    1544:      */
    1545:     public function setDataLabels($labels) {
    1546:         self::$logger->debug('>>setDataLabels(labels=['.print_r($labels, true).'])');
    1547:
    1548:         if(is_array($labels))
    1549:             $this->dataLabels = $labels;
    1550:         else
    1551:             throw new IllegalArguementException('The value ['.print_r($labels, true).'] provided to setDataLabels() is not a valid array!');
    1552:
    1553:         self::$logger->debug('<<setDataLabels()');
    1554:     }
    1555:
    1556:     /**
    1557:      * Gets the data label for the given attribute name.
    1558:      *
    1559:      * @param $att The attribute name to get the label for.
    1560:      * @return string
    1561:      * @since 1.0
    1562:      * @throws IllegalArguementException
    1563:      */
    1564:     public function getDataLabel($att) {
    1565:         self::$logger->debug('>>getDataLabel(att=['.$att.'])');
    1566:
    1567:         if(in_array($att, array_keys($this->dataLabels))) {
    1568:             self::$logger->debug('<<getDataLabel ['.$this->dataLabels[$att].'])');
    1569:             return $this->dataLabels[$att];
    1570:         }else{
    1571:             throw new IllegalArguementException('No data label found on the class ['.get_class($this).'] for the attribute ['.$att.']');
    1572:             self::$logger->debug('<<getDataLabel [])');
    1573:             return '';
    1574:         }
    1575:     }
    1576:
    1577:     /**
    1578:      * Loops over the core and custom BO directories and builds an array of all of the BO class names in the system.
    1579:      *
    1580:      * @return array An array of business object class names.
    1581:      * @since 1.0
    1582:      */
    1583:     public static function getBOClassNames() {
    1584:         if(self::$logger == null)
    1585:             self::$logger = new Logger('AlphaDAO');
    1586:         self::$logger->debug('>>getBOClassNames()');
    1587:
    1588:         global $config;
    1589:
    1590:         $classNameArray = array();
    1591:
    1592:
    1593:         if(file_exists($config->get('app.root').'model')) { // it is possible it has not been created yet...
    1594:             // first get any custom BOs
    1595:             $handle = opendir($config->get('app.root').'model');
    1596:
    1597:             // loop over the business object directory
    1598:             while (false !== ($file = readdir($handle))) {
    1599:                 if (preg_match("/Object.inc/", $file)) {
    1600:                     $classname = substr($file, 0, -4);
    1601:
    1602:                     array_push($classNameArray, $classname);
    1603:                 }
    1604:             }
    1605:         }
    1606:
    1607:         // now loop over the core BOs provided with Alpha
    1608:
    1609:         $handle = opendir($config->get('app.root').'alpha/model');
    1610:
    1611:         // loop over the business object directory
    1612:         while (false !== ($file = readdir($handle))) {
    1613:             if (preg_match("/Object.inc/", $file)) {
    1614:                 $classname = substr($file, 0, -4);
    1615:
    1616:                 array_push($classNameArray, $classname);
    1617:             }
    1618:         }
    1619:
    1620:         asort($classNameArray);
    1621:         self::$logger->debug('<<getBOClassNames ['.var_export($classNameArray, true).']');
    1622:         return $classNameArray;
    1623:     }
    1624:
    1625:     /**
    1626:      * Get the array of default attribute names.
    1627:      *
    1628:      * @return array An array of attribute names.
    1629:      * @since 1.0
    1630:      */
    1631:     public function getDefaultAttributes() {
    1632:         self::$logger->debug('>>getDefaultAttributes()');
    1633:         self::$logger->debug('<<getDefaultAttributes ['.var_export($this->defaultAttributes, true).']');
    1634:         return $this->defaultAttributes;
    1635:     }
    1636:
    1637:     /**
    1638:      * Get the array of transient attribute names.
    1639:      *
    1640:      * @return array An array of attribute names.
    1641:      * @since 1.0
    1642:      */
    1643:     public function getTransientAttributes() {
    1644:         self::$logger->debug('>>getTransientAttributes()');
    1645:         self::$logger->debug('<<getTransientAttributes ['.var_export($this->transientAttributes, true).']');
    1646:         return $this->transientAttributes;
    1647:     }
    1648:
    1649:     /**
    1650:      * Get the array of persistent attribute names, i.e. those that are saved in the database.
    1651:      *
    1652:      * @return array An array of attribute names.
    1653:      * @since 1.0
    1654:      */
    1655:     public function getPersistentAttributes() {
    1656:         self::$logger->debug('>>getPersistentAttributes()');
    1657:
    1658:         $attributes = array();
    1659:
    1660:         // get the class attributes
    1661:         $reflection = new ReflectionClass(get_class($this));
    1662:         $properties = $reflection->getProperties();
    1663:
    1664:         foreach($properties as $propObj) {
    1665:             $propName = $propObj->name;
    1666:
    1667:             // filter transient attributes
    1668:             if(!in_array($propName, $this->transientAttributes)) {
    1669:                 array_push($attributes, $propName);
    1670:             }
    1671:         }
    1672:
    1673:         self::$logger->debug('<<getPersistentAttributes ['.var_export($attributes, true).']');
    1674:         return $attributes;
    1675:     }
    1676:
    1677:     /**
    1678:      * Setter for the Object ID (OID)
    1679:      *
    1680:      * @param integer $OID The Object ID.
    1681:      * @since 1.0
    1682:      */
    1683:     public function setOID($OID) {
    1684:         self::$logger->debug('>>setOID(OID=['.$OID.'])');
    1685:         self::$logger->debug('<<setOID');
    1686:         $this->OID = $OID;
    1687:     }
    1688:
    1689:     /**
    1690:      * Inspector to see if the business object is transient (not presently stored in the database).
    1691:      *
    1692:      * @return boolean
    1693:      * @since 1.0
    1694:      */
    1695:     public function isTransient() {
    1696:         self::$logger->debug('>>isTransient()');
    1697:
    1698:         if(empty($this->OID) || !isset($this->OID) || $this->OID == '00000000000') {
    1699:             self::$logger->debug('<<isTransient [true]');
    1700:             return true;
    1701:         }else{
    1702:             self::$logger->debug('<<isTransient [false]');
    1703:             return false;
    1704:         }
    1705:     }
    1706:
    1707:     /**
    1708:      * Get the last database query run on this object.
    1709:      *
    1710:      * @return string An SQL query string.
    1711:      * @since 1.0
    1712:      */
    1713:     public function getLastQuery() {
    1714:         self::$logger->debug('>>getLastQuery()');
    1715:         self::$logger->debug('<<getLastQuery ['.$this->lastQuery.']');
    1716:         return $this->lastQuery;
    1717:     }
    1718:
    1719:     /**
    1720:      * Unsets all of the attributes of this object to null.
    1721:      *
    1722:      * @since 1.0
    1723:      */
    1724:     private function clear() {
    1725:         self::$logger->debug('>>clear()');
    1726:
    1727:         // get the class attributes
    1728:         $reflection = new ReflectionClass(get_class($this));
    1729:         $properties = $reflection->getProperties();
    1730:
    1731:         foreach($properties as $propObj) {
    1732:             $propName = $propObj->name;
    1733:             if(!$propObj->isPrivate())
    1734:                 unset($this->$propName);
    1735:         }
    1736:
    1737:         self::$logger->debug('<<clear');
    1738:     }
    1739:
    1740:     /**
    1741:      * Reloads the object from the database, overwritting any attribute values in memory.
    1742:      *
    1743:      * @since 1.0
    1744:      * @throws AlphaException
    1745:      */
    1746:     public function reload() {
    1747:         self::$logger->debug('>>reload()');
    1748:
    1749:         if(!$this->isTransient()) {
    1750:             $this->load($this->getOID());
    1751:         }else{
    1752:             throw new AlphaException('Cannot reload transient object from database!');
    1753:         }
    1754:         self::$logger->debug('<<reload');
    1755:     }
    1756:
    1757:     /**
    1758:      * Loads the definition from the file system for the BO class name provided.
    1759:      *
    1760:      * @param string $classname The name of the business object class name.
    1761:      * @since 1.0
    1762:      * @throws IllegalArguementException
    1763:      */
    1764:     public static function loadClassDef($classname) {
    1765:         if(self::$logger == null)
    1766:             self::$logger = new Logger('AlphaDAO');
    1767:         self::$logger->debug('>>loadClassDef(classname=['.$classname.'])');
    1768:
    1769:         global $config;
    1770:
    1771:         if(file_exists($config->get('app.root').'model/'.$classname.'.inc'))
    1772:             require_once $config->get('app.root').'model/'.$classname.'.inc';
    1773:         elseif(file_exists($config->get('app.root').'alpha/model/'.$classname.'.inc'))
    1774:             require_once $config->get('app.root').'alpha/model/'.$classname.'.inc';
    1775:         elseif(file_exists($config->get('app.root').'alpha/model/types/'.$classname.'.inc'))
    1776:             require_once $config->get('app.root').'alpha/model/types/'.$classname.'.inc';
    1777:         else
    1778:             throw new IllegalArguementException('The class ['.$classname.'] is not defined anywhere!');
    1779:
    1780:         self::$logger->debug('<<loadClassDef');
    1781:     }
    1782:
    1783:     /**
    1784:      * Checks if the definition for the BO class name provided exists on the file system.
    1785:      *
    1786:      * @param string $classname The name of the business object class name.
    1787:      * @return boolean
    1788:      * @since 1.0
    1789:      */
    1790:     public static function checkClassDefExists($classname) {
    1791:         if(self::$logger == null)
    1792:             self::$logger = new Logger('AlphaDAO');
    1793:         self::$logger->debug('>>checkClassDefExists(classname=['.$classname.'])');
    1794:
    1795:         global $config;
    1796:
    1797:         $exists = false;
    1798:
    1799:         if(file_exists($config->get('app.root').'model/'.$classname.'.inc'))
    1800:             $exists = true;
    1801:         if(file_exists($config->get('app.root').'alpha/model/'.$classname.'.inc'))
    1802:             $exists = true;
    1803:         if(file_exists($config->get('app.root').'alpha/model/types/'.$classname.'.inc'))
    1804:             $exists = true;
    1805:
    1806:         self::$logger->debug('<<checkClassDefExists ['.$exists.']');
    1807:         return $exists;
    1808:     }
    1809:
    1810:     /**
    1811:      * Checks that a record exists for the BO in the database.
    1812:      *
    1813:      * @param int $OID The Object ID of the object we want to see whether it exists or not.
    1814:      * @return boolean
    1815:      * @since 1.0
    1816:      * @throws AlphaException
    1817:      */
    1818:     public function checkRecordExists($OID) {
    1819:         self::$logger->debug('>>checkRecordExists(OID=['.$OID.'])');
    1820:
    1821:         if(method_exists($this, 'before_checkRecordExists_callback'))
    1822:             $this->before_checkRecordExists_callback();
    1823:
    1824:         global $config;
    1825:
    1826:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1827:         $recordExists = $provider->checkRecordExists($OID);
    1828:
    1829:         if(method_exists($this, 'after_checkRecordExists_callback'))
    1830:             $this->after_checkRecordExists_callback();
    1831:
    1832:         self::$logger->debug('<<checkRecordExists ['.$recordExists.']');
    1833:         return $recordExists;
    1834:     }
    1835:
    1836:     /**
    1837:      * Checks to see if the table name matches the classname, and if not if the table
    1838:      * name matches the classname name of another BO, i.e. the table is used to store
    1839:      * multiple types of BOs.
    1840:      *
    1841:      * @return bool
    1842:      * @since 1.0
    1843:      * @throws BadBOTableNameException
    1844:      */
    1845:     public function isTableOverloaded() {
    1846:         self::$logger->debug('>>isTableOverloaded()');
    1847:
    1848:         global $config;
    1849:
    1850:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1851:         $isOverloaded = $provider->isTableOverloaded();
    1852:
    1853:         self::$logger->debug('<<isTableOverloaded ['.$isOverloaded.']');
    1854:         return $isOverloaded;
    1855:     }
    1856:
    1857:     /**
    1858:      * Starts a new database transaction.
    1859:      *
    1860:      * @param $BO The AlphaDAO instance to pass to the database provider. Leave empty to have a new PersonObject passed.
    1861:      * @since 1.0
    1862:      * @throws AlphaException
    1863:      */
    1864:     public static function begin($BO = null) {
    1865:         if(self::$logger == null)
    1866:             self::$logger = new Logger('AlphaDAO');
    1867:         self::$logger->debug('>>begin()');
    1868:
    1869:         global $config;
    1870:
    1871:         if(isset($BO))
    1872:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $BO);
    1873:         else
    1874:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), new PersonObject());
    1875:
    1876:         try{
    1877:             $provider->begin();
    1878:         }catch (Exception $e) {
    1879:             throw new AlphaException('Error beginning a new transaction, error is ['.$e->getMessage().']');
    1880:         }
    1881:
    1882:         self::$logger->debug('<<begin');
    1883:     }
    1884:
    1885:     /**
    1886:      * Commits the current database transaction.
    1887:      *
    1888:      * @param $BO The AlphaDAO instance to pass to the database provider. Leave empty to have a new PersonObject passed.
    1889:      * @since 1.0
    1890:      * @throws FailedSaveException
    1891:      */
    1892:     public static function commit($BO = null) {
    1893:         if(self::$logger == null)
    1894:             self::$logger = new Logger('AlphaDAO');
    1895:         self::$logger->debug('>>commit()');
    1896:
    1897:         global $config;
    1898:
    1899:         if(isset($BO))
    1900:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $BO);
    1901:         else
    1902:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), new PersonObject());
    1903:
    1904:         try{
    1905:             $provider->commit();
    1906:         }catch (Exception $e) {
    1907:             throw new FailedSaveException('Error commiting a transaction, error is ['.$e->getMessage().']');
    1908:         }
    1909:
    1910:         self::$logger->debug('<<commit');
    1911:     }
    1912:
    1913:     /**
    1914:      * Aborts the current database transaction.
    1915:      *
    1916:      * @param $BO The AlphaDAO instance to pass to the database provider. Leave empty to have a new PersonObject passed.
    1917:      * @since 1.0
    1918:      * @throws AlphaException
    1919:      */
    1920:     public static function rollback($BO = null) {
    1921:         if(self::$logger == null)
    1922:             self::$logger = new Logger('AlphaDAO');
    1923:         self::$logger->debug('>>rollback()');
    1924:
    1925:         global $config;
    1926:
    1927:         if(isset($BO))
    1928:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $BO);
    1929:         else
    1930:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), new PersonObject());
    1931:
    1932:         try{
    1933:             $provider->rollback();
    1934:         }catch (Exception $e) {
    1935:             throw new FailedSaveException('Error aborting a transaction, error is ['.$e->getMessage().']');
    1936:         }
    1937:
    1938:         self::$logger->debug('<<rollback');
    1939:     }
    1940:
    1941:     /**
    1942:      * Static method that tries to determine if the system database has been installed or not.
    1943:      *
    1944:      * @return boolean
    1945:      * @since 1.0
    1946:      */
    1947:     public static function isInstalled() {
    1948:         if(self::$logger == null)
    1949:             self::$logger = new Logger('AlphaDAO');
    1950:         self::$logger->debug('>>isInstalled()');
    1951:
    1952:         global $config;
    1953:
    1954:         /*
    1955:          * Install conditions are:
    1956:          *
    1957:          * 1. person table exists
    1958:          * 2. rights table exists
    1959:          */
    1960:         if(AlphaDAO::checkBOTableExists('PersonObject') && AlphaDAO::checkBOTableExists('RightsObject')) {
    1961:             self::$logger->debug('<<isInstalled [true]');
    1962:             return true;
    1963:         }else{
    1964:             self::$logger->debug('<<isInstalled [false]');
    1965:             return false;
    1966:         }
    1967:     }
    1968:
    1969:     /**
    1970:      * Returns true if the BO has a Relation property called tags, false otherwise.
    1971:      *
    1972:      * @return boolean
    1973:      * @since 1.0
    1974:      */
    1975:     public function isTagged() {
    1976:         if(isset($this->taggedAttributes) && isset($this->tags) && $this->tags instanceof Relation)
    1977:             return true;
    1978:         else
    1979:             return false;
    1980:     }
    1981:
    1982:     /**
    1983:      * Setter for the BO version number.
    1984:      *
    1985:      * @param integer $versionNumber The version number.
    1986:      * @since 1.0
    1987:      */
    1988:     private function setVersion($versionNumber) {
    1989:         $this->version_num->setValue($versionNumber);
    1990:     }
    1991:
    1992:     /**
    1993:      * Cast a BO to another type of BO.  A new BO will be returned with the same OID and
    1994:      * version_num as the old BO, so this is NOT a true cast but is a copy.  All attribute
    1995:      * values will be copied accross.
    1996:      *
    1997:      * @param string $targetClassName The name of the target BO class.
    1998:      * @param AlphaDAO $originalBO The original business object.
    1999:      * @return AlphaDAO The new business object resulting from the cast.
    2000:      * @since 1.0
    2001:      */
    2002:     public function cast($targetClassName, $originalBO) {
    2003:         AlphaDAO::loadClassDef($targetClassName);
    2004:
    2005:         $BO = new $targetClassName;
    2006:         $BO->setOID($originalBO->getOID());
    2007:         $BO->setVersion($originalBO->getVersion());
    2008:
    2009:         // get the class attributes
    2010:         $originalBOreflection = new ReflectionClass(get_class($originalBO));
    2011:         $originalBOproperties = $originalBOreflection->getProperties();
    2012:         $newBOreflection = new ReflectionClass($targetClassName);
    2013:         $newBOproperties = $newBOreflection->getProperties();
    2014:
    2015:         // copy the property values from the old BO to the new BO
    2016:
    2017:         if(count($originalBOproperties) < count($newBOproperties)) {
    2018:             // the original BO is smaller, so loop over its properties
    2019:             foreach($originalBOproperties as $propObj) {
    2020:                 $propName = $propObj->name;
    2021:                 if(!in_array($propName, $this->transientAttributes))
    2022:                     $BO->set($propName, $originalBO->get($propName));
    2023:             }
    2024:         }else{
    2025:             // the new BO is smaller, so loop over its properties
    2026:             foreach($newBOproperties as $propObj) {
    2027:                 $propName = $propObj->name;
    2028:                 if(!in_array($propName, $this->transientAttributes))
    2029:                     $BO->set($propName, $originalBO->get($propName));
    2030:             }
    2031:         }
    2032:
    2033:         return $BO;
    2034:     }
    2035:
    2036:     /**
    2037:      * Converts "BusinessObject" to "Business" and returns.
    2038:      *
    2039:      * @return string
    2040:      * @since 1.0
    2041:      */
    2042:     public function getFriendlyClassName() {
    2043:         $name = substr(get_class($this), 0, -6);
    2044:
    2045:         return $name;
    2046:     }
    2047:
    2048:     /**
    2049:      * Check to see if an attribute exists on the BO.
    2050:      *
    2051:      * @param $attribute The attribute name.
    2052:      * @return boolean
    2053:      * @since 1.0
    2054:      */
    2055:     public function hasAttribute($attribute) {
    2056:         try{
    2057:             $exists = $this->$attribute;
    2058:             return true;
    2059:         }catch(Exception $e) {
    2060:             return false;
    2061:         }
    2062:     }
    2063:
    2064:     /**
    2065:      * Stores the business object to the configured cache instance
    2066:      *
    2067:      * @since 1.1
    2068:      */
    2069:         public function addToCache() {
    2070:         self::$logger->debug('>>addToCache()');
    2071:         global $config;
    2072:
    2073:         try {
    2074:             $cache = AlphaCacheProviderFactory::getInstance($config->get('cache.provider.name'));
    2075:             $cache->set(get_class($this).'-'.$this->getOID(), $this, 3600);
    2076:
    2077:         }catch(Exception $e) {
    2078:             self::$logger->error('Error while attempting to store a business object to the ['.$config->get('cache.provider.name').']
    2079:                 instance: ['.$e->getMessage().']');
    2080:         }
    2081:
    2082:         self::$logger->debug('<<addToCache');
    2083:     }
    2084:
    2085:     /**
    2086:      * Removes the business object from the configured cache instance
    2087:      *
    2088:      * @since 1.1
    2089:      */
    2090:     public function removeFromCache() {
    2091:         self::$logger->debug('>>removeFromCache()');
    2092:         global $config;
    2093:
    2094:         try {
    2095:             $cache = AlphaCacheProviderFactory::getInstance($config->get('cache.provider.name'));
    2096:             $cache->delete(get_class($this).'-'.$this->getOID());
    2097:         }catch(Exception $e) {
    2098:             self::$logger->error('Error while attempting to remove a business object from ['.$config->get('cache.provider.name').']
    2099:                 instance: ['.$e->getMessage().']');
    2100:         }
    2101:
    2102:         self::$logger->debug('<<removeFromCache');
    2103:     }
    2104:
    2105:     /**
    2106:      * Attempts to load the business object from the configured cache instance
    2107:      *
    2108:      * @since 1.1
    2109:      * @return boolean
    2110:      */
    2111:     public function loadFromCache() {
    2112:         self::$logger->debug('>>loadFromCache()');
    2113:         global $config;
    2114:
    2115:         try {
    2116:             $cache = AlphaCacheProviderFactory::getInstance($config->get('cache.provider.name'));
    2117:             $BO = $cache->get(get_class($this).'-'.$this->getOID());
    2118:
    2119:             if(!$BO) {
    2120:                 self::$logger->debug('Cache miss on key ['.get_class($this).'-'.$this->getOID().']');
    2121:                 self::$logger->debug('<<loadFromCache: [false]');
    2122:                 return false;
    2123:             }else{
    2124:                 // get the class attributes
    2125:                 $reflection = new ReflectionClass(get_class($this));
    2126:                 $properties = $reflection->getProperties();
    2127:
    2128:                 foreach($properties as $propObj) {
    2129:                     $propName = $propObj->name;
    2130:
    2131:                     // filter transient attributes
    2132:                     if(!in_array($propName, $this->transientAttributes)) {
    2133:                         $this->set($propName, $BO->get($propName));
    2134:                     }elseif(!$propObj->isPrivate() && isset($this->$propName) && $this->$propName instanceof Relation) {
    2135:                         $prop = $this->getPropObject($propName);
    2136:
    2137:                         // handle the setting of ONE-TO-MANY relation values
    2138:                         if($prop->getRelationType() == 'ONE-TO-MANY') {
    2139:                             $this->set($propObj->name, $this->getOID());
    2140:                         }
    2141:                     }
    2142:                 }
    2143:
    2144:                 self::$logger->debug('<<loadFromCache: [true]');
    2145:                 return true;
    2146:             }
    2147:         }catch(Exception $e) {
    2148:             self::$logger->error('Error while attempting to load a business object from ['.$config->get('cache.provider.name').']
    2149:              instance: ['.$e->getMessage().']');
    2150:
    2151:             self::$logger->debug('<<loadFromCache: [false]');
    2152:             return false;
    2153:         }
    2154:     }
    2155:
    2156:     /**
    2157:      * Sets the last query executed on this business object
    2158:      *
    2159:      * @param string $query
    2160:      * @since 1.1
    2161:      */
    2162:     public function setLastQuery($query) {
    2163:         self::$logger->sql($query);
    2164:         $this->lastQuery = $query;
    2165:     }
    2166:
    2167:     /**
    2168:      * Re-initialize the static logger property on the BO after de-serialize, as PHP does
    2169:      * not serialize static properties.
    2170:      *
    2171:      * @since 1.2
    2172:      */
    2173:     public function __wakeup() {
    2174:         if(self::$logger == null)
    2175:             self::$logger = new Logger(get_class($this));
    2176:     }
    2177:
    2178:     /**
    2179:      * Sets maintainHistory attribute on this DAO
    2180:      *
    2181:      * @param boolean $maintainHistory
    2182:      * @throws IllegalArguementException
    2183:      * @since 1.2
    2184:      */
    2185:     public function setMaintainHistory($maintainHistory) {
    2186:         if(!is_bool($maintainHistory))
    2187:             throw new IllegalArguementException('Non-boolean value ['.$maintainHistory.'] passed to setMaintainHistory method!');
    2188:
    2189:         $this->maintainHistory = $maintainHistory;
    2190:     }
    2191:
    2192:     /**
    2193:      * Gets the value of the  maintainHistory attribute
    2194:      *
    2195:      * @returns boolean
    2196:      * @since 1.2
    2197:      */
    2198:     public function getMaintainHistory() {
    2199:         return $this->maintainHistory;
    2200:     }
    2201: }
    2202:
    2203: ?>