Source for file AlphaDAOProviderMySQL.inc
Documentation is available at AlphaDAOProviderMySQL.inc
require_once $config->get('sysRoot'). 'alpha/model/AlphaDAOProviderInterface.inc';
* MySQL DAO provider (uses the MySQLi native API in PHP).
* @author John Collins <dev@alphaframework.org>
* @version $Id: AlphaDAOProviderMySQL.inc 1453 2011-12-04 15:12:54Z johnc $
* @license http://www.opensource.org/licenses/bsd-license.php The BSD License
* @copyright Copyright (c) 2011, John Collins (founder of Alpha Framework).
* Redistribution and use in source and binary forms, with or
* without modification, are permitted provided that the
* following conditions are met:
* * Redistributions of source code must retain the above
* copyright notice, this list of conditions and the
* * Redistributions in binary form must reproduce the above
* copyright notice, this list of conditions and the
* following disclaimer in the documentation and/or other
* materials provided with the distribution.
* * Neither the name of the Alpha Framework nor the names
* of its contributors may be used to endorse or promote
* products derived from this software without specific
* prior written permission.
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
* CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
* INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
* NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE
* OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
private static $logger = null;
private static $connection;
* The business object that we are mapping back to
self::$logger = new Logger('AlphaDAOProviderMySQL');
self::$logger->debug('>>__construct()');
self::$logger->debug('<<__construct');
* @see alpha/model/AlphaDAOProviderInterface::getConnection()
if (!isset (self::$connection)) {
self::$connection = new mysqli($config->get('sysDBHost'), $config->get('sysDBUsername'), $config->get('sysDBPassword'), $config->get('sysDB'));
if (mysqli_connect_error()) {
self::$logger->fatal('Could not connect to database: ['. mysqli_connect_errno(). '] '. mysqli_connect_error());
return self::$connection;
* @see alpha/model/AlphaDAOProviderInterface::disconnect()
if (isset (self::$connection)) {
self::$connection->close();
self::$connection = null;
* @see alpha/model/AlphaDAOProviderInterface::getLastDatabaseError()
return self::getConnection()->error;
* @see alpha/model/AlphaDAOProviderInterface::query()
public function query($sqlQuery) {
if(!$result = self::getConnection()->query($sqlQuery)) {
throw new CustomQueryException('Failed to run the custom query, MySql error is ['. self::getConnection()->error. '], query ['. $sqlQuery. ']');
while($row = $result->fetch_array(MYSQLI_ASSOC)) {
* @see alpha/model/AlphaDAOProviderInterface::load()
public function load($OID) {
self::$logger->debug('>>load(OID=['. $OID. '])');
foreach($attributes as $att)
$fields = substr($fields, 0, - 1);
$sqlQuery = 'SELECT '. $fields. ' FROM '. $this->BO->getTableName(). ' WHERE OID = ? LIMIT 1;';
$stmt = self::getConnection()->stmt_init();
if($stmt->prepare($sqlQuery)) {
$stmt->bind_param('i', $OID);
$result = $this->bindResult($stmt);
self::$logger->warn('The following query caused an unexpected result ['. $sqlQuery. ']');
throw new BONotFoundException('Failed to load object of OID ['. $OID. '], table ['. $this->BO->getTableName(). '] did not exist so had to create!');
if(!isset ($row['OID']) || $row['OID'] < 1) {
throw new BONotFoundException('Failed to load object of OID ['. $OID. '] not found in database.');
self::$logger->debug('<<load');
// get the class attributes
$reflection = new ReflectionClass(get_class($this->BO));
$properties = $reflection->getProperties();
foreach($properties as $propObj) {
$propName = $propObj->name;
// filter transient attributes
$this->BO->set($propName, $row[$propName]);
// handle the setting of ONE-TO-MANY relation values
if($prop->getRelationType() == 'ONE-TO-MANY') {
$this->BO->set($propObj->name, $this->BO->getOID());
self::$logger->warn('Bad data stored in the table ['. $this->BO->getTableName(). '], field ['. $propObj->name. '] bad value['. $row[$propObj->name]. '], exception ['. $e->getMessage(). ']');
}catch (PHPException $e) {
// it is possible that the load failed due to the table not being up-to-date
$count = count($missingFields);
for($i = 0; $i < $count; $i++ )
throw new BONotFoundException('Failed to load object of OID ['. $OID. '], table ['. $this->BO->getTableName(). '] was out of sync with the database so had to be updated!');
self::$logger->debug('<<load');
self::$logger->debug('<<load');
* @see alpha/model/AlphaDAOProviderInterface::loadByAttribute()
public function loadByAttribute($attribute, $value, $ignoreClassType= false, $loadAttributes= array()) {
self::$logger->debug('>>loadByAttribute(attribute=['. $attribute. '], value=['. $value. '], ignoreClassType=['. $ignoreClassType. '],
loadAttributes=['. var_export($loadAttributes, true). '])');
if(count($loadAttributes) == 0)
$attributes = $loadAttributes;
foreach($attributes as $att)
$fields = substr($fields, 0, - 1);
$sqlQuery = 'SELECT '. $fields. ' FROM '. $this->BO->getTableName(). ' WHERE '. $attribute. ' = ? AND classname = ? LIMIT 1;';
$sqlQuery = 'SELECT '. $fields. ' FROM '. $this->BO->getTableName(). ' WHERE '. $attribute. ' = ? LIMIT 1;';
self::$logger->debug('Query=['. $sqlQuery. ']');
$stmt = self::getConnection()->stmt_init();
if($stmt->prepare($sqlQuery)) {
$stmt->bind_param('is', $value, get_class($this->BO));
$stmt->bind_param('i', $value);
$stmt->bind_param('ss', $value, get_class($this->BO));
$stmt->bind_param('s', $value);
$result = $this->bindResult($stmt);
self::$logger->warn('The following query caused an unexpected result ['. $sqlQuery. ']');
throw new BONotFoundException('Failed to load object by attribute ['. $attribute. '] and value ['. $value. '], table did not exist so had to create!');
if(!isset ($row['OID']) || $row['OID'] < 1) {
throw new BONotFoundException('Failed to load object by attribute ['. $attribute. '] and value ['. $value. '], not found in database.');
self::$logger->debug('<<loadByAttribute');
$this->OID = $row['OID'];
// get the class attributes
$reflection = new ReflectionClass(get_class($this->BO));
$properties = $reflection->getProperties();
foreach($properties as $propObj) {
$propName = $propObj->name;
if(isset ($row[$propName])) {
// filter transient attributes
$this->BO->set($propName, $row[$propName]);
}elseif(!$propObj->isPrivate() && $this->BO->get($propName) != '' && $this->BO->getPropObject($propName) instanceof Relation) {
// handle the setting of ONE-TO-MANY relation values
if($prop->getRelationType() == 'ONE-TO-MANY') {
$this->BO->set($propObj->name, $this->BO->getOID());
self::$logger->warn('Bad data stored in the table ['. $this->BO->getTableName(). '], field ['. $propObj->name. '] bad value['. $row[$propObj->name]. '], exception ['. $e->getMessage(). ']');
}catch (PHPException $e) {
// it is possible that the load failed due to the table not being up-to-date
$count = count($missingFields);
for($i = 0; $i < $count; $i++ )
throw new BONotFoundException('Failed to load object by attribute ['. $attribute. '] and value ['. $value. '], table ['. $this->BO->getTableName(). '] was out of sync with the database so had to be updated!');
self::$logger->debug('<<loadByAttribute');
self::$logger->debug('<<loadByAttribute');
* @see alpha/model/AlphaDAOProviderInterface::loadAll()
public function loadAll($start= 0, $limit= 0, $orderBy= 'OID', $order= 'ASC', $ignoreClassType= false) {
self::$logger->debug('>>loadAll(start=['. $start. '], limit=['. $limit. '], orderBy=['. $orderBy. '], order=['. $order. '], ignoreClassType=['. $ignoreClassType. ']');
// ensure that the field name provided in the orderBy param is legit
$field = $this->BO->get($orderBy);
}catch (AlphaException $e) {
throw new AlphaException('The field name ['. $orderBy. '] provided in the param orderBy does not exist on the class ['. get_class($this->BO). ']');
$sqlQuery = 'SELECT OID FROM '. $this->BO->getTableName(). ' WHERE classname=\''. get_class($this->BO). '\' ORDER BY '. $orderBy. ' '. $order. ';';
$sqlQuery = 'SELECT OID FROM '. $this->BO->getTableName(). ' WHERE classname=\''. get_class($this->BO). '\' ORDER BY '. $orderBy. ' '. $order. ' LIMIT '.
$sqlQuery = 'SELECT OID FROM '. $this->BO->getTableName(). ' ORDER BY '. $orderBy. ' '. $order. ';';
$sqlQuery = 'SELECT OID FROM '. $this->BO->getTableName(). ' ORDER BY '. $orderBy. ' '. $order. ' LIMIT '. $start. ', '. $limit. ';';
self::$logger->debug('<<loadAll [0]');
// now build an array of objects to be returned
$BO_Class = get_class($this->BO);
while($row = $result->fetch_array(MYSQLI_ASSOC)) {
// the resource not allowed will be absent from the list
self::$logger->debug('<<loadAll ['. count($objects). ']');
* @see alpha/model/AlphaDAOProviderInterface::loadAllByAttribute()
public function loadAllByAttribute($attribute, $value, $start= 0, $limit= 0, $orderBy= "OID", $order= "ASC", $ignoreClassType= false, $constructorArgs= array()) {
self::$logger->debug('>>loadAllByAttribute(attribute=['. $attribute. '], value=['. $value. '], start=['. $start. '], limit=['. $limit. '], orderBy=['. $orderBy. '], order=['. $order. '], ignoreClassType=['. $ignoreClassType. '], constructorArgs=['. print_r($constructorArgs, true). ']');
if ($start != 0 && $limit != 0)
$limit = ' LIMIT '. $start. ', '. $limit. ';';
$sqlQuery = "SELECT OID FROM ". $this->BO->getTableName(). " WHERE $attribute = ? AND classname = ? ORDER BY ". $orderBy. " ". $order. $limit;
$sqlQuery = "SELECT OID FROM ". $this->BO->getTableName(). " WHERE $attribute = ? ORDER BY ". $orderBy. " ". $order. $limit;
self::$logger->debug($sqlQuery);
$stmt = AlphaDAOProviderMySQL::getConnection()->stmt_init();
if($stmt->prepare($sqlQuery)) {
$stmt->bind_param('is', $value, get_class($this->BO));
$stmt->bind_param('i', $value);
$stmt->bind_param('ss', $value, get_class($this->BO));
$stmt->bind_param('s', $value);
$result = $this->bindResult($stmt);
self::$logger->warn('The following query caused an unexpected result ['. $sqlQuery. ']');
throw new BONotFoundException('Failed to load objects by attribute ['. $attribute. '] and value ['. $value. '], table did not exist so had to create!');
self::$logger->debug('<<loadAllByAttribute []');
// now build an array of objects to be returned
$BO_Class = get_class($this->BO);
foreach($result as $row) {
$argsCount = count($constructorArgs);
$obj = new $BO_Class($constructorArgs[0]);
$obj = new $BO_Class($constructorArgs[0],$constructorArgs[1]);
$obj = new $BO_Class($constructorArgs[0],$constructorArgs[1],$constructorArgs[2]);
$obj = new $BO_Class($constructorArgs[0],$constructorArgs[1],$constructorArgs[2],$constructorArgs[3]);
$obj = new $BO_Class($constructorArgs[0],$constructorArgs[1],$constructorArgs[2],$constructorArgs[3],$constructorArgs[4]);
throw new IllegalArguementException('Too many elements in the $constructorArgs array passed to the loadAllByAttribute method!');
// the resource not allowed will be absent from the list
self::$logger->debug('<<loadAllByAttribute ['. count($objects). ']');
* @see alpha/model/AlphaDAOProviderInterface::loadAllByAttributes()
public function loadAllByAttributes($attributes= array(), $values= array(), $start= 0, $limit= 0, $orderBy= 'OID', $order= 'ASC', $ignoreClassType= false) {
self::$logger->debug('>>loadAllByAttributes(attributes=['. var_export($attributes, true). '], values=['. var_export($values, true). '], start=['.
$start. '], limit=['. $limit. '], orderBy=['. $orderBy. '], order=['. $order. '], ignoreClassType=['. $ignoreClassType. ']');
$count = count($attributes);
for($i = 0; $i < $count; $i++ ) {
$whereClause .= ' '. $attributes[$i]. ' = ? AND';
self::$logger->debug($whereClause);
$whereClause .= ' classname = ? AND';
// remove the last " AND"
$whereClause = substr($whereClause, 0, - 4);
$limit = ' LIMIT '. $start. ', '. $limit. ';';
$sqlQuery = "SELECT OID FROM ". $this->BO->getTableName(). $whereClause. " ORDER BY ". $orderBy. " ". $order. $limit;
if($stmt->prepare($sqlQuery)) {
// bind params where required attributes are provided
$stmt = $this->bindParams($stmt, $attributes, $values);
// we'll still need to bind the "classname" for overloaded BOs...
$stmt->bind_param('s', get_class($this->BO));
$result = $this->bindResult($stmt);
self::$logger->warn('The following query caused an unexpected result ['. $sqlQuery. ']');
throw new BONotFoundException('Failed to load objects by attributes ['. var_export($attributes, true). '] and values ['.
var_export($values, true). '], table did not exist so had to create!');
self::$logger->debug('<<loadAllByAttributes []');
// now build an array of objects to be returned
$BO_Class = get_class($this->BO);
foreach($result as $row) {
// the resource not allowed will be absent from the list
self::$logger->debug('<<loadAllByAttributes ['. count($objects). ']');
* @see alpha/model/AlphaDAOProviderInterface::loadAllByDayUpdated()
public function loadAllByDayUpdated($date, $start= 0, $limit= 0, $orderBy= "OID", $order= "ASC", $ignoreClassType= false) {
self::$logger->debug('>>loadAllByDayUpdated(date=['. $date. '], start=['. $start. '], limit=['. $limit. '], orderBy=['. $orderBy. '], order=['. $order. '], ignoreClassType=['. $ignoreClassType. ']');
if ($start != 0 && $limit != 0)
$limit = ' LIMIT '. $start. ', '. $limit. ';';
$sqlQuery = "SELECT OID FROM ". $this->BO->getTableName(). " WHERE updated_ts >= '". $date. " 00:00:00' AND updated_ts <= '". $date. " 23:59:59' AND classname='". get_class($this->BO). "' ORDER BY ". $orderBy. " ". $order. $limit;
$sqlQuery = "SELECT OID FROM ". $this->BO->getTableName(). " WHERE updated_ts >= '". $date. " 00:00:00' AND updated_ts <= '". $date. " 23:59:59' ORDER BY ". $orderBy. " ". $order. $limit;
self::$logger->debug('<<loadAllByDayUpdated []');
// now build an array of objects to be returned
$BO_Class = get_class($this->BO);
while($row = $result->fetch_array(MYSQLI_ASSOC)) {
self::$logger->debug('<<loadAllByDayUpdated ['. count($objects). ']');
* @see alpha/model/AlphaDAOProviderInterface::loadAllFieldValuesByAttribute()
self::$logger->debug('>>loadAllFieldValuesByAttribute(attribute=['. $attribute. '], value=['. $value. '], returnAttribute=['. $returnAttribute. '], order=['. $order. '], ignoreClassType=['. $ignoreClassType. ']');
$sqlQuery = "SELECT ". $returnAttribute. " FROM ". $this->BO->getTableName(). " WHERE $attribute = '$value' AND classname='". get_class($this->BO). "' ORDER BY OID ". $order. ";";
$sqlQuery = "SELECT ". $returnAttribute. " FROM ". $this->BO->getTableName(). " WHERE $attribute = '$value' ORDER BY OID ". $order. ";";
self::$logger->debug('lastQuery ['. $sqlQuery. ']');
if(!$result = AlphaDAOProviderMySQL::getConnection()->query($sqlQuery)) {
self::$logger->debug('<<loadAllFieldValuesByAttribute []');
// now build an array of attribute values to be returned
$BO_Class = get_class($this->BO);
while($row = $result->fetch_array(MYSQLI_ASSOC)) {
$values[$count] = $row[$returnAttribute];
self::$logger->debug('<<loadAllFieldValuesByAttribute ['. count($values). ']');
* @see alpha/model/AlphaDAOProviderInterface::save()
self::$logger->debug('>>save()');
// get the class attributes
$reflection = new ReflectionClass(get_class($this->BO));
$properties = $reflection->getProperties();
throw new LockingException('Could not save the object as it has been updated by another user. Please try saving again.');
// set the "updated by" fields, we can only set the user id if someone is logged in
if(isset ($_SESSION['currentUser']))
$this->BO->set('updated_by', $_SESSION['currentUser']->getOID());
// check to see if it is a transient object that needs to be inserted
foreach($properties as $propObj) {
$propName = $propObj->name;
// Skip the OID, database auto number takes care of this.
if($propName != 'OID' && $propName != 'version_num') {
$sqlQuery .= "$propName,";
if($propName == 'version_num') {
$sqlQuery .= 'version_num,';
$sqlQuery .= 'classname,';
$sqlQuery = rtrim($sqlQuery, ",");
$sqlQuery .= ') VALUES (';
for($i = 0; $i < $savedFieldsCount; $i++ )
$sqlQuery = rtrim($sqlQuery, ','). ')';
self::$logger->debug('Query ['. $sqlQuery. ']');
$stmt = AlphaDAOProviderMySQL::getConnection()->stmt_init();
if($stmt->prepare($sqlQuery)) {
$stmt = $this->bindParams($stmt);
// assume that it is a persistent object that needs to be updated
foreach($properties as $propObj) {
$propName = $propObj->name;
// Skip the OID, database auto number takes care of this.
if($propName != 'OID' && $propName != 'version_num') {
$sqlQuery .= "$propName = ?,";
if($propName == 'version_num') {
$sqlQuery .= 'version_num = ?,';
$sqlQuery .= 'classname = ?,';
$sqlQuery = rtrim($sqlQuery, ",");
$sqlQuery .= " WHERE OID=?;";
if($stmt->prepare($sqlQuery)) {
$this->bindParams($stmt);
if ($stmt != null && $stmt->error == '') {
// populate the updated OID in case we just done an insert
foreach($properties as $propObj) {
$propName = $propObj->name;
if(!$propObj->isPrivate() && $this->BO->get($propName) != '' && $this->BO->getPropObject($propName) instanceof Relation) {
// handle the saving of MANY-TO-MANY relation values
if($prop->getRelationType() == 'MANY-TO-MANY') {
// check to see if the rel is on this class
$side = $prop->getSide(get_class($this->BO));
$lookUp = $prop->getLookup();
// first delete all of the old RelationLookup objects for this rel
$lookUp->deleteAllByAttribute('leftID', $this->BO->getOID());
$lookUp->deleteAllByAttribute('rightID', $this->BO->getOID());
throw new FailedSaveException('Failed to delete old RelationLookup objects on the table ['. $prop->getLookup()->getTableName(). '], error is ['. $e->getMessage(). ']');
if(isset ($_POST[$propName]) && $_POST[$propName] != '00000000000')
$OIDs = explode(',', $_POST[$propName]);
if(isset ($OIDs) && !empty($OIDs[0])) {
// now for each posted OID, create a new RelationLookup record and save
foreach ($OIDs as $oid) {
$newLookUp = new RelationLookup($lookUp->get('leftClassName'), $lookUp->get('rightClassName'));
$newLookUp->set('leftID', $this->BO->getOID());
$newLookUp->set('rightID', $oid);
$newLookUp->set('rightID', $this->BO->getOID());
$newLookUp->set('leftID', $oid);
throw new FailedSaveException('Failed to update a MANY-TO-MANY relation on the object, error is ['. $e->getMessage(). ']');
// handle the saving of ONE-TO-MANY relation values
if($prop->getRelationType() == 'ONE-TO-MANY') {
$prop->setValue($this->BO->getOID());
// there has been an error, so decrement the version number back
$this->BO->set('version_num', $temp- 1);
// check for unique violations
* @see alpha/model/AlphaDAOProviderInterface::saveAttribute()
self::$logger->debug('>>saveAttribute(attribute=['. $attribute. '], value=['. $value. '])');
// assume that it is a persistent object that needs to be updated
$sqlQuery = 'UPDATE '. $this->BO->getTableName(). ' SET '. $attribute. '=? WHERE OID=?;';
$stmt = self::getConnection()->stmt_init();
if($stmt->prepare($sqlQuery)) {
$stmt->bind_param($bindingsType. 'i', $value, $this->BO->getOID());
self::$logger->debug('Binding params ['. $bindingsType. 'i, '. $value. ', '. $this->BO->getOID(). ']');
throw new FailedSaveException('Failed to save attribute, error is ['. $stmt->error. '], query ['. $this->BO->getLastQuery(). ']');
self::$logger->debug('<<saveAttribute');
* @see alpha/model/AlphaDAOProviderInterface::delete()
self::$logger->debug('>>delete()');
$sqlQuery = "DELETE FROM ". $this->BO->getTableName(). " WHERE OID = ?;";
$stmt = self::getConnection()->stmt_init();
if($stmt->prepare($sqlQuery)) {
$stmt->bind_param('i', $this->BO->getOID());
self::$logger->debug('Deleted the object ['. $this->BO->getOID(). '] of class ['. get_class($this->BO). ']');
self::$logger->debug('<<delete');
* @see alpha/model/AlphaDAOProviderInterface::getVersion()
self::$logger->debug('>>getVersion()');
$sqlQuery = 'SELECT version_num FROM '. $this->BO->getTableName(). ' WHERE OID = ?;';
$stmt = AlphaDAOProviderMySQL::getConnection()->stmt_init();
if($stmt->prepare($sqlQuery)) {
$stmt->bind_param('i', $this->BO->getOID());
$result = $this->bindResult($stmt);
self::$logger->warn('The following query caused an unexpected result ['. $sqlQuery. ']');
throw new BONotFoundException('Failed to get the version number, table did not exist so had to create!');
if(!isset ($row['version_num']) || $row['version_num'] < 1) {
self::$logger->debug('<<getVersion [0]');
$version_num = $row['version_num'];
self::$logger->debug('<<getVersion ['. $version_num. ']');
* @see alpha/model/AlphaDAOProviderInterface::makeTable()
self::$logger->debug('>>makeTable()');
$sqlQuery = "CREATE TABLE ". $this->BO->getTableName(). " (OID INT(11) ZEROFILL NOT NULL AUTO_INCREMENT,";
// get the class attributes
$reflection = new ReflectionClass(get_class($this->BO));
$properties = $reflection->getProperties();
foreach($properties as $propObj) {
$propName = $propObj->name;
// special properties for RelationLookup OIDs
if($this->BO instanceof RelationLookup && ($propName == 'leftID' || $propName == 'rightID'))
$sqlQuery .= "$propName INT(". $this->BO->getPropObject($propName)->getSize(). ") ZEROFILL NOT NULL,";
$sqlQuery .= "$propName INT(". $this->BO->getPropObject($propName)->getSize(). "),";
$sqlQuery .= "$propName DOUBLE(". $this->BO->getPropObject($propName)->getSize(true). "),";
$sqlQuery .= "$propName VARCHAR(". $this->BO->getPropObject($propName)->getSize(). "),";
$sqlQuery .= "$propName TEXT,";
$sqlQuery .= "$propName CHAR(1) DEFAULT '0',";
$sqlQuery .= "$propName DATE,";
$sqlQuery .= "$propName DATETIME,";
$sqlQuery .= "$propName ENUM(";
foreach($enumVals as $val) {
$sqlQuery .= "'". $val. "',";
$sqlQuery = rtrim($sqlQuery, ",");
$sqlQuery .= "$propName INT(11) ZEROFILL,";
$sqlQuery .= "$propName INT(11) ZEROFILL UNSIGNED,";
$sqlQuery .= "classname VARCHAR(100),";
$sqlQuery .= "PRIMARY KEY (OID)) ENGINE=InnoDB;";
self::$logger->debug('<<makeTable');
// check the table indexes if any additional ones required
self::$logger->debug('<<makeTable');
* @see alpha/model/AlphaDAOProviderInterface::rebuildTable()
self::$logger->debug('>>rebuildTable()');
$sqlQuery = 'DROP TABLE IF EXISTS '. $this->BO->getTableName(). ';';
if(!$result = AlphaDAOProviderMySQL::getConnection()->query($sqlQuery)) {
self::$logger->debug('<<rebuildTable');
self::$logger->debug('<<rebuildTable');
* @see alpha/model/AlphaDAOProviderInterface::dropTable()
self::$logger->debug('>>dropTable()');
$sqlQuery = 'DROP TABLE IF EXISTS '. $tableName. ';';
self::$logger->debug('<<dropTable');
self::$logger->debug('<<dropTable');
* @see alpha/model/AlphaDAOProviderInterface::addProperty()
self::$logger->debug('>>addProperty(propName=['. $propName. '])');
$sqlQuery = 'ALTER TABLE '. $this->BO->getTableName(). ' ADD ';
$sqlQuery .= 'classname VARCHAR(100)';
$sqlQuery .= "$propName INT(". $this->BO->getPropObject($propName)->getSize(). ")";
$sqlQuery .= "$propName DOUBLE(". $this->BO->getPropObject($propName)->getSize(true). ")";
$sqlQuery .= "$propName VARCHAR(". $this->BO->getPropObject($propName)->getSize(). ")";
$sqlQuery .= "$propName VARCHAR(". $this->BO->getPropObject($propName)->getSize(). ")";
$sqlQuery .= "$propName TEXT";
$sqlQuery .= "$propName CHAR(1) DEFAULT '0'";
$sqlQuery .= "$propName DATE";
$sqlQuery .= "$propName DATETIME";
$sqlQuery .= "$propName ENUM(";
foreach($enumVals as $val) {
$sqlQuery .= "'". $val. "',";
$sqlQuery = rtrim($sqlQuery, ",");
$sqlQuery .= "$propName INT(11) ZEROFILL";
$sqlQuery .= "$propName INT(11) ZEROFILL UNSIGNED";
if(!$result = self::getConnection()->query($sqlQuery)) {
self::$logger->debug('<<addProperty');
self::$logger->info('Successfully added the ['. $propName. '] column onto the ['. $this->BO->getTableName(). '] table for the class ['. get_class($this->BO). ']');
self::$logger->debug('<<addProperty');
* @see alpha/model/AlphaDAOProviderInterface::getMAX()
self::$logger->debug('>>getMAX()');
$sqlQuery = 'SELECT MAX(OID) AS max_OID FROM '. $this->BO->getTableName();
$result = $this->BO->query($sqlQuery);
if (isset ($row['max_OID'])) {
self::$logger->debug('<<getMAX ['. $row['max_OID']. ']');
throw new AlphaException('Failed to get the MAX ID for the class ['. get_class($this->BO). '] from the table ['. $this->BO->getTableName(). '], query is ['. $this->BO->getLastQuery(). ']');
self::$logger->debug('<<getMAX [0]');
* @see alpha/model/AlphaDAOProviderInterface::getCount()
public function getCount($attributes= array(), $values= array()) {
self::$logger->debug('>>getCount(attributes=['. var_export($attributes, true). '], values=['. var_export($values, true). '])');
$whereClause = ' WHERE classname = \''. get_class($this->BO). '\' AND';
$count = count($attributes);
for($i = 0; $i < $count; $i++ ) {
$whereClause .= ' '. $attributes[$i]. ' = \''. $values[$i]. '\' AND';
self::$logger->debug($whereClause);
// remove the last " AND"
$whereClause = substr($whereClause, 0, - 4);
if($whereClause != ' WHERE')
$sqlQuery = 'SELECT COUNT(OID) AS class_count FROM '. $this->BO->getTableName(). $whereClause;
$sqlQuery = 'SELECT COUNT(OID) AS class_count FROM '. $this->BO->getTableName();
$result = self::getConnection()->query($sqlQuery);
$row = $result->fetch_array(MYSQLI_ASSOC);
self::$logger->debug('<<getCount ['. $row['class_count']. ']');
return $row['class_count'];
throw new AlphaException('Failed to get the count for the class ['. get_class($this->BO). '] from the table ['. $this->BO->getTableName(). '], query is ['. $this->BO->getLastQuery(). ']');
self::$logger->debug('<<getCount [0]');
* @see alpha/model/AlphaDAOProviderInterface::setEnumOptions()
self::$logger->debug('>>setEnumOptions()');
// get the class attributes
$reflection = new ReflectionClass(get_class($this->BO));
$properties = $reflection->getProperties();
// flag for any database errors
foreach($properties as $propObj) {
$propName = $propObj->name;
if ($propClass == 'Enum') {
$sqlQuery = "SHOW COLUMNS FROM ". $this->BO->getTableName(). " LIKE '$propName'";
$row = $result->fetch_array(MYSQLI_NUM);
$this->after_setEnumOptions_callback();
throw new AlphaException('Failed to load enum options correctly for object instance of class ['. get_class($this). ']');
self::$logger->debug('<<setEnumOptions');
* @see alpha/model/AlphaDAOProviderInterface::checkTableExists()
self::$logger->debug('>>checkTableExists()');
$sqlQuery = 'SHOW TABLES;';
$result = self::getConnection()->query($sqlQuery);
while ($row = $result->fetch_array(MYSQLI_NUM)) {
self::$logger->debug('<<checkTableExists ['. $tableExists. ']');
throw new AlphaException('Failed to access the system database correctly, error is ['. self::getConnection()->error. ']');
self::$logger->debug('<<checkTableExists [false]');
* @see alpha/model/AlphaDAOProviderInterface::checkBOTableExists()
if(self::$logger == null)
self::$logger = new Logger('AlphaDAOProvidermySQL');
self::$logger->debug('>>checkBOTableExists(BOClassName=['. $BOClassName. '])');
eval ('$tableName = '. $BOClassName. '::TABLE_NAME;');
$tableName = substr($BOClassName, 0, strpos($BOClassName, '_'));
$sqlQuery = 'SHOW TABLES;';
$result = self::getConnection()->query($sqlQuery);
while ($row = $result->fetch_array(MYSQLI_NUM)) {
if ($row[0] == $tableName)
self::$logger->debug('<<checkBOTableExists ['. ($tableExists ? 'true' : 'false'). ']');
throw new AlphaException('Failed to access the system database correctly, error is ['. self::getConnection()->error. ']');
self::$logger->debug('<<checkBOTableExists [false]');
* @see alpha/model/AlphaDAOProviderInterface::checkTableNeedsUpdate()
self::$logger->debug('>>checkTableNeedsUpdate()');
$result = self::getConnection()->query($query);
// get the class attributes
$reflection = new ReflectionClass(get_class($this->BO));
$properties = $reflection->getProperties();
foreach($properties as $propObj) {
$propName = $propObj->name;
while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
if ($propName == $row['Field']) {
// check for the "classname" field in overloaded tables
while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
if ('classname' == $row['Field']) {
// check the table indexes
self::$logger->warn("Error while checking database indexes:\n\n". $ae->getMessage());
self::$logger->debug('<<checkTableNeedsUpdate ['. $updateRequired. ']');
throw new AlphaException('Failed to access the system database correctly, error is ['. self::getConnection()->error. ']');
self::$logger->debug('<<checkTableNeedsUpdate [false]');
* @see alpha/model/AlphaDAOProviderInterface::findMissingFields()
self::$logger->debug('>>findMissingFields()');
$missingFields = array();
$result = self::getConnection()->query($sqlQuery);
// get the class attributes
$reflection = new ReflectionClass(get_class($this->BO));
$properties = $reflection->getProperties();
foreach($properties as $propObj) {
$propName = $propObj->name;
while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
if ($propName == $row['Field']) {
// check for the "classname" field in overloaded tables
while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
if ('classname' == $row['Field']) {
throw new AlphaException('Failed to access the system database correctly, error is ['. self::getConnection()->error. ']');
self::$logger->debug('<<findMissingFields ['. var_export($missingFields, true). ']');
* @see alpha/model/AlphaDAOProviderInterface::getIndexes()
self::$logger->debug('>>getIndexes()');
$result = self::getConnection()->query($query);
throw new AlphaException('Failed to access the system database correctly, error is ['. self::getConnection()->error. ']');
while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
self::$logger->debug('<<getIndexes');
* Checks to see if all of the indexes are in place for the BO's table, creates those that are missing.
private function checkIndexes() {
self::$logger->debug('>>checkIndexes()');
// check for composite indexes
foreach ($indexNames as $index) {
if ($attributes[0]. '_'. $attributes[1]. '_unq_idx' == $index) {
if(count($attributes) == 3) {
if ($attributes[0]. '_'. $attributes[1]. '_'. $attributes[2]. '_unq_idx' == $index) {
if(count($attributes) == 3)
foreach ($indexNames as $index) {
if ($prop. '_unq_idx' == $index) {
// process foreign-key indexes
// get the class attributes
$reflection = new ReflectionClass(get_class($this->BO));
$properties = $reflection->getProperties();
foreach($properties as $propObj) {
$propName = $propObj->name;
if($prop->getRelationType() == 'MANY-TO-ONE') {
foreach ($indexNames as $index) {
if ($propName. '_fk_idx' == $index) {
$this->createForeignIndex($propName, $prop->getRelatedClass(), $prop->getRelatedClassField());
if($prop->getRelationType() == 'MANY-TO-MANY') {
$lookup = $prop->getLookup();
$lookupIndexNames = $lookup->getIndexes();
// handle index check/creation on left side of Relation
foreach ($lookupIndexNames as $index) {
if ('leftID_fk_idx' == $index) {
$lookup->createForeignIndex('leftID', $prop->getRelatedClass('left'), 'OID');
// handle index check/creation on right side of Relation
foreach ($lookupIndexNames as $index) {
if ('rightID_fk_idx' == $index) {
$lookup->createForeignIndex('rightID', $prop->getRelatedClass('right'), 'OID');
self::$logger->error($e->getMessage());
self::$logger->debug('<<checkIndexes');
* @see alpha/model/AlphaDAOProviderInterface::createForeignIndex()
public function createForeignIndex($attributeName, $relatedClass, $relatedClassAttribute) {
self::$logger->debug('>>createForeignIndex(attributeName=['. $attributeName. '], relatedClass=['. $relatedClass. '], relatedClassAttribute=['. $relatedClassAttribute. ']');
if(self::checkBOTableExists(ucfirst($tableName). 'Object')) {
if($attributeName == 'leftID')
$sqlQuery = 'ALTER TABLE '. $this->BO->getTableName(). ' ADD INDEX leftID_fk_idx (leftID);';
if($attributeName == 'rightID')
$sqlQuery = 'ALTER TABLE '. $this->BO->getTableName(). ' ADD INDEX rightID_fk_idx (rightID);';
$result = self::getConnection()->query($sqlQuery);
$sqlQuery = 'ALTER TABLE '. $this->BO->getTableName(). ' ADD FOREIGN KEY '. $attributeName. '_fk_idx ('. $attributeName. ') REFERENCES '. $tableName. ' ('. $relatedClassAttribute. ') ON DELETE SET NULL;';
$result = self::getConnection()->query($sqlQuery);
self::$logger->debug('Successfully created the foreign key index ['. $attributeName. '_fk_idx]');
throw new FailedIndexCreateException('Failed to create the index ['. $attributeName. '_fk_idx] on ['. $this->BO->getTableName(). '], error is ['. self::getConnection()->error. '], query ['. $this->BO->getLastQuery(). ']');
self::$logger->debug('<<createForeignIndex');
* @see alpha/model/AlphaDAOProviderInterface::createUniqueIndex()
public function createUniqueIndex($attribute1Name, $attribute2Name = '', $attribute3Name = '') {
self::$logger->debug('>>createUniqueIndex(attribute1Name=['. $attribute1Name. '], attribute2Name=['. $attribute2Name. '], attribute3Name=['. $attribute3Name. '])');
if($attribute2Name != '' && $attribute3Name != '')
$sqlQuery = 'CREATE UNIQUE INDEX '. $attribute1Name. '_'. $attribute2Name. '_'. $attribute3Name. '_unq_idx ON '. $this->BO->getTableName(). ' ('. $attribute1Name. ','. $attribute2Name. ','. $attribute3Name. ');';
if($attribute2Name != '' && $attribute3Name == '')
$sqlQuery = 'CREATE UNIQUE INDEX '. $attribute1Name. '_'. $attribute2Name. '_unq_idx ON '. $this->BO->getTableName(). ' ('. $attribute1Name. ','. $attribute2Name. ');';
if($attribute2Name == '' && $attribute3Name == '')
$sqlQuery = 'CREATE UNIQUE INDEX '. $attribute1Name. '_unq_idx ON '. $this->BO->getTableName(). ' ('. $attribute1Name. ');';
$result = self::getConnection()->query($sqlQuery);
self::$logger->debug('Successfully created the unique index on ['. $this->BO->getTableName(). ']');
throw new FailedIndexCreateException('Failed to create the unique index on ['. $this->BO->getTableName(). '], error is ['. self::getConnection()->error. ']');
self::$logger->debug('<<createUniqueIndex');
* @see alpha/model/AlphaDAOProviderInterface::reload()
self::$logger->debug('>>reload()');
if(!$this->isTransient()) {
$this->load($this->getOID());
throw new AlphaException('Cannot reload transient object from database!');
self::$logger->debug('<<reload');
* @see alpha/model/AlphaDAOProviderInterface::checkRecordExists()
self::$logger->debug('>>checkRecordExists(OID=['. $OID. '])');
$sqlQuery = 'SELECT OID FROM '. $this->BO->getTableName(). ' WHERE OID = ?;';
$stmt = self::getConnection()->stmt_init();
if($stmt->prepare($sqlQuery)) {
$stmt->bind_param('i', $OID);
$result = $this->bindResult($stmt);
self::$logger->debug('<<checkRecordExists [true]');
self::$logger->debug('<<checkRecordExists [false]');
self::$logger->debug('<<checkRecordExists [false]');
self::$logger->debug('<<checkRecordExists [false]');
* @see alpha/model/AlphaDAOProviderInterface::isTableOverloaded()
self::$logger->debug('>>isTableOverloaded()');
$classname = get_class($this->BO);
// use reflection to check to see if we are dealing with a persistent type (e.g. DEnum) which are never overloaded
$reflection = new ReflectionClass($classname);
$implementedInterfaces = $reflection->getInterfaces();
foreach ($implementedInterfaces as $interface) {
if ($interface->name == 'AlphaTypeInterface') {
self::$logger->debug('<<isTableOverloaded [false]');
if($classname != $tablename) {
// loop over all BOs to see if there is one using the same table as this BO
foreach($BOclasses as $BOclassName) {
if($tablename == $BOclassName) {
self::$logger->debug('<<isTableOverloaded [true]');
throw new BadBOTableNameException('The table name ['. $tablename. '] for the class ['. $classname. '] is invalid as it does not match a BO definition in the system!');
self::$logger->debug('<<isTableOverloaded [false]');
// check to see if there is already a "classname" column in the database for this BO
$result = self::getConnection()->query($query);
while ($row = $result->fetch_array(MYSQLI_ASSOC)) {
if ('classname' == $row['Field']) {
self::$logger->debug('<<isTableOverloaded [true]');
self::$logger->warn('Error during show columns ['. self::getConnection()->error. ']');
self::$logger->debug('<<isTableOverloaded [false]');
* @see alpha/model/AlphaDAOProviderInterface::begin()
public static function begin() {
if(self::$logger == null)
self::$logger = new Logger('AlphaDAOProviderMySQL');
self::$logger->debug('>>begin()');
if (!self::getConnection()->autocommit(false))
throw new AlphaException('Error beginning a new transaction, error is ['. self::getConnection()->error. ']');
self::$logger->debug('<<begin');
* @see alpha/model/AlphaDAOProviderInterface::commit()
public static function commit() {
if(self::$logger == null)
self::$logger = new Logger('AlphaDAOProviderMySQL');
self::$logger->debug('>>commit()');
if (!self::getConnection()->commit())
throw new FailedSaveException('Error commiting a transaction, error is ['. self::getConnection()->error. ']');
self::$logger->debug('<<commit');
* @see alpha/model/AlphaDAOProviderInterface::rollback()
if(self::$logger == null)
self::$logger = new Logger('AlphaDAOProviderMySQL');
self::$logger->debug('>>rollback()');
if (!self::getConnection()->rollback())
throw new AlphaException('Error rolling back a transaction, error is ['. self::getConnection()->error. ']');
self::$logger->debug('<<rollback');
* @see alpha/model/AlphaDAOProviderInterface::setBO()
public function setBO($BO) {
* Dynamically binds all of the attributes for the current BO to the supplied prepared statement
* parameters. If arrays of attribute names and values are provided, only those will be bound to
* the supplied statement.
* @param mysqli_stmt $stmt The SQL statement to bind to.
* @param array Optional array of BO attributes.
* @param array Optional array of BO values.
private function bindParams($stmt, $attributes= array(), $values= array()) {
self::$logger->debug('>>bindParams(stmt=['. var_export($stmt, true). '])');
// here we are only binding the supplied attributes
for($i = 0; $i < $count; $i++ ) {
if(isset ($this->classname)) {
}else{ // bind all attributes on the business object
// get the class attributes
$reflection = new ReflectionClass(get_class($this->BO));
$properties = $reflection->getProperties();
foreach($properties as $propObj) {
$propName = $propObj->name;
// Skip the OID, database auto number takes care of this.
if($propName != 'OID' && $propName != 'version_num') {
if($propName == 'version_num') {
$this->BO->set('version_num', $temp+ 1);
if(isset ($this->classname)) {
// the OID may be on the WHERE clause for UPDATEs and DELETEs
self::$logger->debug('bindingsTypes=['. $bindingsTypes. '], count: ['. strlen($bindingsTypes). ']');
self::$logger->debug('params ['. var_export($params, true). ']');
$bind_names[] = $bindingsTypes;
for ($i = 0; $i < $count; $i++ ) {
$ $bind_name = $params[$i];
$bind_names[] = &$ $bind_name;
self::$logger->debug('<<bindParams ['. var_export($stmt, true). ']');
* Dynamically binds the result of the supplied prepared statement to a 2d array, where each element in the array is another array
* representing a database row.
* @param mysqli_stmt $stmt
* @return array A 2D array containing the query result.
private function bindResult($stmt) {
$metadata = $stmt->result_metadata();
$fields = $metadata->fetch_fields();
foreach ($fields as $field) {
$fieldname = $field->name;
$pointers[] = &$row[$fieldname];
* Parses a MySQL error for the value that violated a unique constraint.
* @param string $error The MySQL error string.
private function findOffendingValue($error) {
self::$logger->debug('>>findOffendingValue(error=['. $error. '])');
$singleQuote1 = strpos($error,"'");
$singleQuote2 = strrpos($error,"'");
$value = substr($error, $singleQuote1, ($singleQuote2- $singleQuote1)+ 1);
self::$logger->debug('<<findOffendingValue ['. $value. '])');
|