PageRenderTime 30ms CodeModel.GetById 6ms RepoModel.GetById 1ms app.codeStats 0ms

/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php

http://prado3.googlecode.com/
PHP | 254 lines | 133 code | 21 blank | 100 comment | 20 complexity | a11c05b493f624f1c17aad8142257d55 MD5 | raw file
Possible License(s): Apache-2.0, IPL-1.0, LGPL-3.0, LGPL-2.1, BSD-3-Clause
  1. <?php
  2. /**
  3. * TActiveRecordRelation class file.
  4. *
  5. * @author Wei Zhuo <weizhuo[at]gmail[dot]com>
  6. * @link http://www.pradosoft.com/
  7. * @copyright Copyright &copy; 2005-2012 PradoSoft
  8. * @license http://www.pradosoft.com/license/
  9. * @version $Id$
  10. * @package System.Data.ActiveRecord.Relations
  11. */
  12. /**
  13. * Load active record relationship context.
  14. */
  15. Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordRelationContext');
  16. /**
  17. * Base class for active record relationships.
  18. *
  19. * @author Wei Zhuo <weizho[at]gmail[dot]com>
  20. * @version $Id$
  21. * @package System.Data.ActiveRecord.Relations
  22. * @since 3.1
  23. */
  24. abstract class TActiveRecordRelation
  25. {
  26. private $_context;
  27. private $_criteria;
  28. public function __construct(TActiveRecordRelationContext $context, $criteria)
  29. {
  30. $this->_context = $context;
  31. $this->_criteria = $criteria;
  32. }
  33. /**
  34. * @return TActiveRecordRelationContext
  35. */
  36. protected function getContext()
  37. {
  38. return $this->_context;
  39. }
  40. /**
  41. * @return TActiveRecordCriteria
  42. */
  43. protected function getCriteria()
  44. {
  45. return $this->_criteria;
  46. }
  47. /**
  48. * @return TActiveRecord
  49. */
  50. protected function getSourceRecord()
  51. {
  52. return $this->getContext()->getSourceRecord();
  53. }
  54. abstract protected function collectForeignObjects(&$results);
  55. /**
  56. * Dispatch the method calls to the source record finder object. When
  57. * an instance of TActiveRecord or an array of TActiveRecord is returned
  58. * the corresponding foreign objects are also fetched and assigned.
  59. *
  60. * Multiple relationship calls can be chain together.
  61. *
  62. * @param string method name called
  63. * @param array method arguments
  64. * @return mixed TActiveRecord or array of TActiveRecord results depending on the method called.
  65. */
  66. public function __call($method,$args)
  67. {
  68. static $stack=array();
  69. $results = call_user_func_array(array($this->getSourceRecord(),$method),$args);
  70. $validArray = is_array($results) && count($results) > 0;
  71. if($validArray || $results instanceof ArrayAccess || $results instanceof TActiveRecord)
  72. {
  73. $this->collectForeignObjects($results);
  74. while($obj = array_pop($stack))
  75. $obj->collectForeignObjects($results);
  76. }
  77. else if($results instanceof TActiveRecordRelation)
  78. $stack[] = $this; //call it later
  79. else if($results === null || !$validArray)
  80. $stack = array();
  81. return $results;
  82. }
  83. /**
  84. * Fetch results for current relationship.
  85. * @return boolean always true.
  86. */
  87. public function fetchResultsInto($obj)
  88. {
  89. $this->collectForeignObjects($obj);
  90. return true;
  91. }
  92. /**
  93. * Returns foreign keys in $fromRecord with source column names as key
  94. * and foreign column names in the corresponding $matchesRecord as value.
  95. * The method returns the first matching foreign key between these 2 records.
  96. * @param TActiveRecord $fromRecord
  97. * @param TActiveRecord $matchesRecord
  98. * @return array foreign keys with source column names as key and foreign column names as value.
  99. */
  100. protected function findForeignKeys($from, $matchesRecord, $loose=false)
  101. {
  102. $gateway = $matchesRecord->getRecordGateway();
  103. $recordTableInfo = $gateway->getRecordTableInfo($matchesRecord);
  104. $matchingTableName = strtolower($recordTableInfo->getTableName());
  105. $matchingFullTableName = strtolower($recordTableInfo->getTableFullName());
  106. $tableInfo=$from;
  107. if($from instanceof TActiveRecord)
  108. $tableInfo = $gateway->getRecordTableInfo($from);
  109. //find first non-empty FK
  110. foreach($tableInfo->getForeignKeys() as $fkeys)
  111. {
  112. $fkTable = strtolower($fkeys['table']);
  113. if($fkTable===$matchingTableName || $fkTable===$matchingFullTableName)
  114. {
  115. $hasFkField = !$loose && $this->getContext()->hasFkField();
  116. $key = $hasFkField ? $this->getFkFields($fkeys['keys']) : $fkeys['keys'];
  117. if(!empty($key))
  118. return $key;
  119. }
  120. }
  121. //none found
  122. $matching = $gateway->getRecordTableInfo($matchesRecord)->getTableFullName();
  123. throw new TActiveRecordException('ar_relations_missing_fk',
  124. $tableInfo->getTableFullName(), $matching);
  125. }
  126. /**
  127. * @return array foreign key field names as key and object properties as value.
  128. * @since 3.1.2
  129. */
  130. abstract public function getRelationForeignKeys();
  131. /**
  132. * Find matching foreign key fields from the 3rd element of an entry in TActiveRecord::$RELATION.
  133. * Assume field names consist of [\w-] character sets. Prefix to the field names ending with a dot
  134. * are ignored.
  135. */
  136. private function getFkFields($fkeys)
  137. {
  138. $matching = array();
  139. preg_match_all('/\s*(\S+\.)?([\w-]+)\s*/', $this->getContext()->getFkField(), $matching);
  140. $fields = array();
  141. foreach($fkeys as $fkName => $field)
  142. {
  143. if(in_array($fkName, $matching[2]))
  144. $fields[$fkName] = $field;
  145. }
  146. return $fields;
  147. }
  148. /**
  149. * @param mixed object or array to be hashed
  150. * @param array name of property for hashing the properties.
  151. * @return string object hash using crc32 and serialize.
  152. */
  153. protected function getObjectHash($obj, $properties)
  154. {
  155. $ids=array();
  156. foreach($properties as $property)
  157. $ids[] = is_object($obj) ? (string)$obj->getColumnValue($property) : (string)$obj[$property];
  158. return serialize($ids);
  159. }
  160. /**
  161. * Fetches the foreign objects using TActiveRecord::findAllByIndex()
  162. * @param array field names
  163. * @param array foreign key index values.
  164. * @return TActiveRecord[] foreign objects.
  165. */
  166. protected function findForeignObjects($fields, $indexValues)
  167. {
  168. $finder = $this->getContext()->getForeignRecordFinder();
  169. return $finder->findAllByIndex($this->_criteria, $fields, $indexValues);
  170. }
  171. /**
  172. * Obtain the foreign key index values from the results.
  173. * @param array property names
  174. * @param array TActiveRecord results
  175. * @return array foreign key index values.
  176. */
  177. protected function getIndexValues($keys, $results)
  178. {
  179. if(!is_array($results) && !$results instanceof ArrayAccess)
  180. $results = array($results);
  181. $values=array();
  182. foreach($results as $result)
  183. {
  184. $value = array();
  185. foreach($keys as $name)
  186. $value[] = $result->getColumnValue($name);
  187. $values[] = $value;
  188. }
  189. return $values;
  190. }
  191. /**
  192. * Populate the results with the foreign objects found.
  193. * @param array source results
  194. * @param array source property names
  195. * @param array foreign objects
  196. * @param array foreign object field names.
  197. */
  198. protected function populateResult(&$results,$properties,&$fkObjects,$fields)
  199. {
  200. $collections=array();
  201. foreach($fkObjects as $fkObject)
  202. $collections[$this->getObjectHash($fkObject, $fields)][]=$fkObject;
  203. $this->setResultCollection($results, $collections, $properties);
  204. }
  205. /**
  206. * Populates the result array with foreign objects (matched using foreign key hashed property values).
  207. * @param array $results
  208. * @param array $collections
  209. * @param array property names
  210. */
  211. protected function setResultCollection(&$results, &$collections, $properties)
  212. {
  213. if(is_array($results) || $results instanceof ArrayAccess)
  214. {
  215. for($i=0,$k=count($results);$i<$k;$i++)
  216. $this->setObjectProperty($results[$i], $properties, $collections);
  217. }
  218. else
  219. $this->setObjectProperty($results, $properties, $collections);
  220. }
  221. /**
  222. * Sets the foreign objects to the given property on the source object.
  223. * @param TActiveRecord source object.
  224. * @param array source properties
  225. * @param array foreign objects.
  226. */
  227. protected function setObjectProperty($source, $properties, &$collections)
  228. {
  229. $hash = $this->getObjectHash($source, $properties);
  230. $prop = $this->getContext()->getProperty();
  231. $source->$prop=isset($collections[$hash]) ? $collections[$hash] : array();
  232. }
  233. }