       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 1608 2012-12-17 15:08:19Z 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:      * Gets the maximum OID value from the database for this class type.
     906:      *
     907:      * @return integer The maximum OID value in the class table.
     908:      * @since 1.0
     909:      * @throws AlphaException
     910:      */
     911:     public function getMAX() {
     912:         self::$logger->debug('>>getMAX()');
     913:
     914:         if(method_exists($this, 'before_getMAX_callback'))
     915:             $this->before_getMAX_callback();
     916:
     917:         global $config;
     918:
     919:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     920:         $max = $provider->getMAX();
     921:
     922:         if(method_exists($this, 'after_getMAX_callback'))
     923:             $this->after_getMAX_callback();
     924:
     925:         self::$logger->debug('<<getMAX ['.$max.']');
     926:         return $max;
     927:     }
     928:
     929:     /**
     930:      * Gets the count from the database for the amount of objects of this class.
     931:      *
     932:      * @param array $atributes The attributes to count the objects by (optional).
     933:      * @param array $values The values of the attributes to count the objects by (optional).
     934:      * @return integer
     935:      * @since 1.0
     936:      * @throws AlphaException
     937:      */
     938:     public function getCount($attributes=array(), $values=array()) {
     939:         self::$logger->debug('>>getCount(attributes=['.var_export($attributes, true).'], values=['.var_export($values, true).'])');
     940:
     941:         if(method_exists($this, 'before_getCount_callback'))
     942:             $this->before_getCount_callback();
     943:
     944:         global $config;
     945:
     946:         if(!is_array($attributes) || !is_array($values)) {
     947:             throw new IllegalArguementException('Illegal arrays attributes=['.var_export($attributes, true).'] and values=['.var_export($values, true).'] provided to loadAllByAttributes');
     948:         }
     949:
     950:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     951:         $count = $provider->getCount($attributes, $values);
     952:
     953:         if(method_exists($this, 'after_getCount_callback'))
     954:             $this->after_getCount_callback();
     955:
     956:         self::$logger->debug('<<getCount ['.$count.']');
     957:         return $count;
     958:     }
     959:
     960:     /**
     961:      * Gets the count from the database for the amount of entries in the [tableName]_history table for this business object.  Only call
     962:      * this method on classes where maintainHistory = true, otherwise an exception will be thrown.
     963:      *
     964:      * @return integer
     965:      * @since 1.2
     966:      * @throws AlphaException
     967:      */
     968:     public function getHistoryCount() {
     969:         self::$logger->debug('>>getHistoryCount()');
     970:
     971:         if(method_exists($this, 'before_getHistoryCount_callback'))
     972:             $this->before_getHistoryCount_callback();
     973:
     974:         global $config;
     975:
     976:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
     977:         $count = $provider->getHistoryCount();
     978:
     979:         if(method_exists($this, 'after_getHistoryCount_callback'))
     980:             $this->after_getHistoryCount_callback();
     981:
     982:         self::$logger->debug('<<getHistoryCount ['.$count.']');
     983:         return $count;
     984:     }
     985:
     986:     /**
     987:      * Gets the OID for the object in zero-padded format (same as getOID()).  This version is designed
     988:      * to be overridden in case you want to do something different with the ID of your objects outside
     989:      * of the standard 11 digit OID sequence used internally in Alpha.
     990:      *
     991:      * @return integer 11 digit zero-padded OID value.
     992:      * @since 1.0
     993:      */
     994:     public function getID() {
     995:         self::$logger->debug('>>getID()');
     996:         $oid = str_pad($this->OID, 11, '0', STR_PAD_LEFT);
     997:         self::$logger->debug('<<getID ['.$oid.']');
     998:         return $oid;
     999:     }
    1000:
    1001:     /**
    1002:      * Gets the OID for the object in zero-padded format (same as getID()).  This version is final so cannot
    1003:      * be overridden.
    1004:      *
    1005:      * @return integer 11 digit zero-padded OID value.
    1006:      * @since 1.0
    1007:      */
    1008:     public final function getOID() {
    1009:         if(self::$logger == null)
    1010:             self::$logger = new Logger('AlphaDAO');
    1011:         self::$logger->debug('>>getOID()');
    1012:         $oid = str_pad($this->OID, 11, '0', STR_PAD_LEFT);
    1013:         self::$logger->debug('<<getOID ['.$oid.']');
    1014:         return $oid;
    1015:     }
    1016:
    1017:     /**
    1018:      * Method for getting version number of the object.
    1019:      *
    1020:      * @return Integer The object version number.
    1021:      * @since 1.0
    1022:      */
    1023:     public function getVersionNumber() {
    1024:         self::$logger->debug('>>getVersionNumber()');
    1025:         self::$logger->debug('<<getVersionNumber ['.$this->version_num.']');
    1026:         return $this->version_num;
    1027:     }
    1028:
    1029:     /**
    1030:      * Populate all of the enum options for this object from the database.
    1031:      *
    1032:      * @since 1.0
    1033:      * @throws AlphaException
    1034:      */
    1035:     protected function setEnumOptions() {
    1036:         self::$logger->debug('>>setEnumOptions()');
    1037:
    1038:         if(method_exists($this, 'before_setEnumOptions_callback'))
    1039:             $this->before_setEnumOptions_callback();
    1040:
    1041:         global $config;
    1042:
    1043:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1044:         try {
    1045:             $provider->setEnumOptions();
    1046:         }catch (NotImplementedException $e) {
    1047:             self::$logger->info($e->getMessage());
    1048:         }
    1049:
    1050:         self::$logger->debug('<<setEnumOptions');
    1051:     }
    1052:
    1053:     /**
    1054:      * Generic getter method for accessing class properties.  Will use the method get.ucfirst($prop) instead if that
    1055:      * method exists at a child level (by default).  Set $noChildMethods to true if you don't want to use any
    1056:      * get.ucfirst($prop) method even if it exists, false otherwise (default).
    1057:      *
    1058:      * @param string $prop The name of the object property to get.
    1059:      * @param boolean $noChildMethods Set to true if you do not want to use getters in the child object, defaults to false.
    1060:      * @return mixed The property value.
    1061:      * @since 1.0
    1062:      * @throws IllegalArguementException
    1063:      * @throws AlphaException
    1064:      */
    1065:     public function get($prop, $noChildMethods = false) {
    1066:         if(self::$logger == null)
    1067:             self::$logger = new Logger('AlphaDAO');
    1068:
    1069:         self::$logger->debug('>>get(prop=['.$prop.'], noChildMethods=['.$noChildMethods.'])');
    1070:
    1071:         if(method_exists($this, 'before_get_callback'))
    1072:             $this->before_get_callback();
    1073:
    1074:         if(empty($prop))
    1075:             throw new IllegalArguementException('Cannot call get with empty $prop arguement!');
    1076:
    1077:         // handle attributes with a get.ucfirst($prop) method
    1078:         if(!$noChildMethods && method_exists($this, 'get'.ucfirst($prop))) {
    1079:             if(method_exists($this, 'after_get_callback'))
    1080:                 $this->after_get_callback();
    1081:
    1082:             self::$logger->debug('<<get ['.eval('return print_r($this->get'.ucfirst($prop).'(), true);').'])');
    1083:             return eval('return $this->get'.ucfirst($prop).'();');
    1084:         }else{
    1085:             // handle attributes with no dedicated child get.ucfirst($prop) method
    1086:             if(isset($this->$prop) && is_object($this->$prop) && method_exists($this->$prop, 'getValue')) {
    1087:                 if(method_exists($this, 'after_get_callback'))
    1088:                     $this->after_get_callback();
    1089:
    1090:                 // complex types will have a getValue() method, return the value of that
    1091:                 self::$logger->debug('<<get ['.$this->$prop->getValue().'])');
    1092:                 return $this->$prop->getValue();
    1093:             }elseif(isset($this->$prop)) {
    1094:                 if(method_exists($this, 'after_get_callback'))
    1095:                     $this->after_get_callback();
    1096:
    1097:                 // simple types returned as-is
    1098:                 self::$logger->debug('<<get ['.print_r($this->$prop, true).'])');
    1099:                 return $this->$prop;
    1100:             }else{
    1101:                 throw new AlphaException('Could not access the property ['.$prop.'] on the object of class ['.get_class($this).']');
    1102:                 self::$logger->debug('<<get [false])');
    1103:                 return false;
    1104:             }
    1105:         }
    1106:     }
    1107:
    1108:     /**
    1109:      * Generic setter method for setting class properties.  Will use the method set.ucfirst($prop) instead if that
    1110:      * method exists at a child level (by default).  Set $noChildMethods to true if you don't want to use
    1111:      * any get.ucfirst($prop) method even if it exists, false otherwise (default).
    1112:      *
    1113:      * @param string $prop The name of the property to set.
    1114:      * @param mixed $value The value of the property to set.
    1115:      * @param boolean $noChildMethods Set to true if you do not want to use setters in the child object, defaults
    1116:      * to false.
    1117:      * @since 1.0
    1118:      * @throws AlphaException
    1119:      */
    1120:     public function set($prop, $value, $noChildMethods = false) {
    1121:         self::$logger->debug('>>set(prop=['.$prop.'], $value=['.print_r($value, true).'], noChildMethods=['.$noChildMethods.'])');
    1122:
    1123:         if(method_exists($this, 'before_set_callback'))
    1124:             $this->before_set_callback();
    1125:
    1126:         // handle attributes with a set.ucfirst($prop) method
    1127:         if(!$noChildMethods && method_exists($this, 'set'.ucfirst($prop))) {
    1128:             if(method_exists($this, 'after_set_callback'))
    1129:                 $this->after_set_callback();
    1130:
    1131:             eval('$this->set'.ucfirst($prop).'($value);');
    1132:         }else{
    1133:             // handle attributes with no dedicated child set.ucfirst($prop) method
    1134:             if(isset($this->$prop)) {
    1135:                 if(method_exists($this, 'after_set_callback'))
    1136:                     $this->after_set_callback();
    1137:
    1138:                 // complex types will have a setValue() method to call
    1139:                 if (is_object($this->$prop) && get_class($this->$prop) != false) {
    1140:                     if (strtoupper(get_class($this->$prop)) != 'DATE' && strtoupper(get_class($this->$prop)) != 'TIMESTAMP') {
    1141:                         $this->$prop->setValue($value);
    1142:                     }else{
    1143:                         // Date and Timestamp objects have a special setter accepting a string
    1144:                         $this->$prop->populateFromString($value);
    1145:                     }
    1146:                 }else{
    1147:                     // simple types set directly
    1148:                     $this->$prop = $value;
    1149:                 }
    1150:             }else{
    1151:                 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.');
    1152:             }
    1153:         }
    1154:         self::$logger->debug('<<set');
    1155:     }
    1156:
    1157:     /**
    1158:      * Gets the property object rather than the value for complex attributes.  Returns false if
    1159:      * the property exists but is private.
    1160:      *
    1161:      * @param string $prop The name of the property we are getting.
    1162:      * @return AlphaType The complex type object found.
    1163:      * @since 1.0
    1164:      * @throws IllegalArguementException
    1165:      */
    1166:     public function getPropObject($prop) {
    1167:         self::$logger->debug('>>getPropObject(prop=['.$prop.'])');
    1168:
    1169:         if(method_exists($this, 'before_getPropObject_callback'))
    1170:             $this->before_getPropObject_callback();
    1171:
    1172:         // get the class attributes
    1173:         $reflection = new ReflectionClass(get_class($this));
    1174:         $properties = $reflection->getProperties();
    1175:
    1176:         // firstly, check for private
    1177:         $attribute = new ReflectionProperty(get_class($this), $prop);
    1178:
    1179:         if($attribute->isPrivate()) {
    1180:             if(method_exists($this, 'after_getPropObject_callback'))
    1181:                 $this->after_getPropObject_callback();
    1182:
    1183:             self::$logger->debug('<<getPropObject [false]');
    1184:             return false;
    1185:         }
    1186:
    1187:         foreach($properties as $propObj) {
    1188:             $propName = $propObj->name;
    1189:
    1190:             if($prop == $propName) {
    1191:                 if(method_exists($this, 'after_getPropObject_callback'))
    1192:                     $this->after_getPropObject_callback();
    1193:
    1194:                 self::$logger->debug('<<getPropObject ['.var_export($this->$prop, true).']');
    1195:                 return $this->$prop;
    1196:             }
    1197:         }
    1198:         throw new IllegalArguementException('Could not access the property object ['.$prop.'] on the object of class ['.get_class($this).']');
    1199:         self::$logger->debug('<<getPropObject [false]');
    1200:         return false;
    1201:     }
    1202:
    1203:     /**
    1204:      * Checks to see if the table exists in the database for the current business class.
    1205:      *
    1206:      * @param boolean $checkHistoryTable Set to true if you want to check for the existance of the _history table for this DAO.
    1207:      * @return boolean
    1208:      * @since 1.0
    1209:      * @throws AlphaException
    1210:      */
    1211:     public function checkTableExists($checkHistoryTable = false) {
    1212:         self::$logger->debug('>>checkTableExists()');
    1213:
    1214:         if(method_exists($this, 'before_checkTableExists_callback'))
    1215:             $this->before_checkTableExists_callback();
    1216:
    1217:         global $config;
    1218:
    1219:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1220:         $tableExists = $provider->checkTableExists($checkHistoryTable);
    1221:
    1222:         if(method_exists($this, 'after_checkTableExists_callback'))
    1223:             $this->after_checkTableExists_callback();
    1224:
    1225:         self::$logger->debug('<<checkTableExists ['.$tableExists.']');
    1226:         return $tableExists;
    1227:     }
    1228:
    1229:     /**
    1230:      * Static method to check the database and see if the table for the indicated BO class name
    1231:      * exists (assumes table name will be $BOClassName less "Object").
    1232:      *
    1233:      * @param string $BOClassName The name of the business object class we are checking.
    1234:      * @param boolean $checkHistoryTable Set to true if you want to check for the existance of the _history table for this DAO.
    1235:      * @return boolean
    1236:      * @since 1.0
    1237:      * @throws AlphaException
    1238:      */
    1239:     public static function checkBOTableExists($BOClassName, $checkHistoryTable = false) {
    1240:         if(self::$logger == null)
    1241:             self::$logger = new Logger('AlphaDAO');
    1242:         self::$logger->debug('>>checkBOTableExists(BOClassName=['.$BOClassName.'])');
    1243:
    1244:         global $config;
    1245:
    1246:         require_once $config->get('app.root').'alpha/model/'.$config->get('db.provider.name').'.inc';
    1247:         eval('$tableExists = '.$config->get('db.provider.name').'::checkBOTableExists($BOClassName, $checkHistoryTable);');
    1248:
    1249:         self::$logger->debug('<<checkBOTableExists ['.($tableExists ? 'true' : 'false').']');
    1250:         return $tableExists;
    1251:     }
    1252:
    1253:     /**
    1254:      * Checks to see if the table in the database matches (for fields) the business class definition, i.e. if the
    1255:      * database table is in sync with the class definition.
    1256:      *
    1257:      * @return boolean
    1258:      * @since 1.0
    1259:      * @throws AlphaException
    1260:      */
    1261:     public function checkTableNeedsUpdate() {
    1262:         self::$logger->debug('>>checkTableNeedsUpdate()');
    1263:
    1264:         global $config;
    1265:
    1266:         if(method_exists($this, 'before_checkTableNeedsUpdate_callback'))
    1267:             $this->before_checkTableNeedsUpdate_callback();
    1268:
    1269:         $tableExists = $this->checkTableExists();
    1270:
    1271:         if (!$tableExists) {
    1272:             self::$logger->debug('<<checkTableNeedsUpdate [true]');
    1273:             return true;
    1274:         }else{
    1275:
    1276:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1277:             $updateRequired = $provider->checkTableNeedsUpdate();
    1278:
    1279:             if(method_exists($this, 'after_checkTableNeedsUpdate_callback'))
    1280:                 $this->after_checkTableNeedsUpdate_callback();
    1281:
    1282:             self::$logger->debug('<<checkTableNeedsUpdate ['.$updateRequired.']');
    1283:             return $updateRequired;
    1284:         }
    1285:     }
    1286:
    1287:     /**
    1288:      * Returns an array containing any properties on the class which have not been created on the database
    1289:      * table yet.
    1290:      *
    1291:      * @return array An array of missing fields in the database table.
    1292:      * @since 1.0
    1293:      * @throws AlphaException
    1294:      */
    1295:     public function findMissingFields() {
    1296:         self::$logger->debug('>>findMissingFields()');
    1297:
    1298:         global $config;
    1299:
    1300:         if(method_exists($this, 'before_findMissingFields_callback'))
    1301:             $this->before_findMissingFields_callback();
    1302:
    1303:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1304:         $missingFields = $provider->findMissingFields();
    1305:
    1306:         if(method_exists($this, 'after_findMissingFields_callback'))
    1307:             $this->after_findMissingFields_callback();
    1308:
    1309:         self::$logger->debug('<<findMissingFields ['.var_export($missingFields, true).']');
    1310:         return $missingFields;
    1311:     }
    1312:
    1313:     /**
    1314:      * Getter for the TABLE_NAME, which should be set by a child of this class.
    1315:      *
    1316:      * @return string The table name in the database.
    1317:      * @since 1.0
    1318:      * @throws AlphaException
    1319:      */
    1320:     public function getTableName() {
    1321:         self::$logger->debug('>>getTableName()');
    1322:
    1323:         eval('$TABLE_NAME = '.get_class($this).'::TABLE_NAME;');
    1324:
    1325:         if(!empty($TABLE_NAME)) {
    1326:             self::$logger->debug('<<getTableName ['.$TABLE_NAME.']');
    1327:             return $TABLE_NAME;
    1328:         }else{
    1329:             throw new AlphaException('Error: no TABLE_NAME constant set for the class '.get_class($this));
    1330:             self::$logger->debug('<<getTableName []');
    1331:             return '';
    1332:         }
    1333:     }
    1334:
    1335:     /**
    1336:      * Method for getting the OID of the person who created this BO.
    1337:      *
    1338:      * @return Integer The OID of the creator.
    1339:      * @since 1.0
    1340:      */
    1341:     public function getCreatorId() {
    1342:         self::$logger->debug('>>getCreatorId()');
    1343:         self::$logger->debug('<<getCreatorId ['.$this->created_by.']');
    1344:         return $this->created_by;
    1345:     }
    1346:
    1347:     /**
    1348:      * Method for getting the OID of the person who updated this BO.
    1349:      *
    1350:      * @return Integer The OID of the updator.
    1351:      * @since 1.0
    1352:      */
    1353:     public function getUpdatorId() {
    1354:         self::$logger->debug('>>getUpdatorId()');
    1355:         self::$logger->debug('<<getUpdatorId ['.$this->updated_by.']');
    1356:         return $this->updated_by;
    1357:     }
    1358:
    1359:     /**
    1360:      * Method for getting the date/time of when the BO was created.
    1361:      *
    1362:      * @return Timestamp
    1363:      * @since 1.0
    1364:      */
    1365:     public function getCreateTS() {
    1366:         self::$logger->debug('>>getCreateTS()');
    1367:         self::$logger->debug('<<getCreateTS ['.$this->created_ts.']');
    1368:         return $this->created_ts;
    1369:     }
    1370:
    1371:     /**
    1372:      * Method for getting the date/time of when the BO was last updated.
    1373:      *
    1374:      * @return Timestamp
    1375:      * @since 1.0
    1376:      */
    1377:     public function getUpdateTS() {
    1378:         self::$logger->debug('>>getUpdateTS()');
    1379:         self::$logger->debug('<<getUpdateTS ['.$this->updated_ts.']');
    1380:         return $this->updated_ts;
    1381:     }
    1382:
    1383:     /**
    1384:      * Adds the name of the attribute provided to the list of transient (non-saved) attributes for this BO.
    1385:      *
    1386:      * @param string $attributeName The name of the attribute to not save.
    1387:      * @since 1.0
    1388:      */
    1389:     public function markTransient($attributeName) {
    1390:         self::$logger->debug('>>markTransient(attributeName=['.$attributeName.'])');
    1391:         self::$logger->debug('<<markTransient');
    1392:         array_push($this->transientAttributes, $attributeName);
    1393:     }
    1394:
    1395:     /**
    1396:      * Removes the name of the attribute provided from the list of transient (non-saved) attributes for this BO,
    1397:      * ensuring that it will be saved on the next attempt.
    1398:      *
    1399:      * @param string $attributeName The name of the attribute to save.
    1400:      * @since 1.0
    1401:      */
    1402:     public function markPersistent($attributeName) {
    1403:         self::$logger->debug('>>markPersistent(attributeName=['.$attributeName.'])');
    1404:         self::$logger->debug('<<markPersistent');
    1405:         $this->transientAttributes = array_diff($this->transientAttributes, array($attributeName));
    1406:     }
    1407:
    1408:     /**
    1409:      * Adds the name of the attribute(s) provided to the list of unique (constrained) attributes for this BO.
    1410:      *
    1411:      * @param string $attribute1Name The first attribute to mark unique in the database.
    1412:      * @param string $attribute2Name The second attribute to mark unique in the databse (optional, use only for composite keys).
    1413:      * @param string $attribute3Name The third attribute to mark unique in the databse (optional, use only for composite keys).
    1414:      * @since 1.0
    1415:      */
    1416:     protected function markUnique($attribute1Name, $attribute2Name='', $attribute3Name='') {
    1417:         self::$logger->debug('>>markUnique(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'], attribute3Name=['.$attribute3Name.'])');
    1418:
    1419:         if(empty($attribute2Name)) {
    1420:             array_push($this->uniqueAttributes, $attribute1Name);
    1421:         }else{
    1422:             // Process composite unique keys: add them seperated by a + sign
    1423:             if($attribute3Name == '')
    1424:                 $attributes = $attribute1Name.'+'.$attribute2Name;
    1425:             else
    1426:                 $attributes = $attribute1Name.'+'.$attribute2Name.'+'.$attribute3Name;
    1427:
    1428:             array_push($this->uniqueAttributes, $attributes);
    1429:         }
    1430:
    1431:         self::$logger->debug('<<markUnique');
    1432:     }
    1433:
    1434:     /**
    1435:      * Returns the array of names of unique attributes on this BO
    1436:      *
    1437:      * @return array
    1438:      * @since 1.1
    1439:      */
    1440:     public function getUniqueAttributes() {
    1441:         self::$logger->debug('>>getUniqueAttributes()');
    1442:         self::$logger->debug('<<getUniqueAttributes: ['.print_r($this->uniqueAttributes, true).']');
    1443:         return $this->uniqueAttributes;
    1444:     }
    1445:
    1446:     /**
    1447:      * Gets an array of all of the names of the active database indexes for this class.
    1448:      *
    1449:      * @return array An array of database indexes on this table.
    1450:      * @since 1.0
    1451:      * @throws AlphaException
    1452:      */
    1453:     public function getIndexes() {
    1454:         self::$logger->debug('>>getIndexes()');
    1455:
    1456:         global $config;
    1457:
    1458:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1459:         $indexNames = $provider->getIndexes();
    1460:
    1461:         self::$logger->debug('<<getIndexes ['.print_r($indexNames, true).']');
    1462:         return $indexNames;
    1463:     }
    1464:
    1465:     /**
    1466:      * Creates a foreign key constraint (index) in the database on the given attribute.
    1467:      *
    1468:      * @param string $attributeName The name of the attribute to apply the index on.
    1469:      * @param string $relatedClass The name of the related class in the format "NameObject".
    1470:      * @param string $relatedClassAttribute The name of the field to relate to on the related class.
    1471:      * @param bool $allowNullValues For foreign key indexes that don't allow null values, set this to false (default is true).
    1472:      * @since 1.0
    1473:      * @throws FailedIndexCreateException
    1474:      */
    1475:     public function createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute) {
    1476:         self::$logger->debug('>>createForeignIndex(attributeName=['.$attributeName.'], relatedClass=['.$relatedClass.'], relatedClassAttribute=['.$relatedClassAttribute.']');
    1477:
    1478:         global $config;
    1479:
    1480:         if(method_exists($this, 'before_createForeignIndex_callback'))
    1481:             $this->before_createForeignIndex_callback();
    1482:
    1483:         AlphaDAO::loadClassDef($relatedClass);
    1484:         $relatedBO = new $relatedClass;
    1485:         $tableName = $relatedBO->getTableName();
    1486:
    1487:         // if the relation is on itself (table-wise), exit without attempting to create the foreign keys
    1488:         if($this->getTableName() == $tableName) {
    1489:             self::$logger->debug('<<createForeignIndex');
    1490:             return;
    1491:         }
    1492:
    1493:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1494:         $provider->createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute);
    1495:
    1496:         if(method_exists($this, 'after_createForeignIndex_callback'))
    1497:             $this->after_createForeignIndex_callback();
    1498:
    1499:         self::$logger->debug('<<createForeignIndex');
    1500:     }
    1501:
    1502:     /**
    1503:      * Creates a unique index in the database on the given attribute(s).
    1504:      *
    1505:      * @param string $attribute1Name The first attribute to mark unique in the database.
    1506:      * @param string $attribute2Name The second attribute to mark unique in the databse (optional, use only for composite keys).
    1507:      * @param string $attribute3Name The third attribute to mark unique in the databse (optional, use only for composite keys).
    1508:      * @since 1.0
    1509:      * @throws FailedIndexCreateException
    1510:      */
    1511:     public function createUniqueIndex($attribute1Name, $attribute2Name = '', $attribute3Name = '') {
    1512:         self::$logger->debug('>>createUniqueIndex(attribute1Name=['.$attribute1Name.'], attribute2Name=['.$attribute2Name.'], attribute3Name=['.$attribute3Name.'])');
    1513:
    1514:         if(method_exists($this, 'before_createUniqueIndex_callback'))
    1515:             $this->before_createUniqueIndex_callback();
    1516:
    1517:         global $config;
    1518:
    1519:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1520:         $provider->createUniqueIndex($attribute1Name, $attribute2Name, $attribute3Name);
    1521:
    1522:         if(method_exists($this, 'after_createUniqueIndex_callback'))
    1523:             $this->before_createUniqueIndex_callback();
    1524:
    1525:         self::$logger->debug('<<createUniqueIndex');
    1526:     }
    1527:
    1528:     /**
    1529:      * Gets the data labels array.
    1530:      *
    1531:      * @return array An array of attribute labels.
    1532:      * @since 1.0
    1533:      */
    1534:     public function getDataLabels() {
    1535:         self::$logger->debug('>>getDataLabels()');
    1536:         self::$logger->debug('<<getDataLabels() ['.var_export($this->dataLabels, true).'])');
    1537:         return $this->dataLabels;
    1538:     }
    1539:
    1540:     /**
    1541:      * Sets the data labels array.
    1542:      *
    1543:      * @param array $labels
    1544:      * @throws IllegalArguementException
    1545:      * @since 1.2
    1546:      */
    1547:     public function setDataLabels($labels) {
    1548:         self::$logger->debug('>>setDataLabels(labels=['.print_r($labels, true).'])');
    1549:
    1550:         if(is_array($labels))
    1551:             $this->dataLabels = $labels;
    1552:         else
    1553:             throw new IllegalArguementException('The value ['.print_r($labels, true).'] provided to setDataLabels() is not a valid array!');
    1554:
    1555:         self::$logger->debug('<<setDataLabels()');
    1556:     }
    1557:
    1558:     /**
    1559:      * Gets the data label for the given attribute name.
    1560:      *
    1561:      * @param $att The attribute name to get the label for.
    1562:      * @return string
    1563:      * @since 1.0
    1564:      * @throws IllegalArguementException
    1565:      */
    1566:     public function getDataLabel($att) {
    1567:         self::$logger->debug('>>getDataLabel(att=['.$att.'])');
    1568:
    1569:         if(in_array($att, array_keys($this->dataLabels))) {
    1570:             self::$logger->debug('<<getDataLabel ['.$this->dataLabels[$att].'])');
    1571:             return $this->dataLabels[$att];
    1572:         }else{
    1573:             throw new IllegalArguementException('No data label found on the class ['.get_class($this).'] for the attribute ['.$att.']');
    1574:             self::$logger->debug('<<getDataLabel [])');
    1575:             return '';
    1576:         }
    1577:     }
    1578:
    1579:     /**
    1580:      * Loops over the core and custom BO directories and builds an array of all of the BO class names in the system.
    1581:      *
    1582:      * @return array An array of business object class names.
    1583:      * @since 1.0
    1584:      */
    1585:     public static function getBOClassNames() {
    1586:         if(self::$logger == null)
    1587:             self::$logger = new Logger('AlphaDAO');
    1588:         self::$logger->debug('>>getBOClassNames()');
    1589:
    1590:         global $config;
    1591:
    1592:         $classNameArray = array();
    1593:
    1594:
    1595:         if(file_exists($config->get('app.root').'model')) { // it is possible it has not been created yet...
    1596:             // first get any custom BOs
    1597:             $handle = opendir($config->get('app.root').'model');
    1598:
    1599:             // loop over the business object directory
    1600:             while (false !== ($file = readdir($handle))) {
    1601:                 if (preg_match("/Object.inc/", $file)) {
    1602:                     $classname = substr($file, 0, -4);
    1603:
    1604:                     array_push($classNameArray, $classname);
    1605:                 }
    1606:             }
    1607:         }
    1608:
    1609:         // now loop over the core BOs provided with Alpha
    1610:
    1611:         $handle = opendir($config->get('app.root').'alpha/model');
    1612:
    1613:         // loop over the business object directory
    1614:         while (false !== ($file = readdir($handle))) {
    1615:             if (preg_match("/Object.inc/", $file)) {
    1616:                 $classname = substr($file, 0, -4);
    1617:
    1618:                 array_push($classNameArray, $classname);
    1619:             }
    1620:         }
    1621:
    1622:         asort($classNameArray);
    1623:         self::$logger->debug('<<getBOClassNames ['.var_export($classNameArray, true).']');
    1624:         return $classNameArray;
    1625:     }
    1626:
    1627:     /**
    1628:      * Get the array of default attribute names.
    1629:      *
    1630:      * @return array An array of attribute names.
    1631:      * @since 1.0
    1632:      */
    1633:     public function getDefaultAttributes() {
    1634:         self::$logger->debug('>>getDefaultAttributes()');
    1635:         self::$logger->debug('<<getDefaultAttributes ['.var_export($this->defaultAttributes, true).']');
    1636:         return $this->defaultAttributes;
    1637:     }
    1638:
    1639:     /**
    1640:      * Get the array of transient attribute names.
    1641:      *
    1642:      * @return array An array of attribute names.
    1643:      * @since 1.0
    1644:      */
    1645:     public function getTransientAttributes() {
    1646:         self::$logger->debug('>>getTransientAttributes()');
    1647:         self::$logger->debug('<<getTransientAttributes ['.var_export($this->transientAttributes, true).']');
    1648:         return $this->transientAttributes;
    1649:     }
    1650:
    1651:     /**
    1652:      * Get the array of persistent attribute names, i.e. those that are saved in the database.
    1653:      *
    1654:      * @return array An array of attribute names.
    1655:      * @since 1.0
    1656:      */
    1657:     public function getPersistentAttributes() {
    1658:         self::$logger->debug('>>getPersistentAttributes()');
    1659:
    1660:         $attributes = array();
    1661:
    1662:         // get the class attributes
    1663:         $reflection = new ReflectionClass(get_class($this));
    1664:         $properties = $reflection->getProperties();
    1665:
    1666:         foreach($properties as $propObj) {
    1667:             $propName = $propObj->name;
    1668:
    1669:             // filter transient attributes
    1670:             if(!in_array($propName, $this->transientAttributes)) {
    1671:                 array_push($attributes, $propName);
    1672:             }
    1673:         }
    1674:
    1675:         self::$logger->debug('<<getPersistentAttributes ['.var_export($attributes, true).']');
    1676:         return $attributes;
    1677:     }
    1678:
    1679:     /**
    1680:      * Setter for the Object ID (OID)
    1681:      *
    1682:      * @param integer $OID The Object ID.
    1683:      * @since 1.0
    1684:      */
    1685:     public function setOID($OID) {
    1686:         self::$logger->debug('>>setOID(OID=['.$OID.'])');
    1687:         self::$logger->debug('<<setOID');
    1688:         $this->OID = $OID;
    1689:     }
    1690:
    1691:     /**
    1692:      * Inspector to see if the business object is transient (not presently stored in the database).
    1693:      *
    1694:      * @return boolean
    1695:      * @since 1.0
    1696:      */
    1697:     public function isTransient() {
    1698:         self::$logger->debug('>>isTransient()');
    1699:
    1700:         if(empty($this->OID) || !isset($this->OID) || $this->OID == '00000000000') {
    1701:             self::$logger->debug('<<isTransient [true]');
    1702:             return true;
    1703:         }else{
    1704:             self::$logger->debug('<<isTransient [false]');
    1705:             return false;
    1706:         }
    1707:     }
    1708:
    1709:     /**
    1710:      * Get the last database query run on this object.
    1711:      *
    1712:      * @return string An SQL query string.
    1713:      * @since 1.0
    1714:      */
    1715:     public function getLastQuery() {
    1716:         self::$logger->debug('>>getLastQuery()');
    1717:         self::$logger->debug('<<getLastQuery ['.$this->lastQuery.']');
    1718:         return $this->lastQuery;
    1719:     }
    1720:
    1721:     /**
    1722:      * Unsets all of the attributes of this object to null.
    1723:      *
    1724:      * @since 1.0
    1725:      */
    1726:     private function clear() {
    1727:         self::$logger->debug('>>clear()');
    1728:
    1729:         // get the class attributes
    1730:         $reflection = new ReflectionClass(get_class($this));
    1731:         $properties = $reflection->getProperties();
    1732:
    1733:         foreach($properties as $propObj) {
    1734:             $propName = $propObj->name;
    1735:             if(!$propObj->isPrivate())
    1736:                 unset($this->$propName);
    1737:         }
    1738:
    1739:         self::$logger->debug('<<clear');
    1740:     }
    1741:
    1742:     /**
    1743:      * Reloads the object from the database, overwritting any attribute values in memory.
    1744:      *
    1745:      * @since 1.0
    1746:      * @throws AlphaException
    1747:      */
    1748:     public function reload() {
    1749:         self::$logger->debug('>>reload()');
    1750:
    1751:         if(!$this->isTransient()) {
    1752:             $this->load($this->getOID());
    1753:         }else{
    1754:             throw new AlphaException('Cannot reload transient object from database!');
    1755:         }
    1756:         self::$logger->debug('<<reload');
    1757:     }
    1758:
    1759:     /**
    1760:      * Loads the definition from the file system for the BO class name provided.
    1761:      *
    1762:      * @param string $classname The name of the business object class name.
    1763:      * @since 1.0
    1764:      * @throws IllegalArguementException
    1765:      */
    1766:     public static function loadClassDef($classname) {
    1767:         if(self::$logger == null)
    1768:             self::$logger = new Logger('AlphaDAO');
    1769:         self::$logger->debug('>>loadClassDef(classname=['.$classname.'])');
    1770:
    1771:         global $config;
    1772:
    1773:         if(file_exists($config->get('app.root').'model/'.$classname.'.inc'))
    1774:             require_once $config->get('app.root').'model/'.$classname.'.inc';
    1775:         elseif(file_exists($config->get('app.root').'alpha/model/'.$classname.'.inc'))
    1776:             require_once $config->get('app.root').'alpha/model/'.$classname.'.inc';
    1777:         elseif(file_exists($config->get('app.root').'alpha/model/types/'.$classname.'.inc'))
    1778:             require_once $config->get('app.root').'alpha/model/types/'.$classname.'.inc';
    1779:         else
    1780:             throw new IllegalArguementException('The class ['.$classname.'] is not defined anywhere!');
    1781:
    1782:         self::$logger->debug('<<loadClassDef');
    1783:     }
    1784:
    1785:     /**
    1786:      * Checks if the definition for the BO class name provided exists on the file system.
    1787:      *
    1788:      * @param string $classname The name of the business object class name.
    1789:      * @return boolean
    1790:      * @since 1.0
    1791:      */
    1792:     public static function checkClassDefExists($classname) {
    1793:         if(self::$logger == null)
    1794:             self::$logger = new Logger('AlphaDAO');
    1795:         self::$logger->debug('>>checkClassDefExists(classname=['.$classname.'])');
    1796:
    1797:         global $config;
    1798:
    1799:         $exists = false;
    1800:
    1801:         if(file_exists($config->get('app.root').'model/'.$classname.'.inc'))
    1802:             $exists = true;
    1803:         if(file_exists($config->get('app.root').'alpha/model/'.$classname.'.inc'))
    1804:             $exists = true;
    1805:         if(file_exists($config->get('app.root').'alpha/model/types/'.$classname.'.inc'))
    1806:             $exists = true;
    1807:
    1808:         self::$logger->debug('<<checkClassDefExists ['.$exists.']');
    1809:         return $exists;
    1810:     }
    1811:
    1812:     /**
    1813:      * Checks that a record exists for the BO in the database.
    1814:      *
    1815:      * @param int $OID The Object ID of the object we want to see whether it exists or not.
    1816:      * @return boolean
    1817:      * @since 1.0
    1818:      * @throws AlphaException
    1819:      */
    1820:     public function checkRecordExists($OID) {
    1821:         self::$logger->debug('>>checkRecordExists(OID=['.$OID.'])');
    1822:
    1823:         if(method_exists($this, 'before_checkRecordExists_callback'))
    1824:             $this->before_checkRecordExists_callback();
    1825:
    1826:         global $config;
    1827:
    1828:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1829:         $recordExists = $provider->checkRecordExists($OID);
    1830:
    1831:         if(method_exists($this, 'after_checkRecordExists_callback'))
    1832:             $this->after_checkRecordExists_callback();
    1833:
    1834:         self::$logger->debug('<<checkRecordExists ['.$recordExists.']');
    1835:         return $recordExists;
    1836:     }
    1837:
    1838:     /**
    1839:      * Checks to see if the table name matches the classname, and if not if the table
    1840:      * name matches the classname name of another BO, i.e. the table is used to store
    1841:      * multiple types of BOs.
    1842:      *
    1843:      * @return bool
    1844:      * @since 1.0
    1845:      * @throws BadBOTableNameException
    1846:      */
    1847:     public function isTableOverloaded() {
    1848:         self::$logger->debug('>>isTableOverloaded()');
    1849:
    1850:         global $config;
    1851:
    1852:         $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $this);
    1853:         $isOverloaded = $provider->isTableOverloaded();
    1854:
    1855:         self::$logger->debug('<<isTableOverloaded ['.$isOverloaded.']');
    1856:         return $isOverloaded;
    1857:     }
    1858:
    1859:     /**
    1860:      * Starts a new database transaction.
    1861:      *
    1862:      * @param $BO The AlphaDAO instance to pass to the database provider. Leave empty to have a new PersonObject passed.
    1863:      * @since 1.0
    1864:      * @throws AlphaException
    1865:      */
    1866:     public static function begin($BO = null) {
    1867:         if(self::$logger == null)
    1868:             self::$logger = new Logger('AlphaDAO');
    1869:         self::$logger->debug('>>begin()');
    1870:
    1871:         global $config;
    1872:
    1873:         if(isset($BO))
    1874:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $BO);
    1875:         else
    1876:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), new PersonObject());
    1877:
    1878:         try{
    1879:             $provider->begin();
    1880:         }catch (Exception $e) {
    1881:             throw new AlphaException('Error beginning a new transaction, error is ['.$e->getMessage().']');
    1882:         }
    1883:
    1884:         self::$logger->debug('<<begin');
    1885:     }
    1886:
    1887:     /**
    1888:      * Commits the current database transaction.
    1889:      *
    1890:      * @param $BO The AlphaDAO instance to pass to the database provider. Leave empty to have a new PersonObject passed.
    1891:      * @since 1.0
    1892:      * @throws FailedSaveException
    1893:      */
    1894:     public static function commit($BO = null) {
    1895:         if(self::$logger == null)
    1896:             self::$logger = new Logger('AlphaDAO');
    1897:         self::$logger->debug('>>commit()');
    1898:
    1899:         global $config;
    1900:
    1901:         if(isset($BO))
    1902:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $BO);
    1903:         else
    1904:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), new PersonObject());
    1905:
    1906:         try{
    1907:             $provider->commit();
    1908:         }catch (Exception $e) {
    1909:             throw new FailedSaveException('Error commiting a transaction, error is ['.$e->getMessage().']');
    1910:         }
    1911:
    1912:         self::$logger->debug('<<commit');
    1913:     }
    1914:
    1915:     /**
    1916:      * Aborts the current database transaction.
    1917:      *
    1918:      * @param $BO The AlphaDAO instance to pass to the database provider. Leave empty to have a new PersonObject passed.
    1919:      * @since 1.0
    1920:      * @throws AlphaException
    1921:      */
    1922:     public static function rollback($BO = null) {
    1923:         if(self::$logger == null)
    1924:             self::$logger = new Logger('AlphaDAO');
    1925:         self::$logger->debug('>>rollback()');
    1926:
    1927:         global $config;
    1928:
    1929:         if(isset($BO))
    1930:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), $BO);
    1931:         else
    1932:             $provider = AlphaDAOProviderFactory::getInstance($config->get('db.provider.name'), new PersonObject());
    1933:
    1934:         try{
    1935:             $provider->rollback();
    1936:         }catch (Exception $e) {
    1937:             throw new FailedSaveException('Error aborting a transaction, error is ['.$e->getMessage().']');
    1938:         }
    1939:
    1940:         self::$logger->debug('<<rollback');
    1941:     }
    1942:
    1943:     /**
    1944:      * Static method that tries to determine if the system database has been installed or not.
    1945:      *
    1946:      * @return boolean
    1947:      * @since 1.0
    1948:      */
    1949:     public static function isInstalled() {
    1950:         if(self::$logger == null)
    1951:             self::$logger = new Logger('AlphaDAO');
    1952:         self::$logger->debug('>>isInstalled()');
    1953:
    1954:         global $config;
    1955:
    1956:         /*
    1957:          * Install conditions are:
    1958:          *
    1959:          * 1. person table exists
    1960:          * 2. rights table exists
    1961:          */
    1962:         if(AlphaDAO::checkBOTableExists('PersonObject') && AlphaDAO::checkBOTableExists('RightsObject')) {
    1963:             self::$logger->debug('<<isInstalled [true]');
    1964:             return true;
    1965:         }else{
    1966:             self::$logger->debug('<<isInstalled [false]');
    1967:             return false;
    1968:         }
    1969:     }
    1970:
    1971:     /**
    1972:      * Returns true if the BO has a Relation property called tags, false otherwise.
    1973:      *
    1974:      * @return boolean
    1975:      * @since 1.0
    1976:      */
    1977:     public function isTagged() {
    1978:         if(isset($this->taggedAttributes) && isset($this->tags) && $this->tags instanceof Relation)
    1979:             return true;
    1980:         else
    1981:             return false;
    1982:     }
    1983:
    1984:     /**
    1985:      * Setter for the BO version number.
    1986:      *
    1987:      * @param integer $versionNumber The version number.
    1988:      * @since 1.0
    1989:      */
    1990:     private function setVersion($versionNumber) {
    1991:         $this->version_num->setValue($versionNumber);
    1992:     }
    1993:
    1994:     /**
    1995:      * Cast a BO to another type of BO.  A new BO will be returned with the same OID and
    1996:      * version_num as the old BO, so this is NOT a true cast but is a copy.  All attribute
    1997:      * values will be copied accross.
    1998:      *
    1999:      * @param string $targetClassName The name of the target BO class.
    2000:      * @param AlphaDAO $originalBO The original business object.
    2001:      * @return AlphaDAO The new business object resulting from the cast.
    2002:      * @since 1.0
    2003:      */
    2004:     public function cast($targetClassName, $originalBO) {
    2005:         AlphaDAO::loadClassDef($targetClassName);
    2006:
    2007:         $BO = new $targetClassName;
    2008:         $BO->setOID($originalBO->getOID());
    2009:         $BO->setVersion($originalBO->getVersion());
    2010:
    2011:         // get the class attributes
    2012:         $originalBOreflection = new ReflectionClass(get_class($originalBO));
    2013:         $originalBOproperties = $originalBOreflection->getProperties();
    2014:         $newBOreflection = new ReflectionClass($targetClassName);
    2015:         $newBOproperties = $newBOreflection->getProperties();
    2016:
    2017:         // copy the property values from the old BO to the new BO
    2018:
    2019:         if(count($originalBOproperties) < count($newBOproperties)) {
    2020:             // the original BO is smaller, so loop over its properties
    2021:             foreach($originalBOproperties as $propObj) {
    2022:                 $propName = $propObj->name;
    2023:                 if(!in_array($propName, $this->transientAttributes))
    2024:                     $BO->set($propName, $originalBO->get($propName));
    2025:             }
    2026:         }else{
    2027:             // the new BO is smaller, so loop over its properties
    2028:             foreach($newBOproperties as $propObj) {
    2029:                 $propName = $propObj->name;
    2030:                 if(!in_array($propName, $this->transientAttributes))
    2031:                     $BO->set($propName, $originalBO->get($propName));
    2032:             }
    2033:         }
    2034:
    2035:         return $BO;
    2036:     }
    2037:
    2038:     /**
    2039:      * Converts "BusinessObject" to "Business" and returns.
    2040:      *
    2041:      * @return string
    2042:      * @since 1.0
    2043:      */
    2044:     public function getFriendlyClassName() {
    2045:         $name = substr(get_class($this), 0, -6);
    2046:
    2047:         return $name;
    2048:     }
    2049:
    2050:     /**
    2051:      * Check to see if an attribute exists on the BO.
    2052:      *
    2053:      * @param $attribute The attribute name.
    2054:      * @return boolean
    2055:      * @since 1.0
    2056:      */
    2057:     public function hasAttribute($attribute) {
    2058:         try{
    2059:             $exists = $this->$attribute;
    2060:             return true;
    2061:         }catch(Exception $e) {
    2062:             return false;
    2063:         }
    2064:     }
    2065:
    2066:     /**
    2067:      * Stores the business object to the configured cache instance
    2068:      *
    2069:      * @since 1.1
    2070:      */
    2071:     public function addToCache() {
    2072:         self::$logger->debug('>>addToCache()');
    2073:         global $config;
    2074:
    2075:         try {
    2076:             $cache = AlphaCacheProviderFactory::getInstance($config->get('cache.provider.name'));
    2077:             $cache->set(get_class($this).'-'.$this->getOID(), $this, 3600);
    2078:
    2079:         }catch(Exception $e) {
    2080:             self::$logger->error('Error while attempting to store a business object to the ['.$config->get('cache.provider.name').']
    2081:                 instance: ['.$e->getMessage().']');
    2082:         }
    2083:
    2084:         self::$logger->debug('<<addToCache');
    2085:     }
    2086:
    2087:     /**
    2088:      * Removes the business object from the configured cache instance
    2089:      *
    2090:      * @since 1.1
    2091:      */
    2092:     public function removeFromCache() {
    2093:         self::$logger->debug('>>removeFromCache()');
    2094:         global $config;
    2095:
    2096:         try {
    2097:             $cache = AlphaCacheProviderFactory::getInstance($config->get('cache.provider.name'));
    2098:             $cache->delete(get_class($this).'-'.$this->getOID());
    2099:         }catch(Exception $e) {
    2100:             self::$logger->error('Error while attempting to remove a business object from ['.$config->get('cache.provider.name').']
    2101:                 instance: ['.$e->getMessage().']');
    2102:         }
    2103:
    2104:         self::$logger->debug('<<removeFromCache');
    2105:     }
    2106:
    2107:     /**
    2108:      * Attempts to load the business object from the configured cache instance
    2109:      *
    2110:      * @since 1.1
    2111:      * @return boolean
    2112:      */
    2113:     public function loadFromCache() {
    2114:         self::$logger->debug('>>loadFromCache()');
    2115:         global $config;
    2116:
    2117:         try {
    2118:             $cache = AlphaCacheProviderFactory::getInstance($config->get('cache.provider.name'));
    2119:             $BO = $cache->get(get_class($this).'-'.$this->getOID());
    2120:
    2121:             if(!$BO) {
    2122:                 self::$logger->debug('Cache miss on key ['.get_class($this).'-'.$this->getOID().']');
    2123:                 self::$logger->debug('<<loadFromCache: [false]');
    2124:                 return false;
    2125:             }else{
    2126:                 // get the class attributes
    2127:                 $reflection = new ReflectionClass(get_class($this));
    2128:                 $properties = $reflection->getProperties();
    2129:
    2130:                 foreach($properties as $propObj) {
    2131:                     $propName = $propObj->name;
    2132:
    2133:                     // filter transient attributes
    2134:                     if(!in_array($propName, $this->transientAttributes)) {
    2135:                         $this->set($propName, $BO->get($propName, true));
    2136:                     }elseif(!$propObj->isPrivate() && isset($this->$propName) && $this->$propName instanceof Relation) {
    2137:                         $prop = $this->getPropObject($propName);
    2138:
    2139:                         // handle the setting of ONE-TO-MANY relation values
    2140:                         if($prop->getRelationType() == 'ONE-TO-MANY') {
    2141:                             $this->set($propObj->name, $this->getOID());
    2142:                         }
    2143:                     }
    2144:                 }
    2145:
    2146:                 self::$logger->debug('<<loadFromCache: [true]');
    2147:                 return true;
    2148:             }
    2149:         }catch(Exception $e) {
    2150:             self::$logger->error('Error while attempting to load a business object from ['.$config->get('cache.provider.name').']
    2151:              instance: ['.$e->getMessage().']');
    2152:
    2153:             self::$logger->debug('<<loadFromCache: [false]');
    2154:             return false;
    2155:         }
    2156:     }
    2157:
    2158:     /**
    2159:      * Sets the last query executed on this business object
    2160:      *
    2161:      * @param string $query
    2162:      * @since 1.1
    2163:      */
    2164:     public function setLastQuery($query) {
    2165:         self::$logger->sql($query);
    2166:         $this->lastQuery = $query;
    2167:     }
    2168:
    2169:     /**
    2170:      * Re-initialize the static logger property on the BO after de-serialize, as PHP does
    2171:      * not serialize static properties.
    2172:      *
    2173:      * @since 1.2
    2174:      */
    2175:     public function __wakeup() {
    2176:         if(self::$logger == null)
    2177:             self::$logger = new Logger(get_class($this));
    2178:     }
    2179:
    2180:     /**
    2181:      * Sets maintainHistory attribute on this DAO
    2182:      *
    2183:      * @param boolean $maintainHistory
    2184:      * @throws IllegalArguementException
    2185:      * @since 1.2
    2186:      */
    2187:     public function setMaintainHistory($maintainHistory) {
    2188:         if(!is_bool($maintainHistory))
    2189:             throw new IllegalArguementException('Non-boolean value ['.$maintainHistory.'] passed to setMaintainHistory method!');
    2190:
    2191:         $this->maintainHistory = $maintainHistory;
    2192:     }
    2193:
    2194:     /**
    2195:      * Gets the value of the  maintainHistory attribute
    2196:      *
    2197:      * @returns boolean
    2198:      * @since 1.2
    2199:      */
    2200:     public function getMaintainHistory() {
    2201:         return $this->maintainHistory;
    2202:     }
    2203: }
    2204:
    2205: ?>