summaryrefslogtreecommitdiff
path: root/framework/Data/ActiveRecord/Relations/TActiveRecordRelation.php
blob: f1e8fa60d745c8ecc9b9789d2f0923e893106936 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
<?php
Prado::using('System.Data.ActiveRecord.Relations.TActiveRecordRelationContext');

abstract class TActiveRecordRelation
{
	private $_context;

	public function __construct(TActiveRecordRelationContext $context)
	{
		$this->_context = $context;
	}

	/**
	 * @return TActiveRecordRelationContext
	 */
	protected function getContext()
	{
		return $this->_context;
	}

	/**
	 * @return TActiveRecord
	 */
	protected function getSourceRecord()
	{
		return $this->getContext()->getSourceRecord();
	}

	/**
	 * Dispatch the method calls to the source record finder object. When
	 * the results are returned as array or is an instance of TActiveRecord we
	 * will fetch the corresponding foreign objects with an sql query and populate
	 * the results obtained earlier.
	 *
	 * Allows chaining multiple relation handlers.
	 *
	 * @param string method name called
	 * @param array method arguments
	 * @return mixed TActiveRecord or array of TActiveRecord results depending on the method called.
	 */
	public function __call($method,$args)
	{
		static $stack=array();

		$results = call_user_func_array(array($this->getSourceRecord(),$method),$args);
		if(is_array($results) || $results instanceof TActiveRecord)
		{
			$this->collectForeignObjects($results);
			while($obj = array_pop($stack))
				$obj->collectForeignObjects($results);
		}
		else if($results instanceof TActiveRecordRelation)
			array_push($stack,$this); //call it later
		return $results;
	}

	/**
	 * Returns foreign keys in $fromRecord with source column names as key
	 * and foreign column names in the corresponding $matchesRecord as value.
	 * The method returns the first matching foreign key between these 2 records.
	 * @param TActiveRecord $fromRecord
	 * @param TActiveRecord $matchesRecord
	 * @return array foreign keys with source column names as key and foreign column names as value.
	 */
	protected function findForeignKeys($from, $matchesRecord)
	{
		$gateway = $matchesRecord->getRecordGateway();
		$matchingTableName = $gateway->getRecordTableInfo($matchesRecord)->getTableName();
		$tableInfo=$from;
		if($from instanceof TActiveRecord)
			$tableInfo = $gateway->getRecordTableInfo($from);
		foreach($tableInfo->getForeignKeys() as $fkeys)
		{
			if($fkeys['table']===$matchingTableName)
				return $fkeys['keys'];
		}
		throw new TActiveRecordException('no fk defined for '.$tableInfo->getTableFullName());
	}

	/**
	 * @param mixed object or array to be hashed
	 * @param array name of property for hashing the properties.
	 * @return string object hash using crc32 and serialize.
	 */
	protected function getObjectHash($obj, $properties)
	{
		$ids=array();
		foreach($properties as $property)
			$ids[] = is_object($obj) ? $obj->{$property} : $obj[$property];
		return sprintf('%x',crc32(serialize($ids)));
	}

	/**
	 * Fetches the foreign objects using TActiveRecord::findAllByIndex()
	 * @param array field names
	 * @param array foreign key index values.
	 * @return TActiveRecord[] foreign objects.
	 */
	protected function findForeignObjects($fields, $indexValues)
	{
		$criteria = $this->getContext()->getCriteria();
		$finder = $this->getContext()->getForeignRecordFinder();
		return $finder->findAllByIndex($criteria, $fields, $indexValues);
	}

	/**
	 * Obtain the foreign key index values from the results.
	 * @param array property names
	 * @param array|TActiveRecord TActiveRecord results
	 * @return array foreign key index values.
	 */
	protected function getIndexValues($keys, $results)
	{
		if(!is_array($results))
			$results = array($results);
		foreach($results as $result)
		{
			$value = array();
			foreach($keys as $name)
				$value[] = $result->{$name};
			$values[] = $value;
		}
		return $values;
	}

	/**
	 * Populate the results with the foreign objects found.
	 * @param array source results
	 * @param array source property names
	 * @param array foreign objects
	 * @param array foreign object field names.
	 */
	protected function populateResult(&$results,$properties,&$fkObjects,$fields)
	{
		$collections=array();
		foreach($fkObjects as $fkObject)
		{
			$hash = $this->getObjectHash($fkObject, $fields);
			$collections[$hash][]=$fkObject;
		}

		$this->setResultCollection($results, $collections, $properties);
	}

	protected function setResultCollection(&$results, &$collections, $properties)
	{
		if(is_array($results))
		{
			for($i=0,$k=count($results);$i<$k;$i++)
				$this->setObjectProperty($results[$i], $properties, $collections);
		}
		else
		{
			$this->setObjectProperty($results, $properties, $collections);
		}
	}

	/**
	 * Sets the foreign objects to the given property on the source object.
	 * @param TActiveRecord source object.
	 * @param array source properties
	 * @param array foreign objects.
	 */
	protected function setObjectProperty($source, $properties, &$collections)
	{
		$hash = $this->getObjectHash($source, $properties);
		$prop = $this->getContext()->getProperty();
		$source->{$prop} = isset($collections[$hash]) ? $collections[$hash] : array();
	}
}

?>