diff --git a/framework/db/ar/ActiveFinder.php b/framework/db/ar/ActiveFinder.php index 5d34409..0e55493 100644 --- a/framework/db/ar/ActiveFinder.php +++ b/framework/db/ar/ActiveFinder.php @@ -192,14 +192,32 @@ class ActiveFinder extends \yii\base\Object throw new Exception("$modelClass has no relation named '$with'."); } $relation = clone $relations[$with]; - if (is_string($relation->via) && isset($relations[$relation->via])) { + if (is_string($relation->via)) { // join via an existing relation $parent2 = $this->buildJoinTree($parent, $relation->via); - $relation->via = null; if ($parent2->joinOnly === null) { $parent2->joinOnly = true; } $child = new JoinElement($this->_joinCount++, $relation, $parent2, $parent); + } elseif (is_array($relation->via)) { + // join via a pivoting table + $r = new ActiveRelation; + $r->name = 'pt' . $this->_joinCount; + $r->hasMany = $relation->hasMany; + + foreach ($relation->via as $name => $value) { + $r->$name = $value; + } + + $r->select = false; + if ($r->joinType === null) { + $r->joinType = $relation->joinType; + } + + $parent2 = new JoinElement($this->_joinCount++, $r, $parent, $parent); + $parent2->joinOnly = true; + $child = new JoinElement($this->_joinCount++, $relation, $parent2, $parent); + } else { $child = new JoinElement($this->_joinCount++, $relation, $parent, $parent); } @@ -239,7 +257,9 @@ class ActiveFinder extends \yii\base\Object $this->_tableAliases[$alias] = true; $element->query->tableAlias = $alias; - $this->applyScopes($element->query); + if ($element->query->modelClass !== null) { + $this->applyScopes($element->query); + } foreach ($element->children as $child) { $this->initJoinTree($child, $count); @@ -298,22 +318,41 @@ class ActiveFinder extends \yii\base\Object } if ($element->query instanceof ActiveRelation) { - if (is_array($element->query->via)) { - // todo: join via a pivot table - // $query->join[] = strtr($element->query->via, $quotedPrefixes); - } - + $joinType = if ($element->query->joinType === null) { - $joinType = $element->query->select === false ? 'INNER JOIN' : 'LEFT JOIN'; + $joinType = 'LEFT JOIN'; } else { $joinType = $element->query->joinType; } - $modelClass = $element->query->modelClass; - $tableName = $this->connection->quoteTableName($modelClass::tableName()); + if ($element->query->modelClass !== null) { + $modelClass = $element->query->modelClass; + $tableName = $this->connection->quoteTableName($modelClass::tableName()); + } else { + $tableName = $this->connection->quoteTableName($element->query->table); + } $tableAlias = $this->connection->quoteTableName($element->query->tableAlias); $join = "$joinType $tableName $tableAlias"; + $on = ''; + if (is_array($element->query->link)) { + foreach ($element->query->link as $pk => $fk) { + $pk = $quotedPrefixes['@.'] . $this->connection->quoteColumnName($pk, true); + $fk = $quotedPrefixes['?.'] . $this->connection->quoteColumnName($fk, true); + if ($on !== '') { + $on .= ' AND '; + } + $on .= "$pk = $fk"; + } + } if ($element->query->on !== null) { - $join .= ' ON ' . strtr($qb->buildCondition($element->query->on), $quotedPrefixes); + $condition = strtr($qb->buildCondition($element->query->on), $quotedPrefixes); + if ($on !== '') { + $on .= " AND ($condition)"; + } else { + $on = $condition; + } + } + if ($on !== '') { + $join .= ' ON ' . $on; } $query->join[] = $join; } diff --git a/framework/db/ar/ActiveRelation.php b/framework/db/ar/ActiveRelation.php index 72b242c..fe7975c 100644 --- a/framework/db/ar/ActiveRelation.php +++ b/framework/db/ar/ActiveRelation.php @@ -23,12 +23,9 @@ class ActiveRelation extends BaseActiveQuery */ public $name; /** - * @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 - * must be the corresponding columns from the primary table. Do not prefix or quote the column names. - * They will be done automatically by Yii. + * @var string the name of the table */ - public $link; + public $table; /** * @var boolean whether this relation is a one-many relation */ @@ -39,6 +36,13 @@ class ActiveRelation extends BaseActiveQuery */ public $joinType; /** + * @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 + * must be the corresponding columns from the primary table. Do not prefix or quote the column names. + * They will be done automatically by Yii. + */ + public $link; + /** * @var string the ON clause of the join query */ public $on; diff --git a/tests/unit/data/ar/Customer.php b/tests/unit/data/ar/Customer.php index 5c108da..b2a0907 100644 --- a/tests/unit/data/ar/Customer.php +++ b/tests/unit/data/ar/Customer.php @@ -16,7 +16,7 @@ class Customer extends ActiveRecord { return array( 'orders:Order[]' => array( - 'on' => '@.customer_id = ?.id', + 'link' => array('customer_id' => 'id'), ), ); } @@ -25,7 +25,7 @@ class Customer extends ActiveRecord { return array( 'active' => function($q) { - return $q->andWhere('@.status = ' . self::STATUS_ACTIVE); + return $q->andWhere('@.`status` = ' . Customer::STATUS_ACTIVE); }, ); } diff --git a/tests/unit/data/ar/Order.php b/tests/unit/data/ar/Order.php index 5e0d4b6..09365f0 100644 --- a/tests/unit/data/ar/Order.php +++ b/tests/unit/data/ar/Order.php @@ -13,7 +13,30 @@ class Order extends ActiveRecord { return array( 'customer:Customer' => array( - 'on' => '@.id = ?.customer_id', + 'link' => array('id' => 'customer_id'), + ), + 'orderItems:OrderItem' => array( + 'link' => array('order_id' => 'id'), + ), + 'items:Item[]' => array( + 'via' => 'orderItems', + 'link' => array( + 'id' => 'item_id', + ), + 'order' => '@.id', + ), + 'books:Item[]' => array( + 'joinType' => 'INNER JOIN', + 'via' => array( + 'table' => 'tbl_order_item', + 'link' => array( + 'order_id' => 'id', + ), + ), + 'link' => array( + 'id' => 'item_id', + ), + 'on' => '@.category_id = 1', ), ); } diff --git a/tests/unit/data/ar/OrderItem.php b/tests/unit/data/ar/OrderItem.php index 4d06506..c2ef796 100644 --- a/tests/unit/data/ar/OrderItem.php +++ b/tests/unit/data/ar/OrderItem.php @@ -13,10 +13,10 @@ class OrderItem extends ActiveRecord { return array( 'order:Order' => array( - 'on' => '@.order_id = ?.id', + 'link' => array('order_id' => 'id'), ), 'item:Item' => array( - 'on' => '@.item_id = ?.id', + 'link' => array('item_id' => 'id'), ), ); } diff --git a/tests/unit/framework/db/ar/ActiveRecordTest.php b/tests/unit/framework/db/ar/ActiveRecordTest.php index 636be01..88ab27e 100644 --- a/tests/unit/framework/db/ar/ActiveRecordTest.php +++ b/tests/unit/framework/db/ar/ActiveRecordTest.php @@ -7,6 +7,7 @@ use yii\db\ar\ActiveQuery; use yiiunit\data\ar\ActiveRecord; use yiiunit\data\ar\Customer; use yiiunit\data\ar\OrderItem; +use yiiunit\data\ar\Order; class ActiveRecordTest extends \yiiunit\MysqlTestCase { @@ -185,7 +186,7 @@ class ActiveRecordTest extends \yiiunit\MysqlTestCase // count $query = Customer::findBySql('SELECT * FROM tbl_customer ORDER BY id DESC'); $query->one(); - $this->assertEquals(1, $query->count); + $this->assertEquals(3, $query->count); $query = Customer::findBySql('SELECT * FROM tbl_customer ORDER BY id DESC'); $this->assertEquals(3, $query->count); } @@ -235,6 +236,20 @@ class ActiveRecordTest extends \yiiunit\MysqlTestCase $this->assertEquals(1, $customers[0]->orders[0]->customer->id); $this->assertEquals(2, $customers[1]->orders[0]->customer->id); $this->assertEquals(2, $customers[1]->orders[1]->customer->id); + + $orders = Order::find()->with('items')->order('@.id')->all(); + $this->assertEquals(3, count($orders)); + $this->assertEquals(1, $orders[0]->items[0]->id); + $this->assertEquals(2, $orders[0]->items[1]->id); + $this->assertEquals(3, $orders[1]->items[0]->id); + $this->assertEquals(4, $orders[1]->items[1]->id); + $this->assertEquals(5, $orders[1]->items[2]->id); + + $orders = Order::find()->with('books')->order('@.id')->all(); + $this->assertEquals(2, count($orders)); + $this->assertEquals(1, $orders[0]->books[0]->id); + $this->assertEquals(2, $orders[0]->books[1]->id); + $this->assertEquals(2, $orders[1]->books[0]->id); } /*