Subversion-Projekte lars-tiefland.prado

Revision

Details | Letzte Änderung | Log anzeigen | RSS feed

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