       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 1656 2013-05-28 10:08:25Z 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:                 $this->addToCache();
     470:             }
     471:
     472:             if(method_exists($this, 'after_save_callback'))
     473:                 $this->after_save_callback();
     474:         }
     475:     }
     476:
     477:     /**
     478:      * Saves the field specified with the value supplied.  Only works for persistent BOs.  Note that no Alpha type
     479:      * validation is performed with this method!
     480:      *
     481:      * @param string $attribute The name of the attribute to save.
     482:      * @param mixed $value The value of the attribute to save.
     483:      * @since 1.0
     484:      * @throws IllegalArguementException
     485:      * @throws FailedSaveException
     486:      */
     487:     public function saveAttribute($attribute, $value) {
     488:         self::$logger->debug('>>saveAttribute(attribute=['.$attribute.'], value=['.$value.'])');
     489:
     490:         if(method_exists($this, 'before_saveAttribute_callback'))
     491:             $this->before_saveAttribute_callback();
     492:
     493:         global $config;
     494:
     495:         if(!isset($this->$attribute))
     496:             throw new IllegalArguementException('Could not perform save, as the attribute ['.$attribute.'] is not present on the class['.get_class($this).']');
     497:
     498:         if($this->isTransient())
     499:             throw new FailedSaveException('Cannot perform saveAttribute method on transient BO!');
     500:
     501:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     502:         $provider->saveAttribute($attribute, $value);
     503:
     504:         if($config->get('cache.provider.name') != '') {
     505:             $this->removeFromCache();
     506:             $this->addToCache();
     507:         }
     508:
     509:         if(method_exists($this, 'after_saveAttribute_callback'))
     510:             $this->after_saveAttribute_callback();
     511:
     512:         self::$logger->debug('<<saveAttribute');
     513:     }
     514:
     515:     /**
     516:      * Saves the history of the object in the [tablename]_history table. It will always perform an insert.
     517:      *
     518:      * @since 1.2
     519:      * @throws FailedSaveException
     520:      */
     521:     public function saveHistory() {
     522:         self::$logger->debug('>>saveHistory()');
     523:
     524:         if(method_exists($this, 'before_saveHistory_callback'))
     525:             $this->before_saveHistory_callback();
     526:
     527:         global $config;
     528:
     529:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     530:         $provider->saveHistory();
     531:
     532:         if(method_exists($this, 'after_saveHistory_callback'))
     533:             $this->after_saveHistory_callback();
     534:     }
     535:
     536:     /**
     537:      * Validates the object to be saved.
     538:      *
     539:      * @return boolean
     540:      * @since 1.0
     541:      * @throws ValidationException
     542:      */
     543:     protected function validate() {
     544:         self::$logger->debug('>>validate()');
     545:
     546:         if(method_exists($this, 'before_validate_callback'))
     547:             $this->before_validate_callback();
     548:
     549:         $valid = true;
     550:
     551:         // get the class attributes
     552:         $reflection = new ReflectionClass(get_class($this));
     553:         $properties = $reflection->getProperties();
     554:
     555:         foreach($properties as $propObj) {
     556:             $propName = $propObj->name;
     557:             if(!in_array($propName, $this->defaultAttributes) && !in_array($propName, $this->transientAttributes)) {
     558:                 $propClass = get_class($this->getPropObject($propName));
     559:                 if (strtoupper($propClass) != "ENUM" &&
     560:                 strtoupper($propClass) != "DENUM" &&
     561:                 strtoupper($propClass) != "DENUMITEM" &&
     562:                 strtoupper($propClass) != "BOOLEAN") {
     563:                     if ($this->getPropObject($propName) != false && !preg_match($this->getPropObject($propName)->getRule(), $this->getPropObject($propName)->getValue())) {
     564:                         throw new ValidationException('Failed to save, validation error is: '.$this->getPropObject($propName)->getHelper());
     565:                         $valid = false;
     566:                     }
     567:                 }
     568:             }
     569:         }
     570:
     571:         if(method_exists($this, 'after_validate_callback'))
     572:             $this->after_validate_callback();
     573:
     574:         self::$logger->debug('<<validate ['.$valid.']');
     575:         return $valid;
     576:     }
     577:
     578:     /**
     579:      * Deletes the current object from the database.
     580:      *
     581:      * @since 1.0
     582:      * @throws FailedDeleteException
     583:      */
     584:     public function delete() {
     585:         self::$logger->debug('>>delete()');
     586:
     587:         if(method_exists($this, 'before_delete_callback'))
     588:             $this->before_delete_callback();
     589:
     590:         global $config;
     591:
     592:         // get the class attributes
     593:         $reflection = new ReflectionClass(get_class($this));
     594:         $properties = $reflection->getProperties();
     595:
     596:         // check for any relations on this object, then remove them to prevent orphaned data
     597:         foreach($properties as $propObj) {
     598:             $propName = $propObj->name;
     599:
     600:             if(!$propObj->isPrivate() && isset($this->$propName) && $this->$propName instanceof Relation) {
     601:                 $prop = $this->getPropObject($propName);
     602:
     603:                 // Handle MANY-TO-MANY rels
     604:                 if($prop->getRelationType() == 'MANY-TO-MANY') {
     605:                     self::$logger->debug('Deleting MANY-TO-MANY lookup objects...');
     606:
     607:                     try{
     608:                         // check to see if the rel is on this class
     609:                         $side = $prop->getSide(get_class($this));
     610:                     }catch (IllegalArguementException $iae) {
     611:                         $side = $prop->getSide(ucfirst($this->getTableName()).'Object');
     612:                     }
     613:
     614:                     self::$logger->debug('Side is ['.$side.']'.$this->getOID());
     615:
     616:                     $lookUp = $prop->getLookup();
     617:                     self::$logger->debug('Lookup object['.var_export($lookUp, true).']');
     618:
     619:                     // delete all of the old RelationLookup objects for this rel
     620:                     if($side == 'left')
     621:                         $lookUp->deleteAllByAttribute('leftID', $this->getOID());
     622:                     else
     623:                         $lookUp->deleteAllByAttribute('rightID', $this->getOID());
     624:                     self::$logger->debug('...done deleting!');
     625:                 }
     626:
     627:                 // should set related field values to null (MySQL is doing this for us as-is)
     628:                 if($prop->getRelationType() == 'ONE-TO-MANY' && !$prop->getRelatedClass() == 'TagObject') {
     629:                     $relatedObjects = $prop->getRelatedObjects();
     630:
     631:                     foreach($relatedObjects as $object) {
     632:                         $object->set($prop->getRelatedClassField(), null);
     633:                         $object->save();
     634:                     }
     635:                 }
     636:
     637:                 // in the case of tags, we will always remove the related tags once the BO is deleted
     638:                 if($prop->getRelationType() == 'ONE-TO-MANY' && $prop->getRelatedClass() == 'TagObject') {
     639:                     // just making sure that the Relation is set to current OID as its transient
     640:                     $prop->setValue($this->getOID());
     641:                     $relatedObjects = $prop->getRelatedObjects();
     642:
     643:                     foreach($relatedObjects as $object) {
     644:                         $object->delete();
     645:                     }
     646:                 }
     647:             }
     648:         }
     649:
     650:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     651:         $provider->delete();
     652:
     653:         if($config->get('cache.provider.name') != '')
     654:             $this->removeFromCache();
     655:
     656:         if(method_exists($this, 'after_delete_callback'))
     657:             $this->after_delete_callback();
     658:
     659:         $this->clear();
     660:         self::$logger->debug('<<delete');
     661:     }
     662:
     663:     /**
     664:      * Delete all object instances from the database by the specified attribute matching the value provided.
     665:      *
     666:      * @param string $attribute The name of the field to delete the objects by.
     667:      * @param mixed $value The value of the field to delete the objects by.
     668:      * @return integer The number of rows deleted.
     669:      * @since 1.0
     670:      * @throws FailedDeleteException
     671:      */
     672:     public function deleteAllByAttribute($attribute, $value) {
     673:         self::$logger->debug('>>deleteAllByAttribute(attribute=['.$attribute.'], value=['.$value.'])');
     674:
     675:         if(method_exists($this, 'before_deleteAllByAttribute_callback'))
     676:             $this->before_deleteAllByAttribute_callback();
     677:
     678:         try {
     679:             $doomedObjects = $this->loadAllByAttribute($attribute, $value);
     680:             $deletedRowCount = 0;
     681:
     682:             foreach ($doomedObjects as $object) {
     683:                 $object->delete();
     684:                 $deletedRowCount++;
     685:             }
     686:         }catch (BONotFoundException $bonf) {
     687:             // nothing found to delete
     688:             self::$logger->warn($bonf->getMessage());
     689:             return 0;
     690:         }catch (AlphaException $e) {
     691:             throw new FailedDeleteException('Failed to delete objects, error is ['.$e->getMessage().']');
     692:             self::$logger->debug('<<deleteAllByAttribute [0]');
     693:             return 0;
     694:         }
     695:
     696:         if(method_exists($this, 'after_deleteAllByAttribute_callback'))
     697:             $this->after_deleteAllByAttribute_callback();
     698:
     699:         self::$logger->debug('<<deleteAllByAttribute ['.$deletedRowCount.']');
     700:         return $deletedRowCount;
     701:     }
     702:
     703:     /**
     704:      * Gets the version_num of the object from the database (returns 0 if the BO is not saved yet).
     705:      *
     706:      * @return integer
     707:      * @since 1.0
     708:      * @throws BONotFoundException
     709:      */
     710:     public function getVersion() {
     711:         self::$logger->debug('>>getVersion()');
     712:
     713:         if(method_exists($this, 'before_getVersion_callback'))
     714:             $this->before_getVersion_callback();
     715:
     716:         global $config;
     717:
     718:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     719:         $ver = $provider->getVersion();
     720:
     721:         if(method_exists($this, 'after_getVersion_callback'))
     722:             $this->after_getVersion_callback();
     723:
     724:         self::$logger->debug('<<getVersion ['.$ver.']');
     725:         return $ver;
     726:     }
     727:
     728:     /**
     729:      * Builds a new database table for the BO class.
     730:      *
     731:      * @since 1.0
     732:      * @throws AlphaException
     733:      */
     734:     public function makeTable() {
     735:         self::$logger->debug('>>makeTable()');
     736:
     737:         if(method_exists($this, 'before_makeTable_callback'))
     738:             $this->before_makeTable_callback();
     739:
     740:         global $config;
     741:
     742:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     743:         $provider->makeTable();
     744:
     745:         if(method_exists($this, 'after_makeTable_callback'))
     746:             $this->after_makeTable_callback();
     747:
     748:         self::$logger->info('Successfully created the table ['.$this->getTableName().'] for the class ['.get_class($this).']');
     749:
     750:         self::$logger->debug('<<makeTable');
     751:     }
     752:
     753:     /**
     754:      * Builds a new database table for the BO class to story it's history of changes
     755:      *
     756:      * @since 1.2
     757:      * @throws AlphaException
     758:      */
     759:     public function makeHistoryTable() {
     760:         self::$logger->debug('>>makeHistoryTable()');
     761:
     762:         if(method_exists($this, 'before_makeHistoryTable_callback'))
     763:             $this->before_makeHistoryTable_callback();
     764:
     765:         global $config;
     766:
     767:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     768:         $provider->makeHistoryTable();
     769:
     770:         if(method_exists($this, 'after_makeHistoryTable_callback'))
     771:             $this->after_makeHistoryTable_callback();
     772:
     773:         self::$logger->info('Successfully created the table ['.$this->getTableName().'_history] for the class ['.get_class($this).']');
     774:
     775:         self::$logger->debug('<<makeHistoryTable');
     776:     }
     777:
     778:     /**
     779:      * Re-builds the table if the model requirements have changed.  All data is lost!
     780:      *
     781:      * @since 1.0
     782:      * @throws AlphaException
     783:      */
     784:     public function rebuildTable() {
     785:         self::$logger->debug('>>rebuildTable()');
     786:
     787:         if(method_exists($this, 'before_rebuildTable_callback'))
     788:             $this->before_rebuildTable_callback();
     789:
     790:         global $config;
     791:
     792:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     793:         $provider->rebuildTable();
     794:
     795:         if(method_exists($this, 'after_rebuildTable_callback'))
     796:             $this->after_rebuildTable_callback();
     797:
     798:         self::$logger->debug('<<rebuildTable');
     799:     }
     800:
     801:     /**
     802:      * Drops the table if the model requirements have changed.  All data is lost!
     803:      *
     804:      * @since 1.0
     805:      * @param string $tableName Optional table name, leave blank for the defined table for this class to be dropped
     806:      * @throws AlphaException
     807:      */
     808:     public function dropTable($tableName=null) {
     809:         self::$logger->debug('>>dropTable()');
     810:
     811:         if(method_exists($this, 'before_dropTable_callback'))
     812:             $this->before_dropTable_callback();
     813:
     814:         global $config;
     815:
     816:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     817:         $provider->dropTable($tableName);
     818:
     819:         if(method_exists($this, 'after_dropTable_callback'))
     820:             $this->after_dropTable_callback();
     821:
     822:         self::$logger->debug('<<dropTable');
     823:     }
     824:
     825:     /**
     826:      * Adds in a new class property without loosing existing data (does an ALTER TABLE query on the
     827:      * database).
     828:      *
     829:      * @param string $propName The name of the new field to add to the database table.
     830:      * @since 1.0
     831:      * @throws AlphaException
     832:      */
     833:     public function addProperty($propName) {
     834:         self::$logger->debug('>>addProperty(propName=['.$propName.'])');
     835:
     836:         global $config;
     837:
     838:         if(method_exists($this, 'before_addProperty_callback'))
     839:             $this->before_addProperty_callback();
     840:
     841:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     842:         $provider->addProperty($propName);
     843:
     844:         if(method_exists($this, 'after_addProperty_callback'))
     845:             $this->after_addProperty_callback();
     846:
     847:         self::$logger->debug('<<addProperty');
     848:     }
     849:
     850:     /**
     851:      * Populates the current business object from global POST data.
     852:      *
     853:      * @param boolean $filterAll Defaults to false, set to true if you want to strictly filter all POSTed user input using InputFilter::encode.
     854:      * @since 1.0
     855:      */
     856:     public function populateFromPost($filterAll=false) {
     857:         self::$logger->debug('>>populateFromPost(filterAll=['.$filterAll.'])');
     858:
     859:         if(method_exists($this, 'before_populateFromPost_callback'))
     860:             $this->before_populateFromPost_callback();
     861:
     862:         // get the class attributes
     863:         $reflection = new ReflectionClass(get_class($this));
     864:         $properties = $reflection->getProperties();
     865:
     866:         foreach($properties as $propObj) {
     867:             $propName = $propObj->name;
     868:
     869:             if(!in_array($propName, $this->defaultAttributes) && !in_array($propName, $this->transientAttributes)) {
     870:                 $propClass = get_class($this->getPropObject($propName));
     871:
     872:                 if(isset($_POST[$propName])) {
     873:                     // we will accept "on" as a valid Boolean when dealing with checkboxes
     874:                     if(strtoupper($propClass) == 'BOOLEAN' && $_POST[$propName] == 'on') {
     875:                         $_POST[$propName] = 1;
     876:                     }
     877:
     878:                     if (strtoupper($propClass) != 'DATE' && strtoupper($propClass) != 'TIMESTAMP') {
     879:                         if($filterAll) {
     880:                             $this->getPropObject($propName)->setValue(InputFilter::encode($_POST[$propName], false));
     881:                         }else{
     882:                             if(strtoupper($propClass) == 'TEXT' && !$this->getPropObject($propName)->getAllowHTML())
     883:                                 $this->getPropObject($propName)->setValue(InputFilter::encode($_POST[$propName], false));
     884:                             else
     885:                                 $this->getPropObject($propName)->setValue(InputFilter::encode($_POST[$propName], true));
     886:                         }
     887:                     }else{
     888:                         if($filterAll)
     889:                             $this->getPropObject($propName)->populateFromString(InputFilter::encode($_POST[$propName], false));
     890:                         else
     891:                             $this->getPropObject($propName)->populateFromString(InputFilter::encode($_POST[$propName], true));
     892:                     }
     893:                 }
     894:             }
     895:             if ($propName == 'version_num' && isset($_POST['version_num']))
     896:                 $this->version_num->setValue($_POST['version_num']);
     897:         }
     898:         if(method_exists($this, 'after_populateFromPost_callback'))
     899:             $this->after_populateFromPost_callback();
     900:
     901:         self::$logger->debug('<<populateFromPost');
     902:     }
     903:
     904:     /**
     905:      * Populates the current business object from the provided hash array
     906:      *
     907:      * @param array $hashArray
     908:      * @since 1.2.1
     909:      */
     910:     public function populateFromArray($hashArray) {
     911:         self::$logger->debug('>>populateFromArray(hashArray=['.print_r($hashArray, true).'])');
     912:
     913:         // get the class attributes
     914:         $reflection = new ReflectionClass(get_class($this));
     915:         $properties = $reflection->getProperties();
     916:
     917:         foreach($properties as $propObj) {
     918:             $propName = $propObj->name;
     919:
     920:             if(!in_array($propName, $this->defaultAttributes) && !in_array($propName, $this->transientAttributes)) {
     921:                 $propClass = get_class($this->getPropObject($propName));
     922:
     923:                 if(isset($hashArray[$propName])) {
     924:
     925:                     if (strtoupper($propClass) != 'DATE' && strtoupper($propClass) != 'TIMESTAMP')
     926:                         $this->getPropObject($propName)->setValue($hashArray[$propName]);
     927:                     else
     928:                         $this->getPropObject($propName)->populateFromString($hashArray[$propName]);
     929:                 }
     930:             }
     931:
     932:             if ($propName == 'version_num' && isset($hashArray['version_num']))
     933:                 $this->version_num->setValue($hashArray['version_num']);
     934:         }
     935:
     936:         self::$logger->debug('<<populateFromArray');
     937:     }
     938:
     939:     /**
     940:      * Gets the maximum OID value from the database for this class type.
     941:      *
     942:      * @return integer The maximum OID value in the class table.
     943:      * @since 1.0
     944:      * @throws AlphaException
     945:      */
     946:     public function getMAX() {
     947:         self::$logger->debug('>>getMAX()');
     948:
     949:         if(method_exists($this, 'before_getMAX_callback'))
     950:             $this->before_getMAX_callback();
     951:
     952:         global $config;
     953:
     954:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     955:         $max = $provider->getMAX();
     956:
     957:         if(method_exists($this, 'after_getMAX_callback'))
     958:             $this->after_getMAX_callback();
     959:
     960:         self::$logger->debug('<<getMAX ['.$max.']');
     961:         return $max;
     962:     }
     963:
     964:     /**
     965:      * Gets the count from the database for the amount of objects of this class.
     966:      *
     967:      * @param array $atributes The attributes to count the objects by (optional).
     968:      * @param array $values The values of the attributes to count the objects by (optional).
     969:      * @return integer
     970:      * @since 1.0
     971:      * @throws AlphaException
     972:      */
     973:     public function getCount($attributes=array(), $values=array()) {
     974:         self::$logger->debug('>>getCount(attributes=['.var_export($attributes, true).'], values=['.var_export($values, true).'])');
     975:
     976:         if(method_exists($this, 'before_getCount_callback'))
     977:             $this->before_getCount_callback();
     978:
     979:         global $config;
     980:
     981:         if(!is_array($attributes) || !is_array($values)) {
     982:             throw new IllegalArguementException('Illegal arrays attributes=['.var_export($attributes, true).'] and values=['.var_export($values, true).'] provided to loadAllByAttributes');
     983:         }
     984:
     985:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     986:         $count = $provider->getCount($attributes, $values);
     987:
     988:         if(method_exists($this, 'after_getCount_callback'))
     989:             $this->after_getCount_callback();
     990:
     991:         self::$logger->debug('<<getCount ['.$count.']');
     992:         return $count;
     993:     }
     994:
     995:     /**
     996:      * Gets the count from the database for the amount of entries in the [tableName]_history table for this business object.  Only call
     997:      * this method on classes where maintainHistory = true, otherwise an exception will be thrown.
     998:      *
     999:      * @return integer
    1000:      * @since 1.2
    1001:      * @throws AlphaException
    1002:      */
    1003:     public function getHistoryCount() {
    1004:         self::$logger->debug('>>getHistoryCount()');
    1005:
    1006:         if(method_exists($this, 'before_getHistoryCount_callback'))
    1007:             $this->before_getHistoryCount_callback();
    1008:
    1009:         global $config;
    1010:
    1011:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1012:         $count = $provider->getHistoryCount();
    1013:
    1014:         if(method_exists($this, 'after_getHistoryCount_callback'))
    1015:             $this->after_getHistoryCount_callback();
    1016:
    1017:         self::$logger->debug('<<getHistoryCount ['.$count.']');
    1018:         return $count;
    1019:     }
    1020:
    1021:     /**
    1022:      * Gets the OID for the object in zero-padded format (same as getOID()).  This version is designed
    1023:      * to be overridden in case you want to do something different with the ID of your objects outside
    1024:      * of the standard 11 digit OID sequence used internally in Alpha.
    1025:      *
    1026:      * @return integer 11 digit zero-padded OID value.
    1027:      * @since 1.0
    1028:      */
    1029:     public function getID() {
    1030:         self::$logger->debug('>>getID()');
    1031:         $oid = str_pad($this->OID, 11, '0', STR_PAD_LEFT);
    1032:         self::$logger->debug('<<getID ['.$oid.']');
    1033:         return $oid;
    1034:     }
    1035:
    1036:     /**
    1037:      * Gets the OID for the object in zero-padded format (same as getID()).  This version is final so cannot
    1038:      * be overridden.
    1039:      *
    1040:      * @return integer 11 digit zero-padded OID value.
    1041:      * @since 1.0
    1042:      */
    1043:     public final function getOID() {
    1044:         if(self::$logger == null)
    1045:             self::$logger = new Logger('AlphaDAO');
    1046:         self::$logger->debug('>>getOID()');
    1047:         $oid = str_pad($this->OID, 11, '0', STR_PAD_LEFT);
    1048:         self::$logger->debug('<<getOID ['.$oid.']');
    1049:         return $oid;
    1050:     }
    1051:
    1052:     /**
    1053:      * Method for getting version number of the object.
    1054:      *
    1055:      * @return Integer The object version number.
    1056:      * @since 1.0
    1057:      */
    1058:     public function getVersionNumber() {
    1059:         self::$logger->debug('>>getVersionNumber()');
    1060:         self::$logger->debug('<<getVersionNumber ['.$this->version_num.']');
    1061:         return $this->version_num;
    1062:     }
    1063:
    1064:     /**
    1065:      * Populate all of the enum options for this object from the database.
    1066:      *
    1067:      * @since 1.0
    1068:      * @throws AlphaException
    1069:      */
    1070:     protected function setEnumOptions() {
    1071:         self::$logger->debug('>>setEnumOptions()');
    1072:
    1073:         if(method_exists($this, 'before_setEnumOptions_callback'))
    1074:             $this->before_setEnumOptions_callback();
    1075:
    1076:         global $config;
    1077:
    1078:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1079:         try {
    1080:             $provider->setEnumOptions();
    1081:         }catch (NotImplementedException $e) {
    1082:             self::$logger->info($e->getMessage());
    1083:         }
    1084:
    1085:         self::$logger->debug('<<setEnumOptions');
    1086:     }
    1087:
    1088:     /**
    1089:      * Generic getter method for accessing class properties.  Will use the method get.ucfirst($prop) instead if that
    1090:      * method exists at a child level (by default).  Set $noChildMethods to true if you don't want to use any
    1091:      * get.ucfirst($prop) method even if it exists, false otherwise (default).
    1092:      *
    1093:      * @param string $prop The name of the object property to get.
    1094:      * @param boolean $noChildMethods Set to true if you do not want to use getters in the child object, defaults to false.
    1095:      * @return mixed The property value.
    1096:      * @since 1.0
    1097:      * @throws IllegalArguementException
    1098:      * @throws AlphaException
    1099:      */
    1100:     public function get($prop, $noChildMethods = false) {
    1101:         if(self::$logger == null)
    1102:             self::$logger = new Logger('AlphaDAO');
    1103:
    1104:         self::$logger->debug('>>get(prop=['.$prop.'], noChildMethods=['.$noChildMethods.'])');
    1105:
    1106:         if(method_exists($this, 'before_get_callback'))
    1107:             $this->before_get_callback();
    1108:
    1109:         if(empty($prop))
    1110:             throw new IllegalArguementException('Cannot call get with empty $prop arguement!');
    1111:
    1112:         // handle attributes with a get.ucfirst($prop) method
    1113:         if(!$noChildMethods && method_exists($this, 'get'.ucfirst($prop))) {
    1114:             if(method_exists($this, 'after_get_callback'))
    1115:                 $this->after_get_callback();
    1116:
    1117:             self::$logger->debug('<<get ['.eval('return print_r($this->get'.ucfirst($prop).'(), true);').'])');
    1118:             return eval('return $this->get'.ucfirst($prop).'();');
    1119:         }else{
    1120:             // handle attributes with no dedicated child get.ucfirst($prop) method
    1121:             if(isset($this->$prop) && is_object($this->$prop) && method_exists($this->$prop, 'getValue')) {
    1122:                 if(method_exists($this, 'after_get_callback'))
    1123:                     $this->after_get_callback();
    1124:
    1125:                 // complex types will have a getValue() method, return the value of that
    1126:                 self::$logger->debug('<<get ['.$this->$prop->getValue().'])');
    1127:                 return $this->$prop->getValue();
    1128:             }elseif(isset($this->$prop)) {
    1129:                 if(method_exists($this, 'after_get_callback'))
    1130:                     $this->after_get_callback();
    1131:
    1132:                 // simple types returned as-is
    1133:                 self::$logger->debug('<<get ['.print_r($this->$prop, true).'])');
    1134:                 return $this->$prop;
    1135:             }else{
    1136:                 throw new AlphaException('Could not access the property ['.$prop.'] on the object of class ['.get_class($this).']');
    1137:                 self::$logger->debug('<<get [false])');
    1138:                 return false;
    1139:             }
    1140:         }
    1141:     }
    1142:
    1143:     /**
    1144:      * Generic setter method for setting class properties.  Will use the method set.ucfirst($prop) instead if that
    1145:      * method exists at a child level (by default).  Set $noChildMethods to true if you don't want to use
    1146:      * any get.ucfirst($prop) method even if it exists, false otherwise (default).
    1147:      *
    1148:      * @param string $prop The name of the property to set.
    1149:      * @param mixed $value The value of the property to set.
    1150:      * @param boolean $noChildMethods Set to true if you do not want to use setters in the child object, defaults
    1151:      * to false.
    1152:      * @since 1.0
    1153:      * @throws AlphaException
    1154:      */
    1155:     public function set($prop, $value, $noChildMethods = false) {
    1156:         self::$logger->debug('>>set(prop=['.$prop.'], $value=['.print_r($value, true).'], noChildMethods=['.$noChildMethods.'])');
    1157:
    1158:         if(method_exists($this, 'before_set_callback'))
    1159:             $this->before_set_callback();
    1160:
    1161:         // handle attributes with a set.ucfirst($prop) method
    1162:         if(!$noChildMethods && method_exists($this, 'set'.ucfirst($prop))) {
    1163:             if(method_exists($this, 'after_set_callback'))
    1164:                 $this->after_set_callback();
    1165:
    1166:             eval('$this->set'.ucfirst($prop).'($value);');
    1167:         }else{
    1168:             // handle attributes with no dedicated child set.ucfirst($prop) method
    1169:             if(isset($this->$prop)) {
    1170:                 if(method_exists($this, 'after_set_callback'))
    1171:                     $this->after_set_callback();
    1172:
    1173:                 // complex types will have a setValue() method to call
    1174:                 if (is_object($this->$prop) && get_class($this->$prop) != false) {
    1175:                     if (strtoupper(get_class($this->$prop)) != 'DATE' && strtoupper(get_class($this->$prop)) != 'TIMESTAMP') {
    1176:                         $this->$prop->setValue($value);
    1177:                     }else{
    1178:                         // Date and Timestamp objects have a special setter accepting a string
    1179:                         $this->$prop->populateFromString($value);
    1180:                     }
    1181:                 }else{
    1182:                     // simple types set directly
    1183:                     $this->$prop = $value;
    1184:                 }
    1185:             }else{
    1186:                 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.');
    1187:             }
    1188:         }
    1189:         self::$logger->debug('<<set');
    1190:     }
    1191:
    1192:     /**
    1193:      * Gets the property object rather than the value for complex attributes.  Returns false if
    1194:      * the property exists but is private.
    1195:      *
    1196:      * @param string $prop The name of the property we are getting.
    1197:      * @return AlphaType The complex type object found.
    1198:      * @since 1.0
    1199:      * @throws IllegalArguementException
    1200:      */
    1201:     public function getPropObject($prop) {
    1202:         self::$logger->debug('>>getPropObject(prop=['.$prop.'])');
    1203:
    1204:         if(method_exists($this, 'before_getPropObject_callback'))
    1205:             $this->before_getPropObject_callback();
    1206:
    1207:         // get the class attributes
    1208:         $reflection = new ReflectionClass(get_class($this));
    1209:         $properties = $reflection->getProperties();
    1210:
    1211:         // firstly, check for private
    1212:         $attribute = new ReflectionProperty(get_class($this), $prop);
    1213:
    1214:         if($attribute->isPrivate()) {
    1215:             if(method_exists($this, 'after_getPropObject_callback'))
    1216:                 $this->after_getPropObject_callback();
    1217:
    1218:             self::$logger->debug('<<getPropObject [false]');
    1219:             return false;
    1220:         }
    1221:
    1222:         foreach($properties as $propObj) {
    1223:             $propName = $propObj->name;
    1224:
    1225:             if($prop == $propName) {
    1226:                 if(method_exists($this, 'after_getPropObject_callback'))
    1227:                     $this->after_getPropObject_callback();
    1228:
    1229:                 self::$logger->debug('<<getPropObject ['.var_export($this->$prop, true).']');
    1230:                 return $this->$prop;
    1231:             }
    1232:         }
    1233:         throw new IllegalArguementException('Could not access the property object ['.$prop.'] on the object of class ['.get_class($this).']');
    1234:         self::$logger->debug('<<getPropObject [false]');
    1235:         return false;
    1236:     }
    1237:
    1238:     /**
    1239:      * Checks to see if the table exists in the database for the current business class.
    1240:      *
    1241:      * @param boolean $checkHistoryTable Set to true if you want to check for the existance of the _history table for this DAO.
    1242:      * @return boolean
    1243:      * @since 1.0
    1244:      * @throws AlphaException
    1245:      */
    1246:     public function checkTableExists($checkHistoryTable = false) {
    1247:         self::$logger->debug('>>checkTableExists()');
    1248:
    1249:         if(method_exists($this, 'before_checkTableExists_callback'))
    1250:             $this->before_checkTableExists_callback();
    1251:
    1252:         global $config;
    1253:
    1254:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1255:         $tableExists = $provider->checkTableExists($checkHistoryTable);
    1256:
    1257:         if(method_exists($this, 'after_checkTableExists_callback'))
    1258:             $this->after_checkTableExists_callback();
    1259:
    1260:         self::$logger->debug('<<checkTableExists ['.$tableExists.']');
    1261:         return $tableExists;
    1262:     }
    1263:
    1264:     /**
    1265:      * Static method to check the database and see if the table for the indicated BO class name
    1266:      * exists (assumes table name will be $BOClassName less "Object").
    1267:      *
    1268:      * @param string $BOClassName The name of the business object class we are checking.
    1269:      * @param boolean $checkHistoryTable Set to true if you want to check for the existance of the _history table for this DAO.
    1270:      * @return boolean
    1271:      * @since 1.0
    1272:      * @throws AlphaException
    1273:      */
    1274:     public static function checkBOTableExists($BOClassName, $checkHistoryTable = false) {
    1275:         if(self::$logger == null)
    1276:             self::$logger = new Logger('AlphaDAO');
    1277:         self::$logger->debug('>>checkBOTableExists(BOClassName=['.$BOClassName.'])');
    1278:
    1279:         global $config;
    1280:
    1281:         require_once $config->get('app.root').'alpha/model/'.$config->get('db.provider.name').'.inc';
    1282:         eval('$tableExists = '.$config->get('db.provider.name').'::checkBOTableExists($BOClassName, $checkHistoryTable);');
    1283:
    1284:         self::$logger->debug('<<checkBOTableExists ['.($tableExists ? 'true' : 'false').']');
    1285:         return $tableExists;
    1286:     }
    1287:
    1288:     /**
    1289:      * Checks to see if the table in the database matches (for fields) the business class definition, i.e. if the
    1290:      * database table is in sync with the class definition.
    1291:      *
    1292:      * @return boolean
    1293:      * @since 1.0
    1294:      * @throws AlphaException
    1295:      */
    1296:     public function checkTableNeedsUpdate() {
    1297:         self::$logger->debug('>>checkTableNeedsUpdate()');
    1298:
    1299:         global $config;
    1300:
    1301:         if(method_exists($this, 'before_checkTableNeedsUpdate_callback'))
    1302:             $this->before_checkTableNeedsUpdate_callback();
    1303:
    1304:         $tableExists = $this->checkTableExists();
    1305:
    1306:         if (!$tableExists) {
    1307:             self::$logger->debug('<<checkTableNeedsUpdate [true]');
    1308:             return true;
    1309:         }else{
    1310:
    1311:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1312:             $updateRequired = $provider->checkTableNeedsUpdate();
    1313:
    1314:             if(method_exists($this, 'after_checkTableNeedsUpdate_callback'))
    1315:                 $this->after_checkTableNeedsUpdate_callback();
    1316:
    1317:             self::$logger->debug('<<checkTableNeedsUpdate ['.$updateRequired.']');
    1318:             return $updateRequired;
    1319:         }
    1320:     }
    1321:
    1322:     /**
    1323:      * Returns an array containing any properties on the class which have not been created on the database
    1324:      * table yet.
    1325:      *
    1326:      * @return array An array of missing fields in the database table.
    1327:      * @since 1.0
    1328:      * @throws AlphaException
    1329:      */
    1330:     public function findMissingFields() {
    1331:         self::$logger->debug('>>findMissingFields()');
    1332:
    1333:         global $config;
    1334:
    1335:         if(method_exists($this, 'before_findMissingFields_callback'))
    1336:             $this->before_findMissingFields_callback();
    1337:
    1338:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1339:         $missingFields = $provider->findMissingFields();
    1340:
    1341:         if(method_exists($this, 'after_findMissingFields_callback'))
    1342:             $this->after_findMissingFields_callback();
    1343:
    1344:         self::$logger->debug('<<findMissingFields ['.var_export($missingFields, true).']');
    1345:         return $missingFields;
    1346:     }
    1347:
    1348:     /**
    1349:      * Getter for the TABLE_NAME, which should be set by a child of this class.
    1350:      *
    1351:      * @return string The table name in the database.
    1352:      * @since 1.0
    1353:      * @throws AlphaException
    1354:      */
    1355:     public function getTableName() {
    1356:         self::$logger->debug('>>getTableName()');
    1357:
    1358:         eval('$TABLE_NAME = '.get_class($this).'::TABLE_NAME;');
    1359:
    1360:         if(!empty($TABLE_NAME)) {
    1361:             self::$logger->debug('<<getTableName ['.$TABLE_NAME.']');
    1362:             return $TABLE_NAME;
    1363:         }else{
    1364:             throw new AlphaException('Error: no TABLE_NAME constant set for the class '.get_class($this));
    1365:             self::$logger->debug('<<getTableName []');
    1366:             return '';
    1367:         }
    1368:     }
    1369:
    1370:     /**
    1371:      * Method for getting the OID of the person who created this BO.
    1372:      *
    1373:      * @return Integer The OID of the creator.
    1374:      * @since 1.0
    1375:      */
    1376:     public function getCreatorId() {
    1377:         self::$logger->debug('>>getCreatorId()');
    1378:         self::$logger->debug('<<getCreatorId ['.$this->created_by.']');
    1379:         return $this->created_by;
    1380:     }
    1381:
    1382:     /**
    1383:      * Method for getting the OID of the person who updated this BO.
    1384:      *
    1385:      * @return Integer The OID of the updator.
    1386:      * @since 1.0
    1387:      */
    1388:     public function getUpdatorId() {
    1389:         self::$logger->debug('>>getUpdatorId()');
    1390:         self::$logger->debug('<<getUpdatorId ['.$this->updated_by.']');
    1391:         return $this->updated_by;
    1392:     }
    1393:
    1394:     /**
    1395:      * Method for getting the date/time of when the BO was created.
    1396:      *
    1397:      * @return Timestamp
    1398:      * @since 1.0
    1399:      */
    1400:     public function getCreateTS() {
    1401:         self::$logger->debug('>>getCreateTS()');
    1402:         self::$logger->debug('<<getCreateTS ['.$this->created_ts.']');
    1403:         return $this->created_ts;
    1404:     }
    1405:
    1406:     /**
    1407:      * Method for getting the date/time of when the BO was last updated.
    1408:      *
    1409:      * @return Timestamp
    1410:      * @since 1.0
    1411:      */
    1412:     public function getUpdateTS() {
    1413:         self::$logger->debug('>>getUpdateTS()');
    1414:         self::$logger->debug('<<getUpdateTS ['.$this->updated_ts.']');
    1415:         return $this->updated_ts;
    1416:     }
    1417:
    1418:     /**
    1419:      * Adds the name of the attribute provided to the list of transient (non-saved) attributes for this BO.
    1420:      *
    1421:      * @param string $attributeName The name of the attribute to not save.
    1422:      * @since 1.0
    1423:      */
    1424:     public function markTransient($attributeName) {
    1425:         self::$logger->debug('>>markTransient(attributeName=['.$attributeName.'])');
    1426:         self::$logger->debug('<<markTransient');
    1427:         array_push($this->transientAttributes, $attributeName);
    1428:     }
    1429:
    1430:     /**
    1431:      * Removes the name of the attribute provided from the list of transient (non-saved) attributes for this BO,
    1432:      * ensuring that it will be saved on the next attempt.
    1433:      *
    1434:      * @param string $attributeName The name of the attribute to save.
    1435:      * @since 1.0
    1436:      */
    1437:     public function markPersistent($attributeName) {
    1438:         self::$logger->debug('>>markPersistent(attributeName=['.$attributeName.'])');
    1439:         self::$logger->debug('<<markPersistent');
    1440:         $this->transientAttributes = array_diff($this->transientAttributes, array($attributeName));
    1441:     }
    1442:
    1443:     /**
    1444:      * Adds the name of the attribute(s) provided to the list of unique (constrained) attributes for this BO.
    1445:      *
    1446:      * @param string $attribute1Name The first attribute to mark unique in the database.
    1447:      * @param string $attribute2Name The second attribute to mark unique in the databse (optional, use only for composite keys).
    1448:      * @param string $attribute3Name The third attribute to mark unique in the databse (optional, use only for composite keys).
    1449:      * @since 1.0
    1450:      */
    1451:     protected function markUnique($attribute1Name, $attribute2Name='', $attribute3Name='') {
    1452:         self::$logger->debug('>>markUnique(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'], attribute3Name=['.$attribute3Name.'])');
    1453:
    1454:         if(empty($attribute2Name)) {
    1455:             array_push($this->uniqueAttributes, $attribute1Name);
    1456:         }else{
    1457:             // Process composite unique keys: add them seperated by a + sign
    1458:             if($attribute3Name == '')
    1459:                 $attributes = $attribute1Name.'+'.$attribute2Name;
    1460:             else
    1461:                 $attributes = $attribute1Name.'+'.$attribute2Name.'+'.$attribute3Name;
    1462:
    1463:             array_push($this->uniqueAttributes, $attributes);
    1464:         }
    1465:
    1466:         self::$logger->debug('<<markUnique');
    1467:     }
    1468:
    1469:     /**
    1470:      * Returns the array of names of unique attributes on this BO
    1471:      *
    1472:      * @return array
    1473:      * @since 1.1
    1474:      */
    1475:     public function getUniqueAttributes() {
    1476:         self::$logger->debug('>>getUniqueAttributes()');
    1477:         self::$logger->debug('<<getUniqueAttributes: ['.print_r($this->uniqueAttributes, true).']');
    1478:         return $this->uniqueAttributes;
    1479:     }
    1480:
    1481:     /**
    1482:      * Gets an array of all of the names of the active database indexes for this class.
    1483:      *
    1484:      * @return array An array of database indexes on this table.
    1485:      * @since 1.0
    1486:      * @throws AlphaException
    1487:      */
    1488:     public function getIndexes() {
    1489:         self::$logger->debug('>>getIndexes()');
    1490:
    1491:         global $config;
    1492:
    1493:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1494:         $indexNames = $provider->getIndexes();
    1495:
    1496:         self::$logger->debug('<<getIndexes ['.print_r($indexNames, true).']');
    1497:         return $indexNames;
    1498:     }
    1499:
    1500:     /**
    1501:      * Creates a foreign key constraint (index) in the database on the given attribute.
    1502:      *
    1503:      * @param string $attributeName The name of the attribute to apply the index on.
    1504:      * @param string $relatedClass The name of the related class in the format "NameObject".
    1505:      * @param string $relatedClassAttribute The name of the field to relate to on the related class.
    1506:      * @param bool $allowNullValues For foreign key indexes that don't allow null values, set this to false (default is true).
    1507:      * @since 1.0
    1508:      * @throws FailedIndexCreateException
    1509:      */
    1510:     public function createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute) {
    1511:         self::$logger->debug('>>createForeignIndex(attributeName=['.$attributeName.'], relatedClass=['.$relatedClass.'], relatedClassAttribute=['.$relatedClassAttribute.']');
    1512:
    1513:         global $config;
    1514:
    1515:         if(method_exists($this, 'before_createForeignIndex_callback'))
    1516:             $this->before_createForeignIndex_callback();
    1517:
    1518:         AlphaDAO::loadClassDef($relatedClass);
    1519:         $relatedBO = new $relatedClass;
    1520:         $tableName = $relatedBO->getTableName();
    1521:
    1522:         // if the relation is on itself (table-wise), exit without attempting to create the foreign keys
    1523:         if($this->getTableName() == $tableName) {
    1524:             self::$logger->debug('<<createForeignIndex');
    1525:             return;
    1526:         }
    1527:
    1528:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1529:         $provider->createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute);
    1530:
    1531:         if(method_exists($this, 'after_createForeignIndex_callback'))
    1532:             $this->after_createForeignIndex_callback();
    1533:
    1534:         self::$logger->debug('<<createForeignIndex');
    1535:     }
    1536:
    1537:     /**
    1538:      * Creates a unique index in the database on the given attribute(s).
    1539:      *
    1540:      * @param string $attribute1Name The first attribute to mark unique in the database.
    1541:      * @param string $attribute2Name The second attribute to mark unique in the databse (optional, use only for composite keys).
    1542:      * @param string $attribute3Name The third attribute to mark unique in the databse (optional, use only for composite keys).
    1543:      * @since 1.0
    1544:      * @throws FailedIndexCreateException
    1545:      */
    1546:     public function createUniqueIndex($attribute1Name, $attribute2Name = '', $attribute3Name = '') {
    1547:         self::$logger->debug('>>createUniqueIndex(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'], attribute3Name=['.$attribute3Name.'])');
    1548:
    1549:         if(method_exists($this, 'before_createUniqueIndex_callback'))
    1550:             $this->before_createUniqueIndex_callback();
    1551:
    1552:         global $config;
    1553:
    1554:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1555:         $provider->createUniqueIndex($attribute1Name, $attribute2Name, $attribute3Name);
    1556:
    1557:         if(method_exists($this, 'after_createUniqueIndex_callback'))
    1558:             $this->before_createUniqueIndex_callback();
    1559:
    1560:         self::$logger->debug('<<createUniqueIndex');
    1561:     }
    1562:
    1563:     /**
    1564:      * Gets the data labels array.
    1565:      *
    1566:      * @return array An array of attribute labels.
    1567:      * @since 1.0
    1568:      */
    1569:     public function getDataLabels() {
    1570:         self::$logger->debug('>>getDataLabels()');
    1571:         self::$logger->debug('<<getDataLabels() ['.var_export($this->dataLabels, true).'])');
    1572:         return $this->dataLabels;
    1573:     }
    1574:
    1575:     /**
    1576:      * Sets the data labels array.
    1577:      *
    1578:      * @param array $labels
    1579:      * @throws IllegalArguementException
    1580:      * @since 1.2
    1581:      */
    1582:     public function setDataLabels($labels) {
    1583:         self::$logger->debug('>>setDataLabels(labels=['.print_r($labels, true).'])');
    1584:
    1585:         if(is_array($labels))
    1586:             $this->dataLabels = $labels;
    1587:         else
    1588:             throw new IllegalArguementException('The value ['.print_r($labels, true).'] provided to setDataLabels() is not a valid array!');
    1589:
    1590:         self::$logger->debug('<<setDataLabels()');
    1591:     }
    1592:
    1593:     /**
    1594:      * Gets the data label for the given attribute name.
    1595:      *
    1596:      * @param $att The attribute name to get the label for.
    1597:      * @return string
    1598:      * @since 1.0
    1599:      * @throws IllegalArguementException
    1600:      */
    1601:     public function getDataLabel($att) {
    1602:         self::$logger->debug('>>getDataLabel(att=['.$att.'])');
    1603:
    1604:         if(in_array($att, array_keys($this->dataLabels))) {
    1605:             self::$logger->debug('<<getDataLabel ['.$this->dataLabels[$att].'])');
    1606:             return $this->dataLabels[$att];
    1607:         }else{
    1608:             throw new IllegalArguementException('No data label found on the class ['.get_class($this).'] for the attribute ['.$att.']');
    1609:             self::$logger->debug('<<getDataLabel [])');
    1610:             return '';
    1611:         }
    1612:     }
    1613:
    1614:     /**
    1615:      * Loops over the core and custom BO directories and builds an array of all of the BO class names in the system.
    1616:      *
    1617:      * @return array An array of business object class names.
    1618:      * @since 1.0
    1619:      */
    1620:     public static function getBOClassNames() {
    1621:         if(self::$logger == null)
    1622:             self::$logger = new Logger('AlphaDAO');
    1623:         self::$logger->debug('>>getBOClassNames()');
    1624:
    1625:         global $config;
    1626:
    1627:         $classNameArray = array();
    1628:
    1629:
    1630:         if(file_exists($config->get('app.root').'model')) { // it is possible it has not been created yet...
    1631:             // first get any custom BOs
    1632:             $handle = opendir($config->get('app.root').'model');
    1633:
    1634:             // loop over the business object directory
    1635:             while (false !== ($file = readdir($handle))) {
    1636:                 if (preg_match("/Object.inc/", $file)) {
    1637:                     $classname = substr($file, 0, -4);
    1638:
    1639:                     array_push($classNameArray, $classname);
    1640:                 }
    1641:             }
    1642:         }
    1643:
    1644:         // now loop over the core BOs provided with Alpha
    1645:
    1646:         $handle = opendir($config->get('app.root').'alpha/model');
    1647:
    1648:         // loop over the business object directory
    1649:         while (false !== ($file = readdir($handle))) {
    1650:             if (preg_match("/Object.inc/", $file)) {
    1651:                 $classname = substr($file, 0, -4);
    1652:
    1653:                 array_push($classNameArray, $classname);
    1654:             }
    1655:         }
    1656:
    1657:         asort($classNameArray);
    1658:         self::$logger->debug('<<getBOClassNames ['.var_export($classNameArray, true).']');
    1659:         return $classNameArray;
    1660:     }
    1661:
    1662:     /**
    1663:      * Get the array of default attribute names.
    1664:      *
    1665:      * @return array An array of attribute names.
    1666:      * @since 1.0
    1667:      */
    1668:     public function getDefaultAttributes() {
    1669:         self::$logger->debug('>>getDefaultAttributes()');
    1670:         self::$logger->debug('<<getDefaultAttributes ['.var_export($this->defaultAttributes, true).']');
    1671:         return $this->defaultAttributes;
    1672:     }
    1673:
    1674:     /**
    1675:      * Get the array of transient attribute names.
    1676:      *
    1677:      * @return array An array of attribute names.
    1678:      * @since 1.0
    1679:      */
    1680:     public function getTransientAttributes() {
    1681:         self::$logger->debug('>>getTransientAttributes()');
    1682:         self::$logger->debug('<<getTransientAttributes ['.var_export($this->transientAttributes, true).']');
    1683:         return $this->transientAttributes;
    1684:     }
    1685:
    1686:     /**
    1687:      * Get the array of persistent attribute names, i.e. those that are saved in the database.
    1688:      *
    1689:      * @return array An array of attribute names.
    1690:      * @since 1.0
    1691:      */
    1692:     public function getPersistentAttributes() {
    1693:         self::$logger->debug('>>getPersistentAttributes()');
    1694:
    1695:         $attributes = array();
    1696:
    1697:         // get the class attributes
    1698:         $reflection = new ReflectionClass(get_class($this));
    1699:         $properties = $reflection->getProperties();
    1700:
    1701:         foreach($properties as $propObj) {
    1702:             $propName = $propObj->name;
    1703:
    1704:             // filter transient attributes
    1705:             if(!in_array($propName, $this->transientAttributes)) {
    1706:                 array_push($attributes, $propName);
    1707:             }
    1708:         }
    1709:
    1710:         self::$logger->debug('<<getPersistentAttributes ['.var_export($attributes, true).']');
    1711:         return $attributes;
    1712:     }
    1713:
    1714:     /**
    1715:      * Setter for the Object ID (OID)
    1716:      *
    1717:      * @param integer $OID The Object ID.
    1718:      * @since 1.0
    1719:      */
    1720:     public function setOID($OID) {
    1721:         self::$logger->debug('>>setOID(OID=['.$OID.'])');
    1722:         self::$logger->debug('<<setOID');
    1723:         $this->OID = $OID;
    1724:     }
    1725:
    1726:     /**
    1727:      * Inspector to see if the business object is transient (not presently stored in the database).
    1728:      *
    1729:      * @return boolean
    1730:      * @since 1.0
    1731:      */
    1732:     public function isTransient() {
    1733:         self::$logger->debug('>>isTransient()');
    1734:
    1735:         if(empty($this->OID) || !isset($this->OID) || $this->OID == '00000000000') {
    1736:             self::$logger->debug('<<isTransient [true]');
    1737:             return true;
    1738:         }else{
    1739:             self::$logger->debug('<<isTransient [false]');
    1740:             return false;
    1741:         }
    1742:     }
    1743:
    1744:     /**
    1745:      * Get the last database query run on this object.
    1746:      *
    1747:      * @return string An SQL query string.
    1748:      * @since 1.0
    1749:      */
    1750:     public function getLastQuery() {
    1751:         self::$logger->debug('>>getLastQuery()');
    1752:         self::$logger->debug('<<getLastQuery ['.$this->lastQuery.']');
    1753:         return $this->lastQuery;
    1754:     }
    1755:
    1756:     /**
    1757:      * Unsets all of the attributes of this object to null.
    1758:      *
    1759:      * @since 1.0
    1760:      */
    1761:     private function clear() {
    1762:         self::$logger->debug('>>clear()');
    1763:
    1764:         // get the class attributes
    1765:         $reflection = new ReflectionClass(get_class($this));
    1766:         $properties = $reflection->getProperties();
    1767:
    1768:         foreach($properties as $propObj) {
    1769:             $propName = $propObj->name;
    1770:             if(!$propObj->isPrivate())
    1771:                 unset($this->$propName);
    1772:         }
    1773:
    1774:         self::$logger->debug('<<clear');
    1775:     }
    1776:
    1777:     /**
    1778:      * Reloads the object from the database, overwritting any attribute values in memory.
    1779:      *
    1780:      * @since 1.0
    1781:      * @throws AlphaException
    1782:      */
    1783:     public function reload() {
    1784:         self::$logger->debug('>>reload()');
    1785:
    1786:         if(!$this->isTransient()) {
    1787:             $this->load($this->getOID());
    1788:         }else{
    1789:             throw new AlphaException('Cannot reload transient object from database!');
    1790:         }
    1791:         self::$logger->debug('<<reload');
    1792:     }
    1793:
    1794:     /**
    1795:      * Loads the definition from the file system for the BO class name provided.
    1796:      *
    1797:      * @param string $classname The name of the business object class name.
    1798:      * @since 1.0
    1799:      * @throws IllegalArguementException
    1800:      */
    1801:     public static function loadClassDef($classname) {
    1802:         if(self::$logger == null)
    1803:             self::$logger = new Logger('AlphaDAO');
    1804:         self::$logger->debug('>>loadClassDef(classname=['.$classname.'])');
    1805:
    1806:         global $config;
    1807:
    1808:         if(file_exists($config->get('app.root').'model/'.$classname.'.inc'))
    1809:             require_once $config->get('app.root').'model/'.$classname.'.inc';
    1810:         elseif(file_exists($config->get('app.root').'alpha/model/'.$classname.'.inc'))
    1811:             require_once $config->get('app.root').'alpha/model/'.$classname.'.inc';
    1812:         elseif(file_exists($config->get('app.root').'alpha/model/types/'.$classname.'.inc'))
    1813:             require_once $config->get('app.root').'alpha/model/types/'.$classname.'.inc';
    1814:         else
    1815:             throw new IllegalArguementException('The class ['.$classname.'] is not defined anywhere!');
    1816:
    1817:         self::$logger->debug('<<loadClassDef');
    1818:     }
    1819:
    1820:     /**
    1821:      * Checks if the definition for the BO class name provided exists on the file system.
    1822:      *
    1823:      * @param string $classname The name of the business object class name.
    1824:      * @return boolean
    1825:      * @since 1.0
    1826:      */
    1827:     public static function checkClassDefExists($classname) {
    1828:         if(self::$logger == null)
    1829:             self::$logger = new Logger('AlphaDAO');
    1830:         self::$logger->debug('>>checkClassDefExists(classname=['.$classname.'])');
    1831:
    1832:         global $config;
    1833:
    1834:         $exists = false;
    1835:
    1836:         if(file_exists($config->get('app.root').'model/'.$classname.'.inc'))
    1837:             $exists = true;
    1838:         if(file_exists($config->get('app.root').'alpha/model/'.$classname.'.inc'))
    1839:             $exists = true;
    1840:         if(file_exists($config->get('app.root').'alpha/model/types/'.$classname.'.inc'))
    1841:             $exists = true;
    1842:
    1843:         self::$logger->debug('<<checkClassDefExists ['.$exists.']');
    1844:         return $exists;
    1845:     }
    1846:
    1847:     /**
    1848:      * Checks that a record exists for the BO in the database.
    1849:      *
    1850:      * @param int $OID The Object ID of the object we want to see whether it exists or not.
    1851:      * @return boolean
    1852:      * @since 1.0
    1853:      * @throws AlphaException
    1854:      */
    1855:     public function checkRecordExists($OID) {
    1856:         self::$logger->debug('>>checkRecordExists(OID=['.$OID.'])');
    1857:
    1858:         if(method_exists($this, 'before_checkRecordExists_callback'))
    1859:             $this->before_checkRecordExists_callback();
    1860:
    1861:         global $config;
    1862:
    1863:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1864:         $recordExists = $provider->checkRecordExists($OID);
    1865:
    1866:         if(method_exists($this, 'after_checkRecordExists_callback'))
    1867:             $this->after_checkRecordExists_callback();
    1868:
    1869:         self::$logger->debug('<<checkRecordExists ['.$recordExists.']');
    1870:         return $recordExists;
    1871:     }
    1872:
    1873:     /**
    1874:      * Checks to see if the table name matches the classname, and if not if the table
    1875:      * name matches the classname name of another BO, i.e. the table is used to store
    1876:      * multiple types of BOs.
    1877:      *
    1878:      * @return bool
    1879:      * @since 1.0
    1880:      * @throws BadBOTableNameException
    1881:      */
    1882:     public function isTableOverloaded() {
    1883:         self::$logger->debug('>>isTableOverloaded()');
    1884:
    1885:         global $config;
    1886:
    1887:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1888:         $isOverloaded = $provider->isTableOverloaded();
    1889:
    1890:         self::$logger->debug('<<isTableOverloaded ['.$isOverloaded.']');
    1891:         return $isOverloaded;
    1892:     }
    1893:
    1894:     /**
    1895:      * Starts a new database transaction.
    1896:      *
    1897:      * @param $BO The AlphaDAO instance to pass to the database provider. Leave empty to have a new PersonObject passed.
    1898:      * @since 1.0
    1899:      * @throws AlphaException
    1900:      */
    1901:     public static function begin($BO = null) {
    1902:         if(self::$logger == null)
    1903:             self::$logger = new Logger('AlphaDAO');
    1904:         self::$logger->debug('>>begin()');
    1905:
    1906:         global $config;
    1907:
    1908:         if(isset($BO))
    1909:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $BO);
    1910:         else
    1911:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), new PersonObject());
    1912:
    1913:         try{
    1914:             $provider->begin();
    1915:         }catch (Exception $e) {
    1916:             throw new AlphaException('Error beginning a new transaction, error is ['.$e->getMessage().']');
    1917:         }
    1918:
    1919:         self::$logger->debug('<<begin');
    1920:     }
    1921:
    1922:     /**
    1923:      * Commits the current database transaction.
    1924:      *
    1925:      * @param $BO The AlphaDAO instance to pass to the database provider. Leave empty to have a new PersonObject passed.
    1926:      * @since 1.0
    1927:      * @throws FailedSaveException
    1928:      */
    1929:     public static function commit($BO = null) {
    1930:         if(self::$logger == null)
    1931:             self::$logger = new Logger('AlphaDAO');
    1932:         self::$logger->debug('>>commit()');
    1933:
    1934:         global $config;
    1935:
    1936:         if(isset($BO))
    1937:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $BO);
    1938:         else
    1939:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), new PersonObject());
    1940:
    1941:         try{
    1942:             $provider->commit();
    1943:         }catch (Exception $e) {
    1944:             throw new FailedSaveException('Error commiting a transaction, error is ['.$e->getMessage().']');
    1945:         }
    1946:
    1947:         self::$logger->debug('<<commit');
    1948:     }
    1949:
    1950:     /**
    1951:      * Aborts the current database transaction.
    1952:      *
    1953:      * @param $BO The AlphaDAO instance to pass to the database provider. Leave empty to have a new PersonObject passed.
    1954:      * @since 1.0
    1955:      * @throws AlphaException
    1956:      */
    1957:     public static function rollback($BO = null) {
    1958:         if(self::$logger == null)
    1959:             self::$logger = new Logger('AlphaDAO');
    1960:         self::$logger->debug('>>rollback()');
    1961:
    1962:         global $config;
    1963:
    1964:         if(isset($BO))
    1965:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $BO);
    1966:         else
    1967:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), new PersonObject());
    1968:
    1969:         try{
    1970:             $provider->rollback();
    1971:         }catch (Exception $e) {
    1972:             throw new FailedSaveException('Error aborting a transaction, error is ['.$e->getMessage().']');
    1973:         }
    1974:
    1975:         self::$logger->debug('<<rollback');
    1976:     }
    1977:
    1978:     /**
    1979:      * Static method that tries to determine if the system database has been installed or not.
    1980:      *
    1981:      * @return boolean
    1982:      * @since 1.0
    1983:      */
    1984:     public static function isInstalled() {
    1985:         if(self::$logger == null)
    1986:             self::$logger = new Logger('AlphaDAO');
    1987:         self::$logger->debug('>>isInstalled()');
    1988:
    1989:         global $config;
    1990:
    1991:         /*
    1992:          * Install conditions are:
    1993:          *
    1994:          * 1. person table exists
    1995:          * 2. rights table exists
    1996:          */
    1997:         if(AlphaDAO::checkBOTableExists('PersonObject') && AlphaDAO::checkBOTableExists('RightsObject')) {
    1998:             self::$logger->debug('<<isInstalled [true]');
    1999:             return true;
    2000:         }else{
    2001:             self::$logger->debug('<<isInstalled [false]');
    2002:             return false;
    2003:         }
    2004:     }
    2005:
    2006:     /**
    2007:      * Returns true if the BO has a Relation property called tags, false otherwise.
    2008:      *
    2009:      * @return boolean
    2010:      * @since 1.0
    2011:      */
    2012:     public function isTagged() {
    2013:         if(isset($this->taggedAttributes) && isset($this->tags) && $this->tags instanceof Relation)
    2014:             return true;
    2015:         else
    2016:             return false;
    2017:     }
    2018:
    2019:     /**
    2020:      * Setter for the BO version number.
    2021:      *
    2022:      * @param integer $versionNumber The version number.
    2023:      * @since 1.0
    2024:      */
    2025:     private function setVersion($versionNumber) {
    2026:         $this->version_num->setValue($versionNumber);
    2027:     }
    2028:
    2029:     /**
    2030:      * Cast a BO to another type of BO.  A new BO will be returned with the same OID and
    2031:      * version_num as the old BO, so this is NOT a true cast but is a copy.  All attribute
    2032:      * values will be copied accross.
    2033:      *
    2034:      * @param string $targetClassName The name of the target BO class.
    2035:      * @param AlphaDAO $originalBO The original business object.
    2036:      * @return AlphaDAO The new business object resulting from the cast.
    2037:      * @since 1.0
    2038:      */
    2039:     public function cast($targetClassName, $originalBO) {
    2040:         AlphaDAO::loadClassDef($targetClassName);
    2041:
    2042:         $BO = new $targetClassName;
    2043:         $BO->setOID($originalBO->getOID());
    2044:         $BO->setVersion($originalBO->getVersion());
    2045:
    2046:         // get the class attributes
    2047:         $originalBOreflection = new ReflectionClass(get_class($originalBO));
    2048:         $originalBOproperties = $originalBOreflection->getProperties();
    2049:         $newBOreflection = new ReflectionClass($targetClassName);
    2050:         $newBOproperties = $newBOreflection->getProperties();
    2051:
    2052:         // copy the property values from the old BO to the new BO
    2053:
    2054:         if(count($originalBOproperties) < count($newBOproperties)) {
    2055:             // the original BO is smaller, so loop over its properties
    2056:             foreach($originalBOproperties as $propObj) {
    2057:                 $propName = $propObj->name;
    2058:                 if(!in_array($propName, $this->transientAttributes))
    2059:                     $BO->set($propName, $originalBO->get($propName));
    2060:             }
    2061:         }else{
    2062:             // the new BO is smaller, so loop over its properties
    2063:             foreach($newBOproperties as $propObj) {
    2064:                 $propName = $propObj->name;
    2065:                 if(!in_array($propName, $this->transientAttributes))
    2066:                     $BO->set($propName, $originalBO->get($propName));
    2067:             }
    2068:         }
    2069:
    2070:         return $BO;
    2071:     }
    2072:
    2073:     /**
    2074:      * Converts "BusinessObject" to "Business" and returns.
    2075:      *
    2076:      * @return string
    2077:      * @since 1.0
    2078:      */
    2079:     public function getFriendlyClassName() {
    2080:         $name = substr(get_class($this), 0, -6);
    2081:
    2082:         return $name;
    2083:     }
    2084:
    2085:     /**
    2086:      * Check to see if an attribute exists on the BO.
    2087:      *
    2088:      * @param $attribute The attribute name.
    2089:      * @return boolean
    2090:      * @since 1.0
    2091:      */
    2092:     public function hasAttribute($attribute) {
    2093:         try{
    2094:             $exists = $this->$attribute;
    2095:             return true;
    2096:         }catch(Exception $e) {
    2097:             return false;
    2098:         }
    2099:     }
    2100:
    2101:     /**
    2102:      * Stores the business object to the configured cache instance
    2103:      *
    2104:      * @since 1.1
    2105:      */
    2106:     public function addToCache() {
    2107:         self::$logger->debug('>>addToCache()');
    2108:         global $config;
    2109:
    2110:         try {
    2111:             $cache = AlphaCacheProviderFactory::getInstance($config->get('cache.provider.name'));
    2112:             $cache->set(get_class($this).'-'.$this->getOID(), $this, 3600);
    2113:
    2114:         }catch(Exception $e) {
    2115:             self::$logger->error('Error while attempting to store a business object to the ['.$config->get('cache.provider.name').']
    2116:                 instance: ['.$e->getMessage().']');
    2117:         }
    2118:
    2119:         self::$logger->debug('<<addToCache');
    2120:     }
    2121:
    2122:     /**
    2123:      * Removes the business object from the configured cache instance
    2124:      *
    2125:      * @since 1.1
    2126:      */
    2127:     public function removeFromCache() {
    2128:         self::$logger->debug('>>removeFromCache()');
    2129:         global $config;
    2130:
    2131:         try {
    2132:             $cache = AlphaCacheProviderFactory::getInstance($config->get('cache.provider.name'));
    2133:             $cache->delete(get_class($this).'-'.$this->getOID());
    2134:         }catch(Exception $e) {
    2135:             self::$logger->error('Error while attempting to remove a business object from ['.$config->get('cache.provider.name').']
    2136:                 instance: ['.$e->getMessage().']');
    2137:         }
    2138:
    2139:         self::$logger->debug('<<removeFromCache');
    2140:     }
    2141:
    2142:     /**
    2143:      * Attempts to load the business object from the configured cache instance
    2144:      *
    2145:      * @since 1.1
    2146:      * @return boolean
    2147:      */
    2148:     public function loadFromCache() {
    2149:         self::$logger->debug('>>loadFromCache()');
    2150:         global $config;
    2151:
    2152:         try {
    2153:             $cache = AlphaCacheProviderFactory::getInstance($config->get('cache.provider.name'));
    2154:             $BO = $cache->get(get_class($this).'-'.$this->getOID());
    2155:
    2156:             if(!$BO) {
    2157:                 self::$logger->debug('Cache miss on key ['.get_class($this).'-'.$this->getOID().']');
    2158:                 self::$logger->debug('<<loadFromCache: [false]');
    2159:                 return false;
    2160:             }else{
    2161:                 // get the class attributes
    2162:                 $reflection = new ReflectionClass(get_class($this));
    2163:                 $properties = $reflection->getProperties();
    2164:
    2165:                 foreach($properties as $propObj) {
    2166:                     $propName = $propObj->name;
    2167:
    2168:                     // filter transient attributes
    2169:                     if(!in_array($propName, $this->transientAttributes)) {
    2170:                         $this->set($propName, $BO->get($propName, true));
    2171:                     }elseif(!$propObj->isPrivate() && isset($this->$propName) && $this->$propName instanceof Relation) {
    2172:                         $prop = $this->getPropObject($propName);
    2173:
    2174:                         // handle the setting of ONE-TO-MANY relation values
    2175:                         if($prop->getRelationType() == 'ONE-TO-MANY') {
    2176:                             $this->set($propObj->name, $this->getOID());
    2177:                         }
    2178:                     }
    2179:                 }
    2180:
    2181:                 self::$logger->debug('<<loadFromCache: [true]');
    2182:                 return true;
    2183:             }
    2184:         }catch(Exception $e) {
    2185:             self::$logger->error('Error while attempting to load a business object from ['.$config->get('cache.provider.name').']
    2186:              instance: ['.$e->getMessage().']');
    2187:
    2188:             self::$logger->debug('<<loadFromCache: [false]');
    2189:             return false;
    2190:         }
    2191:     }
    2192:
    2193:     /**
    2194:      * Sets the last query executed on this business object
    2195:      *
    2196:      * @param string $query
    2197:      * @since 1.1
    2198:      */
    2199:     public function setLastQuery($query) {
    2200:         self::$logger->sql($query);
    2201:         $this->lastQuery = $query;
    2202:     }
    2203:
    2204:     /**
    2205:      * Re-initialize the static logger property on the BO after de-serialize, as PHP does
    2206:      * not serialize static properties.
    2207:      *
    2208:      * @since 1.2
    2209:      */
    2210:     public function __wakeup() {
    2211:         if(self::$logger == null)
    2212:             self::$logger = new Logger(get_class($this));
    2213:     }
    2214:
    2215:     /**
    2216:      * Sets maintainHistory attribute on this DAO
    2217:      *
    2218:      * @param boolean $maintainHistory
    2219:      * @throws IllegalArguementException
    2220:      * @since 1.2
    2221:      */
    2222:     public function setMaintainHistory($maintainHistory) {
    2223:         if(!is_bool($maintainHistory))
    2224:             throw new IllegalArguementException('Non-boolean value ['.$maintainHistory.'] passed to setMaintainHistory method!');
    2225:
    2226:         $this->maintainHistory = $maintainHistory;
    2227:     }
    2228:
    2229:     /**
    2230:      * Gets the value of the  maintainHistory attribute
    2231:      *
    2232:      * @returns boolean
    2233:      * @since 1.2
    2234:      */
    2235:     public function getMaintainHistory() {
    2236:         return $this->maintainHistory;
    2237:     }
    2238: }
    2239:
    2240: ?>