ActiveRelation.php 5.05 KB
Newer Older
Qiang Xue committed
1
<?php
Qiang Xue committed
2 3 4 5 6 7 8 9
/**
 * ActiveRelation class file.
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @link http://www.yiiframework.com/
 * @copyright Copyright &copy; 2008-2012 Yii Software LLC
 * @license http://www.yiiframework.com/license/
 */
Qiang Xue committed
10 11 12

namespace yii\db\ar;

Qiang Xue committed
13
/**
Qiang Xue committed
14 15 16 17
 * It is used in three scenarios:
 * - eager loading: User::find()->with('posts')->all();
 * - lazy loading: $user->posts;
 * - lazy loading with query options: $user->posts()->where('status=1')->get();
Qiang Xue committed
18 19 20 21
 *
 * @author Qiang Xue <qiang.xue@gmail.com>
 * @since 2.0
 */
Qiang Xue committed
22
class ActiveRelation extends ActiveQuery
Qiang Xue committed
23
{
Qiang Xue committed
24
	/**
Qiang Xue committed
25
	 * @var ActiveRecord the primary model that this relation is associated with.
Qiang Xue committed
26
	 * This is used only in lazy loading with dynamic query options.
27
	 */
Qiang Xue committed
28
	public $primaryModel;
Qiang Xue committed
29
	/**
Qiang Xue committed
30 31
	 * @var boolean whether this relation should populate all query results into AR instances.
	 * If false, only the first row of the results will be taken.
Qiang Xue committed
32
	 */
Qiang Xue committed
33
	public $multiple;
Qiang Xue committed
34 35 36
	/**
	 * @var array the columns of the primary and foreign tables that establish the relation.
	 * The array keys must be columns of the table for this relation, and the array values
Qiang Xue committed
37 38
	 * must be the corresponding columns from the primary table.
	 * Do not prefix or quote the column names as they will be done automatically by Yii.
Qiang Xue committed
39 40
	 */
	public $link;
Qiang Xue committed
41
	/**
Qiang Xue committed
42
	 * @var array
Qiang Xue committed
43
	 */
Qiang Xue committed
44
	public $via;
Qiang Xue committed
45 46 47 48 49 50 51
	/**
	 * @var array
	 */
	public $viaTable;

	public function via($modelClass, $properties = array())
	{
Qiang Xue committed
52
		$this->via = $modelClass;
Qiang Xue committed
53 54 55 56 57 58 59 60
		return $this;
	}

	public function viaTable($tableName, $link, $properties = array())
	{
		$this->viaTable = array($tableName, $link, $properties);
		return $this;
	}
Qiang Xue committed
61

62
	public function createCommand()
Qiang Xue committed
63
	{
Qiang Xue committed
64
		if ($this->primaryModel !== null) {
65 66 67
			if ($this->via !== null) {
				/** @var $viaQuery ActiveRelation */
				$viaName = $this->via;
Qiang Xue committed
68 69 70 71 72 73
				$viaModels = $this->primaryModel->$viaName;
				if ($viaModels === null) {
					$viaModels = array();
				} elseif (!is_array($viaModels)) {
					$viaModels = array($viaModels);
				}
74 75 76 77
				$this->filterByModels($viaModels);
			} else {
				$this->filterByModels(array($this->primaryModel));
			}
Qiang Xue committed
78
		}
79
		return parent::createCommand();
Qiang Xue committed
80 81
	}

Qiang Xue committed
82
	public function findWith($name, &$primaryModels, $viaQuery = null)
Qiang Xue committed
83
	{
Qiang Xue committed
84
		if (!is_array($this->link)) {
Qiang Xue committed
85 86
			throw new \yii\base\Exception('invalid link');
		}
Qiang Xue committed
87

Qiang Xue committed
88 89
		if ($viaQuery !== null) {
			$viaModels = $viaQuery->findWith($this->via, $primaryModels);
90 91 92
			$this->filterByModels($viaModels);
		} else {
			$this->filterByModels($primaryModels);
Qiang Xue committed
93 94
		}

Qiang Xue committed
95
		if (count($primaryModels) === 1 && !$this->multiple) {
96
			$model = $this->one();
Qiang Xue committed
97
			foreach ($primaryModels as $i => $primaryModel) {
98
				$primaryModels[$i][$name] = $model;
Qiang Xue committed
99
			}
100
			return array($model);
Qiang Xue committed
101 102
		} else {
			$models = $this->all();
103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
			if (isset($viaModels, $viaQuery)) {
				$buckets = $this->buildBuckets($models, $this->link, $viaModels, $viaQuery->link);
			} else {
				$buckets = $this->buildBuckets($models, $this->link);
			}

			foreach ($primaryModels as $i => $primaryModel) {
				if (isset($viaQuery)) {
					$key = $this->getModelKey($primaryModel, array_values($viaQuery->link));
				} else {
					$key = $this->getModelKey($primaryModel, array_values($this->link));
				}
				if (isset($buckets[$key])) {
					$primaryModels[$i][$name] = $buckets[$key];
				} else {
					$primaryModels[$i][$name] = $this->multiple ? array() : null;
				}
			}
			return $models;
Qiang Xue committed
122 123 124
		}
	}

125
	protected function buildBuckets($models, $link, $viaModels = null, $viaLink = null)
Qiang Xue committed
126 127 128
	{
		$buckets = array();
		foreach ($models as $i => $model) {
129
			$key = $this->getModelKey($model, array_keys($link));
Qiang Xue committed
130 131 132 133
			if ($this->index !== null) {
				$buckets[$key][$i] = $model;
			} else {
				$buckets[$key][] = $model;
Qiang Xue committed
134
			}
Qiang Xue committed
135
		}
136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154

		if ($viaModels !== null) {
			$viaBuckets = array();
			foreach ($viaModels as $viaModel) {
				$key1 = $this->getModelKey($viaModel, array_keys($viaLink));
				$key2 = $this->getModelKey($viaModel, array_values($link));
				if (isset($buckets[$key2])) {
					foreach ($buckets[$key2] as $i => $bucket) {
						if ($this->index !== null) {
							$viaBuckets[$key1][$i] = $bucket;
						} else {
							$viaBuckets[$key1][] = $bucket;
						}
					}
				}
			}
			$buckets = $viaBuckets;
		}

Qiang Xue committed
155 156 157
		if (!$this->multiple) {
			foreach ($buckets as $i => $bucket) {
				$buckets[$i] = reset($bucket);
Qiang Xue committed
158
			}
Qiang Xue committed
159
		}
160
		return $buckets;
Qiang Xue committed
161 162
	}

Qiang Xue committed
163
	protected function getModelKey($model, $attributes)
Qiang Xue committed
164
	{
Qiang Xue committed
165
		if (count($attributes) > 1) {
Qiang Xue committed
166 167
			$key = array();
			foreach ($attributes as $attribute) {
Qiang Xue committed
168
				$key[] = $model[$attribute];
Qiang Xue committed
169 170 171
			}
			return serialize($key);
		} else {
Qiang Xue committed
172 173
			$attribute = reset($attributes);
			return $model[$attribute];
Qiang Xue committed
174 175 176
		}
	}

177
	protected function filterByModels($models)
Qiang Xue committed
178 179 180
	{
		$attributes = array_keys($this->link);
		$values = array();
Qiang Xue committed
181 182 183 184 185 186 187
		if (count($attributes) ===1) {
			// single key
			$attribute = reset($this->link);
			foreach ($models as $model) {
				$values[] = $model[$attribute];
			}
		} else {
Qiang Xue committed
188
			// composite keys
189
			foreach ($models as $model) {
Qiang Xue committed
190 191
				$v = array();
				foreach ($this->link as $attribute => $link) {
Qiang Xue committed
192
					$v[$attribute] = $model[$link];
Qiang Xue committed
193 194 195 196 197 198 199
				}
				$values[] = $v;
			}
		}
		$this->andWhere(array('in', $attributes, $values));
	}

Qiang Xue committed
200
}