includes/framework/QModelTrait.trait.php
<?php
/**
* Class QModelTrait
*
* This trait class is a mixin helper for all the generated Model classes. It works together with the code generator
* to create particular functions that are common to all the classes. For historical reasons, and to prevent problems
* with polymorhpism, this is a trait and not a base class.
*/
trait QModelTrait {
/*** Requirements of Model classes ***/
/*
* The generated model classes must implement the following functions and members.
*/
/**
* Returns the value of the primary key for this object. If a composite primary key, this should return a string representation
* of the combined keys such that the combination will be unique.
* @return integer|string
*/
// protected function PrimaryKey();
/**
* A helper function to get the primary key associated with this object type from a query result row.
*
* @param QDatabaseRowBase $objDbRow
* @param string $strAliasPrefix Prefix to use if this is a row expansion (as in, a join)
* @param string[] $strColumnAliasArray Array of column aliases associateing our column names with the minimized names in the query.
* @return mixed The primary key found in the row
*/
// protected static function GetRowPrimaryKey($objDbRow, $strAliasPrefix, $strColumnAliasArray){}
/**
* Return the database object associated with this object.
*
* @return QDatabaseBase
*/
// public static function GetDatabase(){}
/**
* Return the name of the database table associated with this object.
*
* @return string
*/
// public static function GetTableName(){}
/**
* Add select fields to the query as part of the query building process. The superclass should override this to add the necessary fields
* to the query builder object. The default is to add all the fields in the object.
*
* @param QQueryBuilder $objBuilder
* @param string|null $strPrefix optional prefix to be used if this is an extended query (as in, a join)
* @param QQSelect|null $objSelect optional QQSelect clause to select specific fields, rather than the entire set of fields in the object
*/
//public static function GetSelectFields(QQueryBuilder $objBuilder, $strPrefix = null, QQSelect $objSelect = null){}
/*** Implementation ***/
/**
* Takes a query builder object and outputs the sql query that corresponds to its structure and the given parameters.
*
* @param QQueryBuilder &$objQueryBuilder the QueryBuilder object that will be created
* @param QQCondition $objConditions any conditions on the query, itself
* @param QQClause[] $objOptionalClauses additional optional QQClause object or array of QQClause objects for this query
* @param mixed[] $mixParameterArray a array of name-value pairs to perform PrepareStatement with (sending in null will skip the PrepareStatement step)
* @param boolean $blnCountOnly only select a rowcount
* @return string the query statement
* @throws QCallerException
* @throws Exception
*/
protected static function BuildQueryStatement(&$objQueryBuilder, QQCondition $objConditions, $objOptionalClauses, $mixParameterArray, $blnCountOnly) {
// Get the Database Object for this Class
$objDatabase = static::GetDatabase();
$strTableName = static::GetTableName();
// Create/Build out the QueryBuilder object with class-specific SELECT and FROM fields
$objQueryBuilder = new QQueryBuilder($objDatabase, $strTableName);
$blnAddAllFieldsToSelect = true;
if ($objDatabase->OnlyFullGroupBy) {
// see if we have any group by or aggregation clauses, if yes, don't add all the fields to select clause by default
// because these databases post an error instead of just choosing a value to return when a select item could
// have multiple values
if ($objOptionalClauses instanceof QQClause) {
if ($objOptionalClauses instanceof QQAggregationClause || $objOptionalClauses instanceof QQGroupBy) {
$blnAddAllFieldsToSelect = false;
}
} else if (is_array($objOptionalClauses)) {
foreach ($objOptionalClauses as $objClause) {
if ($objClause instanceof QQAggregationClause || $objClause instanceof QQGroupBy) {
$blnAddAllFieldsToSelect = false;
break;
}
}
}
}
$objSelectClauses = QQuery::ExtractSelectClause($objOptionalClauses);
if ($objSelectClauses || $blnAddAllFieldsToSelect) {
static::BaseNode()->PutSelectFields($objQueryBuilder, null, $objSelectClauses);
}
$objQueryBuilder->AddFromItem($strTableName);
// Set "CountOnly" option (if applicable)
if ($blnCountOnly)
$objQueryBuilder->SetCountOnlyFlag();
// Apply Any Conditions
if ($objConditions)
try {
$objConditions->UpdateQueryBuilder($objQueryBuilder);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
$objExc->IncrementOffset();
throw $objExc;
}
// Iterate through all the Optional Clauses (if any) and perform accordingly
if ($objOptionalClauses) {
if ($objOptionalClauses instanceof QQClause) {
try {
$objOptionalClauses->UpdateQueryBuilder($objQueryBuilder);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
$objExc->IncrementOffset();
throw $objExc;
}
}
else if (is_array($objOptionalClauses)) {
foreach ($objOptionalClauses as $objClause) {
try {
$objClause->UpdateQueryBuilder($objQueryBuilder);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
$objExc->IncrementOffset();
throw $objExc;
}
}
}
else
throw new QCallerException('Optional Clauses must be a QQClause object or an array of QQClause objects');
}
// Get the SQL Statement
$strQuery = $objQueryBuilder->GetStatement();
// Substitute the correct sql variable names for the placeholders specified in the query, if any.
if ($mixParameterArray) {
if (is_array($mixParameterArray)) {
if (count($mixParameterArray))
$strQuery = $objDatabase->PrepareStatement($strQuery, $mixParameterArray);
// Ensure that there are no other Unresolved Named Parameters
if (strpos($strQuery, chr(QQNamedValue::DelimiterCode) . '{') !== false)
throw new QCallerException('Unresolved named parameters in the query');
} else
throw new QCallerException('Parameter Array must be an array of name-value parameter pairs');
}
// Return the Objects
return $strQuery;
}
/**
* Static Qcubed Query method to query for a single <?php echo $objTable->ClassName ?> object.
* Uses BuildQueryStatment to perform most of the work.
* Is called by QuerySincle function of each object so that the correct return type will be put in the comments.
*
* @param QQCondition $objConditions any conditions on the query, itself
* @param null $objOptionalClauses
* @param mixed[] $mixParameterArray a array of name-value pairs to perform PrepareStatement with
* @throws Exception
* @throws QCallerException
* @return null|QModelBase the queried object
*/
protected static function _QuerySingle(QQCondition $objConditions, $objOptionalClauses = null, $mixParameterArray = null) {
// Get the Query Statement
try {
$strQuery = static::BuildQueryStatement($objQueryBuilder, $objConditions, $objOptionalClauses, $mixParameterArray, false);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
// Perform the Query, Get the First Row, and Instantiate a new object
$objDbResult = $objQueryBuilder->Database->Query($strQuery);
// Do we have to expand anything?
if ($objQueryBuilder->ExpandAsArrayNode) {
$objToReturn = array();
$objPrevItemArray = array();
while ($objDbRow = $objDbResult->GetNextRow()) {
$objItem = static::InstantiateDbRow($objDbRow, null, $objQueryBuilder->ExpandAsArrayNode, $objPrevItemArray, $objQueryBuilder->ColumnAliasArray);
if ($objItem) {
$objToReturn[] = $objItem;
$pk = $objItem->PrimaryKey();
if ($pk) {
$objPrevItemArray[$pk][] = $objItem;
} else {
$objPrevItemArray[] = $objItem;
}
}
}
if (count($objToReturn)) {
// Since we only want the object to return, lets return the object and not the array.
return $objToReturn[0];
} else {
return null;
}
} else {
// No expands just return the first row
$objDbRow = $objDbResult->GetNextRow();
if(null === $objDbRow)
return null;
return static::InstantiateDbRow($objDbRow, null, null, null, $objQueryBuilder->ColumnAliasArray);
}
}
/**
* Static Qcubed Query method to query for an array of objects.
* Uses BuildQueryStatment to perform most of the work.
* Is called by QueryArray function of each object so that the correct return type will be put in the comments.
*
* @param QQCondition $objConditions any conditions on the query, itself
* @param QQClause[]|null $objOptionalClauses additional optional QQClause objects for this query
* @param mixed[]|null $mixParameterArray an array of name-value pairs to substitute in to the placeholders in the query, if needed
* @return mixed[] an array of objects
* @throws Exception
* @throws QCallerException
*/
protected static function _QueryArray(QQCondition $objConditions, $objOptionalClauses = null, $mixParameterArray = null) {
// Get the Query Statement
try {
$strQuery = static::BuildQueryStatement($objQueryBuilder, $objConditions, $objOptionalClauses, $mixParameterArray, false);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
// Perform the Query and Instantiate the Array Result
$objDbResult = $objQueryBuilder->Database->Query($strQuery);
return static::InstantiateDbResult($objDbResult, $objQueryBuilder->ExpandAsArrayNode, $objQueryBuilder->ColumnAliasArray);
}
/**
* Static Qcubed query method to issue a query and get a cursor to progressively fetch its results.
* Uses BuildQueryStatment to perform most of the work.
*
* @param QQCondition $objConditions any conditions on the query, itself
* @param QQClause[] $objOptionalClauses additional optional QQClause objects for this query
* @param mixed[] $mixParameterArray an array of name-value pairs to substitute in to the placeholders in the query, if needed
* @return QDatabaseResultBase the cursor resource instance
* @throws Exception
* @throws QCallerException
*/
public static function QueryCursor(QQCondition $objConditions, $objOptionalClauses = null, $mixParameterArray = null) {
// Get the query statement
try {
$strQuery = static::BuildQueryStatement($objQueryBuilder, $objConditions, $objOptionalClauses, $mixParameterArray, false);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
// Perform the query
$objDbResult = $objQueryBuilder->Database->Query($strQuery);
// Return the results cursor
$objDbResult->QueryBuilder = $objQueryBuilder;
return $objDbResult;
}
/**
* Static Qcubed Query method to query for a count of objects.
* Uses BuildQueryStatment to perform most of the work.
*
* @param QQCondition $objConditions any conditions on the query, itself
* @param QQClause[] $objOptionalClauses additional optional QQClause objects for this query
* @param mixed[] $mixParameterArray a array of name-value pairs to perform PrepareStatement with
* @return integer the count of queried objects as an integer
*/
public static function QueryCount(QQCondition $objConditions, $objOptionalClauses = null, $mixParameterArray = null) {
// Get the Query Statement
try {
$strQuery = static::BuildQueryStatement($objQueryBuilder, $objConditions, $objOptionalClauses, $mixParameterArray, true);
} catch (QCallerException $objExc) {
$objExc->IncrementOffset();
throw $objExc;
}
// Perform the Query and return the row_count
$objDbResult = $objQueryBuilder->Database->Query($strQuery);
// Figure out if the query is using GroupBy
$blnGrouped = false;
if ($objOptionalClauses) {
if ($objOptionalClauses instanceof QQClause) {
if ($objOptionalClauses instanceof QQGroupBy) {
$blnGrouped = true;
}
} else if (is_array($objOptionalClauses)) {
foreach ($objOptionalClauses as $objClause) {
if ($objClause instanceof QQGroupBy) {
$blnGrouped = true;
break;
}
}
} else {
throw new QCallerException('Optional Clauses must be a QQClause object or an array of QQClause objects');
}
}
if ($blnGrouped)
// Groups in this query - return the count of Groups (which is the count of all rows)
return $objDbResult->CountRows();
else {
// No Groups - return the sql-calculated count(*) value
$strDbRow = $objDbResult->FetchRow();
return (integer)$strDbRow[0];
}
}
public static function QueryArrayCached(QQCondition $objConditions, $objOptionalClauses = null, $mixParameterArray = null, $blnForceUpdate = false) {
$strQuery = static::BuildQueryStatement($objQueryBuilder, $objConditions, $objOptionalClauses, $mixParameterArray, false);
$strTableName = static::GetTableName();
$objCache = new QCache(sprintf('qquery/%s', $strTableName), $strQuery);
$cacheData = $objCache->GetData();
if (!$cacheData || $blnForceUpdate) {
$objDbResult = $objQueryBuilder->Database->Query($strQuery);
$arrResult = static::InstantiateDbResult($objDbResult, $objQueryBuilder->ExpandAsArrayNode, $objQueryBuilder->ColumnAliasArray);
$objCache->SaveData(serialize($arrResult));
} else {
$arrResult = unserialize($cacheData);
}
return $arrResult;
}
/**
* Do a possible array expansion on the given node. If the node is an ExpandAsArray node,
* it will add to the corresponding array in the object. Otherwise, it will follow the node
* so that any leaf expansions can be handled.
*
* @param QDatabaseRowBase $objDbRow
* @param string $strAliasPrefix
* @param QQNode $objNode
* @param QModelBase[] $objPreviousItemArray
* @param string[] $strColumnAliasArray
* @return boolean|null Returns true if the we used the row for an expansion, false if we already expanded this node in a previous row, or null if no expansion data was found
*/
public static function ExpandArray ($objDbRow, $strAliasPrefix, $objNode, $objPreviousItemArray, $strColumnAliasArray) {
if (!$objNode->ChildNodeArray) {
return null;
}
$blnExpanded = null;
$pk = static::GetRowPrimaryKey ($objDbRow, $strAliasPrefix, $strColumnAliasArray);
foreach ($objPreviousItemArray as $objPreviousItem) {
if ($pk != $objPreviousItem->PrimaryKey()) {
continue;
}
foreach ($objNode->ChildNodeArray as $objChildNode) {
$strPropName = $objChildNode->_PropertyName;
$strClassName = $objChildNode->_ClassName;
$strLongAlias = $objChildNode->FullAlias();
$blnExpandAsArray = false;
if ($objChildNode->ExpandAsArray) {
$strPostfix = 'Array';
$blnExpandAsArray = true;
} else {
$strPostfix = '';
}
$nodeType = $objChildNode->_Type;
if ($nodeType == 'reverse_reference') {
$strPrefix = '_obj';
} elseif ($nodeType == 'association') {
$objChildNode = $objChildNode->FirstChild();
if ($objChildNode->IsType) {
$strPrefix = '_int';
} else {
$strPrefix = '_obj';
}
} else {
$strPrefix = 'obj';
}
$strVarName = $strPrefix . $strPropName . $strPostfix;
if ($blnExpandAsArray) {
if (null === $objPreviousItem->$strVarName) {
$objPreviousItem->$strVarName = array();
}
if (count($objPreviousItem->$strVarName)) {
$objPreviousChildItems = $objPreviousItem->$strVarName;
$nextAlias = $objChildNode->FullAlias() . '__';
$objChildItem = $strClassName::InstantiateDbRow ($objDbRow, $nextAlias, $objChildNode, $objPreviousChildItems, $strColumnAliasArray, true);
if ($objChildItem) {
$objPreviousItem->{$strVarName}[] = $objChildItem;
$blnExpanded = true;
} elseif ($objChildItem === false) {
$blnExpanded = true;
}
}
} elseif (!$objChildNode->IsType) {
// Follow single node if keys match
if (null === $objPreviousItem->$strVarName) {
return false;
}
$objPreviousChildItems = array($objPreviousItem->$strVarName);
$blnResult = $strClassName::ExpandArray ($objDbRow, $strLongAlias . '__', $objChildNode, $objPreviousChildItems, $strColumnAliasArray);
if ($blnResult) {
$blnExpanded = true;
}
}
}
}
return $blnExpanded;
}
/**
* Return an object corresponding to the given key, or null.
*
* The key might be null if:
* The table has no primary key, or
* SetSkipPrimaryKey was used in a query with QSelect.
*
* Otherwise, the default here is to use the local cache.
*
* Note that you rarely would want to change this. Caching at this level happens
* after a query has executed. Using a cache like APC or MemCache at this point would
* be really expensive, and would only be worth it for a large table.
*
* @param $key
* @return null|object
*/
public static function GetFromCache($key) {
if ($key===null) return null;
if (QApplication::$blnLocalCache === true && !empty(static::$objCacheArray[$key])) {
return clone(static::$objCacheArray[$key]);
}
elseif (QApplication::$objCacheProvider) {
$strCacheKey = QApplication::$objCacheProvider->CreateKey(static::GetDatabase()->Database, __CLASS__, $key);
return QApplication::$objCacheProvider->Get($strCacheKey);
}
return null;
}
/**
* Put the current object in the cache for future reference.
*/
public function WriteToCache() {
$key = $this->PrimaryKey();
if ($key === null) return;
$obj = clone($this);
if (QApplication::$blnLocalCache === true) static::$objCacheArray[$key] = $obj;
if (QApplication::$objCacheProvider) {
$strCacheKey = QApplication::$objCacheProvider->CreateKey(static::GetDatabase()->Database, __CLASS__, $key);
QApplication::$objCacheProvider->Set($strCacheKey, $obj);
}
}
/**
* Delete this particular object from the cache
* @return void
*/
public function DeleteFromCache() {
$key = $this->PrimaryKey();
if ($key === null) return;
unset (static::$objCacheArray[$key]);
if (QApplication::$objCacheProvider) {
$strCacheKey = QApplication::$objCacheProvider->CreateKey(static::GetDatabase()->Database, __CLASS__, $key);
QApplication::$objCacheProvider->Delete($strCacheKey);
}
}
/**
* Clears the caches associated with this table.
*/
public static function ClearCache() {
static::$objCacheArray = array();
if (QApplication::$objCacheProvider) {
QApplication::$objCacheProvider->DeleteAll();
}
}
}