1: <?php
2:
3: /**
4: * Class QModelTrait
5: *
6: * This trait class is a mixin helper for all the generated Model classes. It works together with the code generator
7: * to create particular functions that are common to all the classes. For historical reasons, and to prevent problems
8: * with polymorhpism, this is a trait and not a base class.
9: */
10: trait QModelTrait {
11:
12: /*** Requirements of Model classes ***/
13:
14: /*
15: * The generated model classes must implement the following functions and members.
16: */
17:
18: /**
19: * Returns the value of the primary key for this object. If a composite primary key, this should return a string representation
20: * of the combined keys such that the combination will be unique.
21: * @return integer|string
22: */
23: // protected function PrimaryKey();
24:
25: /**
26: * A helper function to get the primary key associated with this object type from a query result row.
27: *
28: * @param QDatabaseRowBase $objDbRow
29: * @param string $strAliasPrefix Prefix to use if this is a row expansion (as in, a join)
30: * @param string[] $strColumnAliasArray Array of column aliases associateing our column names with the minimized names in the query.
31: * @return mixed The primary key found in the row
32: */
33: // protected static function GetRowPrimaryKey($objDbRow, $strAliasPrefix, $strColumnAliasArray){}
34:
35: /**
36: * Return the database object associated with this object.
37: *
38: * @return QDatabaseBase
39: */
40: // public static function GetDatabase(){}
41:
42: /**
43: * Return the name of the database table associated with this object.
44: *
45: * @return string
46: */
47: // public static function GetTableName(){}
48:
49: /**
50: * Add select fields to the query as part of the query building process. The superclass should override this to add the necessary fields
51: * to the query builder object. The default is to add all the fields in the object.
52: *
53: * @param QQueryBuilder $objBuilder
54: * @param string|null $strPrefix optional prefix to be used if this is an extended query (as in, a join)
55: * @param QQSelect|null $objSelect optional QQSelect clause to select specific fields, rather than the entire set of fields in the object
56: */
57: //public static function GetSelectFields(QQueryBuilder $objBuilder, $strPrefix = null, QQSelect $objSelect = null){}
58:
59:
60: /*** Implementation ***/
61:
62: /**
63: * Takes a query builder object and outputs the sql query that corresponds to its structure and the given parameters.
64: *
65: * @param QQueryBuilder &$objQueryBuilder the QueryBuilder object that will be created
66: * @param QQCondition $objConditions any conditions on the query, itself
67: * @param QQClause[] $objOptionalClauses additional optional QQClause object or array of QQClause objects for this query
68: * @param mixed[] $mixParameterArray a array of name-value pairs to perform PrepareStatement with (sending in null will skip the PrepareStatement step)
69: * @param boolean $blnCountOnly only select a rowcount
70: * @return string the query statement
71: * @throws QCallerException
72: * @throws Exception
73: */
74: protected static function BuildQueryStatement(&$objQueryBuilder, QQCondition $objConditions, $objOptionalClauses, $mixParameterArray, $blnCountOnly) {
75: // Get the Database Object for this Class
76: $objDatabase = static::GetDatabase();
77: $strTableName = static::GetTableName();
78:
79: // Create/Build out the QueryBuilder object with class-specific SELECT and FROM fields
80: $objQueryBuilder = new QQueryBuilder($objDatabase, $strTableName);
81:
82: $blnAddAllFieldsToSelect = true;
83: if ($objDatabase->OnlyFullGroupBy) {
84: // see if we have any group by or aggregation clauses, if yes, don't add all the fields to select clause by default
85: // because these databases post an error instead of just choosing a value to return when a select item could
86: // have multiple values
87: if ($objOptionalClauses instanceof QQClause) {
88: if ($objOptionalClauses instanceof QQAggregationClause || $objOptionalClauses instanceof QQGroupBy) {
89: $blnAddAllFieldsToSelect = false;
90: }
91: } else if (is_array($objOptionalClauses)) {
92: foreach ($objOptionalClauses as $objClause) {
93: if ($objClause instanceof QQAggregationClause || $objClause instanceof QQGroupBy) {
94: $blnAddAllFieldsToSelect = false;
95: break;
96: }
97: }
98: }
99: }
100:
101: $objSelectClauses = QQuery::ExtractSelectClause($objOptionalClauses);
102: if ($objSelectClauses || $blnAddAllFieldsToSelect) {
103: static::BaseNode()->PutSelectFields($objQueryBuilder, null, $objSelectClauses);
104: }
105:
106: $objQueryBuilder->AddFromItem($strTableName);
107:
108: // Set "CountOnly" option (if applicable)
109: if ($blnCountOnly)
110: $objQueryBuilder->SetCountOnlyFlag();
111:
112: // Apply Any Conditions
113: if ($objConditions)
114: try {
115: $objConditions->UpdateQueryBuilder($objQueryBuilder);
116: } catch (QCallerException $objExc) {
117: $objExc->IncrementOffset();
118: $objExc->IncrementOffset();
119: throw $objExc;
120: }
121:
122: // Iterate through all the Optional Clauses (if any) and perform accordingly
123: if ($objOptionalClauses) {
124: if ($objOptionalClauses instanceof QQClause) {
125: try {
126: $objOptionalClauses->UpdateQueryBuilder($objQueryBuilder);
127: } catch (QCallerException $objExc) {
128: $objExc->IncrementOffset();
129: $objExc->IncrementOffset();
130: throw $objExc;
131: }
132:
133: }
134: else if (is_array($objOptionalClauses)) {
135: foreach ($objOptionalClauses as $objClause) {
136: try {
137: $objClause->UpdateQueryBuilder($objQueryBuilder);
138: } catch (QCallerException $objExc) {
139: $objExc->IncrementOffset();
140: $objExc->IncrementOffset();
141: throw $objExc;
142: }
143:
144: }
145: }
146: else
147: throw new QCallerException('Optional Clauses must be a QQClause object or an array of QQClause objects');
148: }
149:
150: // Get the SQL Statement
151: $strQuery = $objQueryBuilder->GetStatement();
152:
153: // Substitute the correct sql variable names for the placeholders specified in the query, if any.
154: if ($mixParameterArray) {
155: if (is_array($mixParameterArray)) {
156: if (count($mixParameterArray))
157: $strQuery = $objDatabase->PrepareStatement($strQuery, $mixParameterArray);
158:
159: // Ensure that there are no other Unresolved Named Parameters
160: if (strpos($strQuery, chr(QQNamedValue::DelimiterCode) . '{') !== false)
161: throw new QCallerException('Unresolved named parameters in the query');
162: } else
163: throw new QCallerException('Parameter Array must be an array of name-value parameter pairs');
164: }
165:
166: // Return the Objects
167: return $strQuery;
168: }
169:
170: /**
171: * Static Qcubed Query method to query for a single <?php echo $objTable->ClassName ?> object.
172: * Uses BuildQueryStatment to perform most of the work.
173: * Is called by QuerySincle function of each object so that the correct return type will be put in the comments.
174: *
175: * @param QQCondition $objConditions any conditions on the query, itself
176: * @param null $objOptionalClauses
177: * @param mixed[] $mixParameterArray a array of name-value pairs to perform PrepareStatement with
178: * @throws Exception
179: * @throws QCallerException
180: * @return null|QModelBase the queried object
181: */
182: protected static function _QuerySingle(QQCondition $objConditions, $objOptionalClauses = null, $mixParameterArray = null) {
183: // Get the Query Statement
184: try {
185: $strQuery = static::BuildQueryStatement($objQueryBuilder, $objConditions, $objOptionalClauses, $mixParameterArray, false);
186: } catch (QCallerException $objExc) {
187: $objExc->IncrementOffset();
188: throw $objExc;
189: }
190:
191: // Perform the Query, Get the First Row, and Instantiate a new object
192: $objDbResult = $objQueryBuilder->Database->Query($strQuery);
193:
194: // Do we have to expand anything?
195: if ($objQueryBuilder->ExpandAsArrayNode) {
196: $objToReturn = array();
197: $objPrevItemArray = array();
198: while ($objDbRow = $objDbResult->GetNextRow()) {
199: $objItem = static::InstantiateDbRow($objDbRow, null, $objQueryBuilder->ExpandAsArrayNode, $objPrevItemArray, $objQueryBuilder->ColumnAliasArray);
200: if ($objItem) {
201: $objToReturn[] = $objItem;
202: $pk = $objItem->PrimaryKey();
203: if ($pk) {
204: $objPrevItemArray[$pk][] = $objItem;
205: } else {
206: $objPrevItemArray[] = $objItem;
207: }
208: }
209: }
210: if (count($objToReturn)) {
211: // Since we only want the object to return, lets return the object and not the array.
212: return $objToReturn[0];
213: } else {
214: return null;
215: }
216: } else {
217: // No expands just return the first row
218: $objDbRow = $objDbResult->GetNextRow();
219: if(null === $objDbRow)
220: return null;
221: return static::InstantiateDbRow($objDbRow, null, null, null, $objQueryBuilder->ColumnAliasArray);
222: }
223: }
224:
225: /**
226: * Static Qcubed Query method to query for an array of objects.
227: * Uses BuildQueryStatment to perform most of the work.
228: * Is called by QueryArray function of each object so that the correct return type will be put in the comments.
229: *
230: * @param QQCondition $objConditions any conditions on the query, itself
231: * @param QQClause[]|null $objOptionalClauses additional optional QQClause objects for this query
232: * @param mixed[]|null $mixParameterArray an array of name-value pairs to substitute in to the placeholders in the query, if needed
233: * @return mixed[] an array of objects
234: * @throws Exception
235: * @throws QCallerException
236: */
237: protected static function _QueryArray(QQCondition $objConditions, $objOptionalClauses = null, $mixParameterArray = null) {
238: // Get the Query Statement
239: try {
240: $strQuery = static::BuildQueryStatement($objQueryBuilder, $objConditions, $objOptionalClauses, $mixParameterArray, false);
241: } catch (QCallerException $objExc) {
242: $objExc->IncrementOffset();
243: throw $objExc;
244: }
245:
246: // Perform the Query and Instantiate the Array Result
247: $objDbResult = $objQueryBuilder->Database->Query($strQuery);
248: return static::InstantiateDbResult($objDbResult, $objQueryBuilder->ExpandAsArrayNode, $objQueryBuilder->ColumnAliasArray);
249: }
250:
251: /**
252: * Static Qcubed query method to issue a query and get a cursor to progressively fetch its results.
253: * Uses BuildQueryStatment to perform most of the work.
254: *
255: * @param QQCondition $objConditions any conditions on the query, itself
256: * @param QQClause[] $objOptionalClauses additional optional QQClause objects for this query
257: * @param mixed[] $mixParameterArray an array of name-value pairs to substitute in to the placeholders in the query, if needed
258: * @return QDatabaseResultBase the cursor resource instance
259: * @throws Exception
260: * @throws QCallerException
261: */
262: public static function QueryCursor(QQCondition $objConditions, $objOptionalClauses = null, $mixParameterArray = null) {
263: // Get the query statement
264: try {
265: $strQuery = static::BuildQueryStatement($objQueryBuilder, $objConditions, $objOptionalClauses, $mixParameterArray, false);
266: } catch (QCallerException $objExc) {
267: $objExc->IncrementOffset();
268: throw $objExc;
269: }
270:
271: // Perform the query
272: $objDbResult = $objQueryBuilder->Database->Query($strQuery);
273:
274: // Return the results cursor
275: $objDbResult->QueryBuilder = $objQueryBuilder;
276: return $objDbResult;
277: }
278:
279: /**
280: * Static Qcubed Query method to query for a count of objects.
281: * Uses BuildQueryStatment to perform most of the work.
282: *
283: * @param QQCondition $objConditions any conditions on the query, itself
284: * @param QQClause[] $objOptionalClauses additional optional QQClause objects for this query
285: * @param mixed[] $mixParameterArray a array of name-value pairs to perform PrepareStatement with
286: * @return integer the count of queried objects as an integer
287: */
288: public static function QueryCount(QQCondition $objConditions, $objOptionalClauses = null, $mixParameterArray = null) {
289: // Get the Query Statement
290: try {
291: $strQuery = static::BuildQueryStatement($objQueryBuilder, $objConditions, $objOptionalClauses, $mixParameterArray, true);
292: } catch (QCallerException $objExc) {
293: $objExc->IncrementOffset();
294: throw $objExc;
295: }
296:
297: // Perform the Query and return the row_count
298: $objDbResult = $objQueryBuilder->Database->Query($strQuery);
299:
300: // Figure out if the query is using GroupBy
301: $blnGrouped = false;
302:
303: if ($objOptionalClauses) {
304: if ($objOptionalClauses instanceof QQClause) {
305: if ($objOptionalClauses instanceof QQGroupBy) {
306: $blnGrouped = true;
307: }
308: } else if (is_array($objOptionalClauses)) {
309: foreach ($objOptionalClauses as $objClause) {
310: if ($objClause instanceof QQGroupBy) {
311: $blnGrouped = true;
312: break;
313: }
314: }
315: } else {
316: throw new QCallerException('Optional Clauses must be a QQClause object or an array of QQClause objects');
317: }
318: }
319:
320: if ($blnGrouped)
321: // Groups in this query - return the count of Groups (which is the count of all rows)
322: return $objDbResult->CountRows();
323: else {
324: // No Groups - return the sql-calculated count(*) value
325: $strDbRow = $objDbResult->FetchRow();
326: return (integer)$strDbRow[0];
327: }
328: }
329:
330: public static function QueryArrayCached(QQCondition $objConditions, $objOptionalClauses = null, $mixParameterArray = null, $blnForceUpdate = false) {
331: $strQuery = static::BuildQueryStatement($objQueryBuilder, $objConditions, $objOptionalClauses, $mixParameterArray, false);
332:
333: $strTableName = static::GetTableName();
334: $objCache = new QCache(sprintf('qquery/%s', $strTableName), $strQuery);
335: $cacheData = $objCache->GetData();
336:
337: if (!$cacheData || $blnForceUpdate) {
338: $objDbResult = $objQueryBuilder->Database->Query($strQuery);
339: $arrResult = static::InstantiateDbResult($objDbResult, $objQueryBuilder->ExpandAsArrayNode, $objQueryBuilder->ColumnAliasArray);
340: $objCache->SaveData(serialize($arrResult));
341: } else {
342: $arrResult = unserialize($cacheData);
343: }
344:
345: return $arrResult;
346: }
347:
348: /**
349: * Do a possible array expansion on the given node. If the node is an ExpandAsArray node,
350: * it will add to the corresponding array in the object. Otherwise, it will follow the node
351: * so that any leaf expansions can be handled.
352: *
353: * @param QDatabaseRowBase $objDbRow
354: * @param string $strAliasPrefix
355: * @param QQNode $objNode
356: * @param QModelBase[] $objPreviousItemArray
357: * @param string[] $strColumnAliasArray
358: * @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
359: */
360: public static function ExpandArray ($objDbRow, $strAliasPrefix, $objNode, $objPreviousItemArray, $strColumnAliasArray) {
361: if (!$objNode->ChildNodeArray) {
362: return null;
363: }
364: $blnExpanded = null;
365:
366: $pk = static::GetRowPrimaryKey ($objDbRow, $strAliasPrefix, $strColumnAliasArray);
367:
368: foreach ($objPreviousItemArray as $objPreviousItem) {
369: if ($pk != $objPreviousItem->PrimaryKey()) {
370: continue;
371: }
372:
373: foreach ($objNode->ChildNodeArray as $objChildNode) {
374: $strPropName = $objChildNode->_PropertyName;
375: $strClassName = $objChildNode->_ClassName;
376: $strLongAlias = $objChildNode->FullAlias();
377: $blnExpandAsArray = false;
378:
379: if ($objChildNode->ExpandAsArray) {
380: $strPostfix = 'Array';
381: $blnExpandAsArray = true;
382: } else {
383: $strPostfix = '';
384: }
385: $nodeType = $objChildNode->_Type;
386: if ($nodeType == 'reverse_reference') {
387: $strPrefix = '_obj';
388: } elseif ($nodeType == 'association') {
389: $objChildNode = $objChildNode->FirstChild();
390: if ($objChildNode->IsType) {
391: $strPrefix = '_int';
392: } else {
393: $strPrefix = '_obj';
394: }
395: } else {
396: $strPrefix = 'obj';
397: }
398:
399: $strVarName = $strPrefix . $strPropName . $strPostfix;
400:
401: if ($blnExpandAsArray) {
402: if (null === $objPreviousItem->$strVarName) {
403: $objPreviousItem->$strVarName = array();
404: }
405: if (count($objPreviousItem->$strVarName)) {
406: $objPreviousChildItems = $objPreviousItem->$strVarName;
407: $nextAlias = $objChildNode->FullAlias() . '__';
408:
409: $objChildItem = $strClassName::InstantiateDbRow ($objDbRow, $nextAlias, $objChildNode, $objPreviousChildItems, $strColumnAliasArray, true);
410:
411: if ($objChildItem) {
412: $objPreviousItem->{$strVarName}[] = $objChildItem;
413: $blnExpanded = true;
414: } elseif ($objChildItem === false) {
415: $blnExpanded = true;
416: }
417: }
418: } elseif (!$objChildNode->IsType) {
419:
420: // Follow single node if keys match
421: if (null === $objPreviousItem->$strVarName) {
422: return false;
423: }
424: $objPreviousChildItems = array($objPreviousItem->$strVarName);
425: $blnResult = $strClassName::ExpandArray ($objDbRow, $strLongAlias . '__', $objChildNode, $objPreviousChildItems, $strColumnAliasArray);
426:
427: if ($blnResult) {
428: $blnExpanded = true;
429: }
430: }
431: }
432: }
433: return $blnExpanded;
434: }
435:
436: /**
437: * Return an object corresponding to the given key, or null.
438: *
439: * The key might be null if:
440: * The table has no primary key, or
441: * SetSkipPrimaryKey was used in a query with QSelect.
442: *
443: * Otherwise, the default here is to use the local cache.
444: *
445: * Note that you rarely would want to change this. Caching at this level happens
446: * after a query has executed. Using a cache like APC or MemCache at this point would
447: * be really expensive, and would only be worth it for a large table.
448: *
449: * @param $key
450: * @return null|object
451: */
452: public static function GetFromCache($key) {
453: if ($key===null) return null;
454: if (QApplication::$blnLocalCache === true && !empty(static::$objCacheArray[$key])) {
455: return clone(static::$objCacheArray[$key]);
456: }
457: elseif (QApplication::$objCacheProvider) {
458: $strCacheKey = QApplication::$objCacheProvider->CreateKey(static::GetDatabase()->Database, __CLASS__, $key);
459: return QApplication::$objCacheProvider->Get($strCacheKey);
460: }
461: return null;
462: }
463:
464: /**
465: * Put the current object in the cache for future reference.
466: */
467: public function WriteToCache() {
468: $key = $this->PrimaryKey();
469: if ($key === null) return;
470: $obj = clone($this);
471: if (QApplication::$blnLocalCache === true) static::$objCacheArray[$key] = $obj;
472: if (QApplication::$objCacheProvider) {
473: $strCacheKey = QApplication::$objCacheProvider->CreateKey(static::GetDatabase()->Database, __CLASS__, $key);
474: QApplication::$objCacheProvider->Set($strCacheKey, $obj);
475: }
476: }
477:
478: /**
479: * Delete this particular object from the cache
480: * @return void
481: */
482: public function DeleteFromCache() {
483: $key = $this->PrimaryKey();
484: if ($key === null) return;
485: unset (static::$objCacheArray[$key]);
486: if (QApplication::$objCacheProvider) {
487: $strCacheKey = QApplication::$objCacheProvider->CreateKey(static::GetDatabase()->Database, __CLASS__, $key);
488: QApplication::$objCacheProvider->Delete($strCacheKey);
489: }
490: }
491:
492:
493: /**
494: * Clears the caches associated with this table.
495: */
496: public static function ClearCache() {
497: static::$objCacheArray = array();
498: if (QApplication::$objCacheProvider) {
499: QApplication::$objCacheProvider->DeleteAll();
500: }
501: }
502:
503: }