Commit 23572c74 by Qiang Xue

refactoring AR.

parent 78348d78
...@@ -232,14 +232,7 @@ class ActiveQuery extends BaseQuery ...@@ -232,14 +232,7 @@ class ActiveQuery extends BaseQuery
// inherit asArray from primary query // inherit asArray from primary query
$relation->asArray = $this->asArray; $relation->asArray = $this->asArray;
} }
if ($relation->via !== null) { $relation->findWith($name, $models);
$viaName = $relation->via;
$viaQuery = $primaryModel->$viaName();
$viaQuery->primaryModel = null;
$relation->findWith($name, $models, $viaQuery);
} else {
$relation->findWith($name, $models);
}
} }
} }
} }
<?php
/**
* ActiveQueryBuilder class file.
*
* @link http://www.yiiframework.com/
* @copyright Copyright &copy; 2008-2012 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db\ar;
/**
* ActiveQueryBuilder is ...
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ActiveQueryBuilder extends \yii\base\Object
{
/**
* @var \yii\db\dao\QueryBuilder
*/
public $queryBuilder;
/**
* @var ActiveQuery
*/
public $query;
public function __construct($query, $config = array())
{
$this->query = $query;
parent::__construct($config);
}
public function build()
{
}
}
\ No newline at end of file
...@@ -10,6 +10,10 @@ ...@@ -10,6 +10,10 @@
namespace yii\db\ar; namespace yii\db\ar;
use yii\db\dao\Connection;
use yii\db\dao\Command;
use yii\db\dao\QueryBuilder;
/** /**
* It is used in three scenarios: * It is used in three scenarios:
* - eager loading: User::find()->with('posts')->all(); * - eager loading: User::find()->with('posts')->all();
...@@ -22,50 +26,90 @@ namespace yii\db\ar; ...@@ -22,50 +26,90 @@ namespace yii\db\ar;
class ActiveRelation extends ActiveQuery class ActiveRelation extends ActiveQuery
{ {
/** /**
* @var ActiveRecord the primary model that this relation is associated with.
* This is used only in lazy loading with dynamic query options.
*/
public $primaryModel;
/**
* @var boolean whether this relation should populate all query results into AR instances. * @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. * If false, only the first row of the results will be taken.
*/ */
public $multiple; public $multiple;
/** /**
* @var ActiveRecord the primary model that this relation is associated with.
* This is used only in lazy loading with dynamic query options.
*/
protected $primaryModel;
/**
* @var array the columns of the primary and foreign tables that establish the relation. * @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 * 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. * 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. * Do not prefix or quote the column names as they will be done automatically by Yii.
*/ */
public $link; protected $link;
/** /**
* @var array * @var array|ActiveRelation
*/ */
public $via; protected $via;
/** /**
* @var array * @param string $relationName
* @param array|\Closure $options
* @return ActiveRelation
*/ */
public $viaTable; public function via($relationName, $options = null)
public function via($modelClass, $properties = array())
{ {
$this->via = $modelClass; /** @var $relation ActiveRelation */
$relation = $this->primaryModel->$relationName();
$relation->primaryModel = null;
$this->via = array($relationName, $relation);
if (is_array($options)) {
foreach ($options as $name => $value) {
$this->$name = $value;
}
} elseif ($options instanceof \Closure) {
$options($relation);
}
return $this; return $this;
} }
public function viaTable($tableName, $link, $properties = array()) /**
* @param string $tableName
* @param array $link
* @param array|\Closure $options
* @return ActiveRelation
*/
public function viaTable($tableName, $link, $options = null)
{ {
$this->viaTable = array($tableName, $link, $properties); $relation = new ActiveRelation(array(
'modelClass' => get_class($this->primaryModel),
'from' => array($tableName),
'link' => $link,
'multiple' => true,
'asArray' => true,
));
$this->via = $relation;
if (is_array($options)) {
foreach ($options as $name => $value) {
$this->$name = $value;
}
} elseif ($options instanceof \Closure) {
$options($relation);
}
return $this; return $this;
} }
/**
* Creates a DB command that can be used to execute this query.
* @return Command the created DB command instance.
*/
public function createCommand() public function createCommand()
{ {
if ($this->primaryModel !== null) { if ($this->primaryModel !== null) {
if ($this->via !== null) { // lazy loading
/** @var $viaQuery ActiveRelation */ if ($this->via instanceof self) {
$viaName = $this->via; // via pivot table
$viaModels = $this->primaryModel->$viaName; $viaModels = $this->via->findPivotRows(array($this->primaryModel));
$this->filterByModels($viaModels);
} elseif (is_array($this->via)) {
// via relation
$relationName = $this->via[0];
$viaModels = $this->primaryModel->$relationName;
if ($viaModels === null) { if ($viaModels === null) {
$viaModels = array(); $viaModels = array();
} elseif (!is_array($viaModels)) { } elseif (!is_array($viaModels)) {
...@@ -79,14 +123,23 @@ class ActiveRelation extends ActiveQuery ...@@ -79,14 +123,23 @@ class ActiveRelation extends ActiveQuery
return parent::createCommand(); return parent::createCommand();
} }
public function findWith($name, &$primaryModels, $viaQuery = null) public function findWith($name, &$primaryModels)
{ {
if (!is_array($this->link)) { if (!is_array($this->link)) {
throw new \yii\base\Exception('invalid link'); throw new \yii\base\Exception('invalid link');
} }
if ($viaQuery !== null) { if ($this->via instanceof self) {
$viaModels = $viaQuery->findWith($this->via, $primaryModels); // via pivot table
/** @var $viaQuery ActiveRelation */
$viaQuery = $this->via;
$viaModels = $viaQuery->findPivotRows($primaryModels);
$this->filterByModels($viaModels);
} elseif (is_array($this->via)) {
// via relation
/** @var $viaQuery ActiveRelation */
list($viaName, $viaQuery) = $this->via;
$viaModels = $viaQuery->findWith($viaName, $primaryModels);
$this->filterByModels($viaModels); $this->filterByModels($viaModels);
} else { } else {
$this->filterByModels($primaryModels); $this->filterByModels($primaryModels);
...@@ -106,12 +159,9 @@ class ActiveRelation extends ActiveQuery ...@@ -106,12 +159,9 @@ class ActiveRelation extends ActiveQuery
$buckets = $this->buildBuckets($models, $this->link); $buckets = $this->buildBuckets($models, $this->link);
} }
$link = array_values(isset($viaQuery) ? $viaQuery->link : $this->link);
foreach ($primaryModels as $i => $primaryModel) { foreach ($primaryModels as $i => $primaryModel) {
if (isset($viaQuery)) { $key = $this->getModelKey($primaryModel, $link);
$key = $this->getModelKey($primaryModel, array_values($viaQuery->link));
} else {
$key = $this->getModelKey($primaryModel, array_values($this->link));
}
if (isset($buckets[$key])) { if (isset($buckets[$key])) {
$primaryModels[$i][$name] = $buckets[$key]; $primaryModels[$i][$name] = $buckets[$key];
} else { } else {
...@@ -125,8 +175,9 @@ class ActiveRelation extends ActiveQuery ...@@ -125,8 +175,9 @@ class ActiveRelation extends ActiveQuery
protected function buildBuckets($models, $link, $viaModels = null, $viaLink = null) protected function buildBuckets($models, $link, $viaModels = null, $viaLink = null)
{ {
$buckets = array(); $buckets = array();
$linkKeys = array_keys($link);
foreach ($models as $i => $model) { foreach ($models as $i => $model) {
$key = $this->getModelKey($model, array_keys($link)); $key = $this->getModelKey($model, $linkKeys);
if ($this->index !== null) { if ($this->index !== null) {
$buckets[$key][$i] = $model; $buckets[$key][$i] = $model;
} else { } else {
...@@ -136,9 +187,11 @@ class ActiveRelation extends ActiveQuery ...@@ -136,9 +187,11 @@ class ActiveRelation extends ActiveQuery
if ($viaModels !== null) { if ($viaModels !== null) {
$viaBuckets = array(); $viaBuckets = array();
$viaLinkKeys = array_keys($viaLink);
$linkValues = array_values($link);
foreach ($viaModels as $viaModel) { foreach ($viaModels as $viaModel) {
$key1 = $this->getModelKey($viaModel, array_keys($viaLink)); $key1 = $this->getModelKey($viaModel, $viaLinkKeys);
$key2 = $this->getModelKey($viaModel, array_values($link)); $key2 = $this->getModelKey($viaModel, $linkValues);
if (isset($buckets[$key2])) { if (isset($buckets[$key2])) {
foreach ($buckets[$key2] as $i => $bucket) { foreach ($buckets[$key2] as $i => $bucket) {
if ($this->index !== null) { if ($this->index !== null) {
...@@ -194,7 +247,23 @@ class ActiveRelation extends ActiveQuery ...@@ -194,7 +247,23 @@ class ActiveRelation extends ActiveQuery
$values[] = $v; $values[] = $v;
} }
} }
$this->andWhere(array('in', $attributes, $values)); $this->andWhere(array('in', $attributes, array_unique($values, SORT_REGULAR)));
} }
/**
* @param ActiveRecord[] $primaryModels
* @return array
*/
protected function findPivotRows($primaryModels)
{
if (empty($primaryModels)) {
return array();
}
$this->filterByModels($primaryModels);
/** @var $primaryModel ActiveRecord */
$primaryModel = reset($primaryModels);
$db = $primaryModel->getDbConnection();
$sql = $db->getQueryBuilder()->build($this);
return $db->createCommand($sql, $this->params)->queryAll();
}
} }
...@@ -16,112 +16,112 @@ class ActiveRecordTest extends \yiiunit\MysqlTestCase ...@@ -16,112 +16,112 @@ class ActiveRecordTest extends \yiiunit\MysqlTestCase
ActiveRecord::$db = $this->getConnection(); ActiveRecord::$db = $this->getConnection();
} }
// public function testFind() public function testFind()
// { {
// // find one // find one
// $result = Customer::find(); $result = Customer::find();
// $this->assertTrue($result instanceof ActiveQuery); $this->assertTrue($result instanceof ActiveQuery);
// $customer = $result->one(); $customer = $result->one();
// $this->assertTrue($customer instanceof Customer); $this->assertTrue($customer instanceof Customer);
//
// // find all // find all
// $result = Customer::find(); $result = Customer::find();
// $customers = $result->all(); $customers = $result->all();
// $this->assertEquals(3, count($customers)); $this->assertEquals(3, count($customers));
// $this->assertTrue($customers[0] instanceof Customer); $this->assertTrue($customers[0] instanceof Customer);
// $this->assertTrue($customers[1] instanceof Customer); $this->assertTrue($customers[1] instanceof Customer);
// $this->assertTrue($customers[2] instanceof Customer); $this->assertTrue($customers[2] instanceof Customer);
//
// // find by a single primary key // find by a single primary key
// $customer = Customer::find(2); $customer = Customer::find(2);
// $this->assertTrue($customer instanceof Customer); $this->assertTrue($customer instanceof Customer);
// $this->assertEquals('user2', $customer->name); $this->assertEquals('user2', $customer->name);
//
// // find by attributes // find by attributes
// $customer = Customer::find()->where(array('name' => 'user2'))->one(); $customer = Customer::find()->where(array('name' => 'user2'))->one();
// $this->assertTrue($customer instanceof Customer); $this->assertTrue($customer instanceof Customer);
// $this->assertEquals(2, $customer->id); $this->assertEquals(2, $customer->id);
//
// // find by Query array // find by Query array
// $query = array( $query = array(
// 'where' => 'id=:id', 'where' => 'id=:id',
// 'params' => array(':id' => 2), 'params' => array(':id' => 2),
// ); );
// $customer = Customer::find($query)->one(); $customer = Customer::find($query)->one();
// $this->assertTrue($customer instanceof Customer); $this->assertTrue($customer instanceof Customer);
// $this->assertEquals('user2', $customer->name); $this->assertEquals('user2', $customer->name);
//
// // find count // find count
// $this->assertEquals(3, Customer::count()->value()); $this->assertEquals(3, Customer::count()->value());
// $this->assertEquals(2, Customer::count(array( $this->assertEquals(2, Customer::count(array(
// 'where' => 'id=1 OR id=2', 'where' => 'id=1 OR id=2',
// ))->value()); ))->value());
// $this->assertEquals(2, Customer::find()->select('COUNT(*)')->where('id=1 OR id=2')->value()); $this->assertEquals(2, Customer::find()->select('COUNT(*)')->where('id=1 OR id=2')->value());
// } }
//
// public function testFindBySql()
// {
// // find one
// $customer = Customer::findBySql('SELECT * FROM tbl_customer ORDER BY id DESC')->one();
// $this->assertTrue($customer instanceof Customer);
// $this->assertEquals('user3', $customer->name);
//
// // find all
// $customers = Customer::findBySql('SELECT * FROM tbl_customer')->all();
// $this->assertEquals(3, count($customers));
//
// // find with parameter binding
// $customer = Customer::findBySql('SELECT * FROM tbl_customer WHERE id=:id', array(':id' => 2))->one();
// $this->assertTrue($customer instanceof Customer);
// $this->assertEquals('user2', $customer->name);
// }
//
// public function testScope()
// {
// $customers = Customer::find(array(
// 'scopes' => array('active'),
// ))->all();
// $this->assertEquals(2, count($customers));
//
// $customers = Customer::find()->active()->all();
// $this->assertEquals(2, count($customers));
// }
//
// public function testFindLazy()
// {
// /** @var $customer Customer */
// $customer = Customer::find(2);
// $orders = $customer->orders;
// $this->assertEquals(2, count($orders));
//
// $orders = $customer->orders()->where('id=3')->all();
// $this->assertEquals(1, count($orders));
// $this->assertEquals(3, $orders[0]->id);
// }
//
// public function testFindEager()
// {
// $customers = Customer::find()->with('orders')->all();
// $this->assertEquals(3, count($customers));
// $this->assertEquals(1, count($customers[0]->orders));
// $this->assertEquals(2, count($customers[1]->orders));
// }
//
// public function testFindLazyVia()
// {
// /** @var $order Order */
// $order = Order::find(1);
// $this->assertEquals(1, $order->id);
// $this->assertEquals(2, count($order->items));
// $this->assertEquals(1, $order->items[0]->id);
// $this->assertEquals(2, $order->items[1]->id);
//
// $order = Order::find(1);
// $order->id = 100;
// $this->assertEquals(array(), $order->items);
// }
public function testFindEagerVia() public function testFindBySql()
{
// find one
$customer = Customer::findBySql('SELECT * FROM tbl_customer ORDER BY id DESC')->one();
$this->assertTrue($customer instanceof Customer);
$this->assertEquals('user3', $customer->name);
// find all
$customers = Customer::findBySql('SELECT * FROM tbl_customer')->all();
$this->assertEquals(3, count($customers));
// find with parameter binding
$customer = Customer::findBySql('SELECT * FROM tbl_customer WHERE id=:id', array(':id' => 2))->one();
$this->assertTrue($customer instanceof Customer);
$this->assertEquals('user2', $customer->name);
}
public function testScope()
{
$customers = Customer::find(array(
'scopes' => array('active'),
))->all();
$this->assertEquals(2, count($customers));
$customers = Customer::find()->active()->all();
$this->assertEquals(2, count($customers));
}
public function testFindLazy()
{
/** @var $customer Customer */
$customer = Customer::find(2);
$orders = $customer->orders;
$this->assertEquals(2, count($orders));
$orders = $customer->orders()->where('id=3')->all();
$this->assertEquals(1, count($orders));
$this->assertEquals(3, $orders[0]->id);
}
public function testFindEager()
{
$customers = Customer::find()->with('orders')->all();
$this->assertEquals(3, count($customers));
$this->assertEquals(1, count($customers[0]->orders));
$this->assertEquals(2, count($customers[1]->orders));
}
public function testFindLazyVia()
{
/** @var $order Order */
$order = Order::find(1);
$this->assertEquals(1, $order->id);
$this->assertEquals(2, count($order->items));
$this->assertEquals(1, $order->items[0]->id);
$this->assertEquals(2, $order->items[1]->id);
$order = Order::find(1);
$order->id = 100;
$this->assertEquals(array(), $order->items);
}
public function testFindEagerViaRelation()
{ {
$orders = Order::find()->with('items')->orderBy('id')->all(); $orders = Order::find()->with('items')->orderBy('id')->all();
$this->assertEquals(3, count($orders)); $this->assertEquals(3, count($orders));
...@@ -132,6 +132,41 @@ class ActiveRecordTest extends \yiiunit\MysqlTestCase ...@@ -132,6 +132,41 @@ class ActiveRecordTest extends \yiiunit\MysqlTestCase
$this->assertEquals(2, $order->items[1]->id); $this->assertEquals(2, $order->items[1]->id);
} }
public function testFindLazyViaTable()
{
/** @var $order Order */
$order = Order::find(1);
$this->assertEquals(1, $order->id);
$this->assertEquals(2, count($order->books));
$this->assertEquals(1, $order->items[0]->id);
$this->assertEquals(2, $order->items[1]->id);
$order = Order::find(2);
$this->assertEquals(2, $order->id);
$this->assertEquals(0, count($order->books));
}
public function testFindEagerViaTable()
{
$orders = Order::find()->with('books')->orderBy('id')->all();
$this->assertEquals(3, count($orders));
$order = $orders[0];
$this->assertEquals(1, $order->id);
$this->assertEquals(2, count($order->books));
$this->assertEquals(1, $order->books[0]->id);
$this->assertEquals(2, $order->books[1]->id);
$order = $orders[1];
$this->assertEquals(2, $order->id);
$this->assertEquals(0, count($order->books));
$order = $orders[2];
$this->assertEquals(3, $order->id);
$this->assertEquals(1, count($order->books));
$this->assertEquals(2, $order->books[0]->id);
}
// public function testInsert() // public function testInsert()
// { // {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment