From 0cd6b346b605980aba4114ef532c1415ab927fdf Mon Sep 17 00:00:00 2001 From: Qiang Xue Date: Sat, 5 Jan 2013 08:40:11 -0500 Subject: [PATCH] refactoring AR --- framework/db/ar/ActiveQuery.php | 83 +++++++++---- framework/db/ar/ActiveRecord.php | 4 +- framework/db/ar/ActiveRelation.php | 10 +- tests/unit/framework/db/ar/ActiveRecordTest.php | 157 +++++------------------- 4 files changed, 96 insertions(+), 158 deletions(-) diff --git a/framework/db/ar/ActiveQuery.php b/framework/db/ar/ActiveQuery.php index 808ab95..11ae08d 100644 --- a/framework/db/ar/ActiveQuery.php +++ b/framework/db/ar/ActiveQuery.php @@ -32,7 +32,7 @@ class ActiveQuery extends BaseQuery * @var string the name of the column by which the query result should be indexed. * This is only used when the query result is returned as an array when calling [[all()]]. */ - public $index; + public $indexBy; /** * @var boolean whether to return each record as an array. If false (default), an object * of [[modelClass]] will be created to represent each record. @@ -170,9 +170,9 @@ class ActiveQuery extends BaseQuery return $this; } - public function index($column) + public function indexBy($column) { - $this->index = $column; + $this->indexBy = $column; return $this; } @@ -186,48 +186,34 @@ class ActiveQuery extends BaseQuery { $models = array(); if ($this->asArray) { - if ($this->index === null) { + if ($this->indexBy === null) { return $rows; } foreach ($rows as $row) { - $models[$row[$this->index]] = $row; + $models[$row[$this->indexBy]] = $row; } } else { /** @var $class ActiveRecord */ $class = $this->modelClass; - if ($this->index === null) { + if ($this->indexBy === null) { foreach ($rows as $row) { $models[] = $class::create($row); } } else { foreach ($rows as $row) { $model = $class::create($row); - $models[$model->{$this->index}] = $model; + $models[$model->{$this->indexBy}] = $model; } } } return $models; } - protected function fetchRelatedModels(&$models, $relations) + protected function fetchRelatedModels(&$models, $with) { - // todo: normalize $relations $primaryModel = new $this->modelClass; - foreach ($relations as $name => $properties) { - if (is_integer($name)) { - $name = $properties; - $properties = array(); - } - - if (!method_exists($primaryModel, $name)) { - throw new Exception("Unknown relation: $name"); - } - /** @var $relation ActiveRelation */ - $relation = $primaryModel->$name(); - $relation->primaryModel = null; - foreach ($properties as $p => $v) { - $relation->$p = $v; - } + $relations = $this->normalizeRelations($primaryModel, $with); + foreach ($relations as $name => $relation) { if ($relation->asArray === null) { // inherit asArray from primary query $relation->asArray = $this->asArray; @@ -235,4 +221,53 @@ class ActiveQuery extends BaseQuery $relation->findWith($name, $models); } } + + /** + * @param ActiveRecord $model + * @param array $with + * @return ActiveRelation[] + * @throws \yii\db\Exception + */ + protected function normalizeRelations($model, $with) + { + $relations = array(); + foreach ($with as $name => $options) { + if (is_integer($name)) { + $name = $options; + $options = array(); + } + if (($pos = strpos($name, '.')) !== false) { + // with sub-relations + $childName = substr($name, $pos + 1); + $name = substr($name, 0, $pos); + } else { + $childName = null; + } + + if (!isset($relations[$name])) { + if (!method_exists($model, $name)) { + throw new Exception("Unknown relation: $name"); + } + /** @var $relation ActiveRelation */ + $relation = $model->$name(); + $relation->primaryModel = null; + $relations[$name] = $relation; + } else { + $relation = $relations[$name]; + } + + if (isset($childName)) { + if (isset($relation->with[$childName])) { + $relation->with[$childName] = array_merge($relation->with, $options); + } else { + $relation->with[$childName] = $options; + } + } else { + foreach ($options as $p => $v) { + $relation->$p = $v; + } + } + } + return $relations; + } } diff --git a/framework/db/ar/ActiveRecord.php b/framework/db/ar/ActiveRecord.php index c8dfc38..9685909 100644 --- a/framework/db/ar/ActiveRecord.php +++ b/framework/db/ar/ActiveRecord.php @@ -430,8 +430,8 @@ abstract class ActiveRecord extends Model public function addRelatedRecord($relation, $record) { if ($relation->hasMany) { - if ($relation->index !== null) { - $this->_related[$relation->name][$record->{$relation->index}] = $record; + if ($relation->indexBy !== null) { + $this->_related[$relation->name][$record->{$relation->indexBy}] = $record; } else { $this->_related[$relation->name][] = $record; } diff --git a/framework/db/ar/ActiveRelation.php b/framework/db/ar/ActiveRelation.php index c1f6a10..64f15a2 100644 --- a/framework/db/ar/ActiveRelation.php +++ b/framework/db/ar/ActiveRelation.php @@ -34,18 +34,18 @@ 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. */ - protected $primaryModel; + public $primaryModel; /** * @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 as they will be done automatically by Yii. */ - protected $link; + public $link; /** * @var array|ActiveRelation */ - protected $via; + public $via; /** * @param string $relationName @@ -178,7 +178,7 @@ class ActiveRelation extends ActiveQuery $linkKeys = array_keys($link); foreach ($models as $i => $model) { $key = $this->getModelKey($model, $linkKeys); - if ($this->index !== null) { + if ($this->indexBy !== null) { $buckets[$key][$i] = $model; } else { $buckets[$key][] = $model; @@ -194,7 +194,7 @@ class ActiveRelation extends ActiveQuery $key2 = $this->getModelKey($viaModel, $linkValues); if (isset($buckets[$key2])) { foreach ($buckets[$key2] as $i => $bucket) { - if ($this->index !== null) { + if ($this->indexBy !== null) { $viaBuckets[$key1][$i] = $bucket; } else { $viaBuckets[$key1][] = $bucket; diff --git a/tests/unit/framework/db/ar/ActiveRecordTest.php b/tests/unit/framework/db/ar/ActiveRecordTest.php index ee28e18..522e3d2 100644 --- a/tests/unit/framework/db/ar/ActiveRecordTest.php +++ b/tests/unit/framework/db/ar/ActiveRecordTest.php @@ -25,8 +25,7 @@ class ActiveRecordTest extends \yiiunit\MysqlTestCase $this->assertTrue($customer instanceof Customer); // find all - $result = Customer::find(); - $customers = $result->all(); + $customers = Customer::find()->all(); $this->assertEquals(3, count($customers)); $this->assertTrue($customers[0] instanceof Customer); $this->assertTrue($customers[1] instanceof Customer); @@ -57,6 +56,23 @@ class ActiveRecordTest extends \yiiunit\MysqlTestCase 'where' => 'id=1 OR id=2', ))->value()); $this->assertEquals(2, Customer::find()->select('COUNT(*)')->where('id=1 OR id=2')->value()); + + // asArray + $customer = Customer::find()->where('id=2')->asArray()->one(); + $this->assertEquals(array( + 'id' => '2', + 'email' => 'user2@example.com', + 'name' => 'user2', + 'address' => 'address2', + 'status' => '1', + ), $customer); + + // indexBy + $customers = Customer::find()->indexBy('name')->orderBy('id')->all(); + $this->assertEquals(3, count($customers)); + $this->assertTrue($customers['user1'] instanceof Customer); + $this->assertTrue($customers['user2'] instanceof Customer); + $this->assertTrue($customers['user3'] instanceof Customer); } public function testFindBySql() @@ -167,6 +183,17 @@ class ActiveRecordTest extends \yiiunit\MysqlTestCase $this->assertEquals(2, $order->books[0]->id); } + public function testFindNestedRelation() + { + $customers = Customer::find()->with('orders', 'orders.items')->all(); + $this->assertEquals(3, count($customers)); + $this->assertEquals(1, count($customers[0]->orders)); + $this->assertEquals(2, count($customers[1]->orders)); + $this->assertEquals(0, count($customers[2]->orders)); + $this->assertEquals(2, count($customers[0]->orders[0]->items)); + $this->assertEquals(3, count($customers[1]->orders[0]->items)); + $this->assertEquals(1, count($customers[1]->orders[1]->items)); + } // public function testInsert() // { @@ -373,129 +400,5 @@ class ActiveRecordTest extends \yiiunit\MysqlTestCase // $customers = Customer::find()->orderBy('id')->index('name')->all(); // $this->assertEquals(2, $customers['user2']['id']); // } -// -// public function testEagerLoading() -// { -// // has many -// $customers = Customer::find()->with('orders')->orderBy('@.id')->all(); -// $this->assertEquals(3, count($customers)); -// $this->assertEquals(1, count($customers[0]->orders)); -// $this->assertEquals(2, count($customers[1]->orders)); -// $this->assertEquals(0, count($customers[2]->orders)); -// -// // nested -// $customers = Customer::find()->with('orders.customer')->orderBy('@.id')->all(); -// $this->assertEquals(3, count($customers)); -// $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); -// -// // has many via relation -// $orders = Order::find()->with('items')->orderBy('@.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); -// -// // has many via join table -// $orders = Order::find()->with('books')->orderBy('@.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); -// -// // has many and base limited -// $orders = Order::find()->with('items')->orderBy('@.id')->limit(2)->all(); -// $this->assertEquals(2, count($orders)); -// $this->assertEquals(1, $orders[0]->items[0]->id); -// -// /// customize "with" query -// $orders = Order::find()->with(array('items' => function($q) { -// $q->orderBy('@.id DESC'); -// }))->orderBy('@.id')->limit(2)->all(); -// $this->assertEquals(2, count($orders)); -// $this->assertEquals(2, $orders[0]->items[0]->id); -// -// // findBySql with -// $orders = Order::findBySql('SELECT * FROM tbl_order WHERE customer_id=2')->with('items')->all(); -// $this->assertEquals(2, count($orders)); -// -// // index and array -// $customers = Customer::find()->with('orders.customer')->orderBy('@.id')->index('id')->asArray()->all(); -// $this->assertEquals(3, count($customers)); -// $this->assertTrue(isset($customers[1], $customers[2], $customers[3])); -// $this->assertTrue(is_array($customers[1])); -// $this->assertEquals(1, count($customers[1]['orders'])); -// $this->assertEquals(2, count($customers[2]['orders'])); -// $this->assertEquals(0, count($customers[3]['orders'])); -// $this->assertTrue(is_array($customers[1]['orders'][0]['customer'])); -// -// // count with -// $this->assertEquals(3, Order::count()); -// $value = Order::count(array( -// 'select' => array('COUNT(DISTINCT @.id, @.customer_id)'), -// 'with' => 'books', -// )); -// $this->assertEquals(2, $value); -// -// } -// -// public function testLazyLoading() -// { -// // has one -// $order = Order::find(3); -// $this->assertTrue($order->customer instanceof Customer); -// $this->assertEquals(2, $order->customer->id); -// -// // has many -// $customer = Customer::find(2); -// $orders = $customer->orders; -// $this->assertEquals(2, count($orders)); -// $this->assertEquals(2, $orders[0]->id); -// $this->assertEquals(3, $orders[1]->id); -// -// // has many via join table -// $orders = Order::find()->orderBy('@.id')->all(); -// $this->assertEquals(3, count($orders)); -// $this->assertEquals(2, count($orders[0]->books)); -// $this->assertEquals(1, $orders[0]->books[0]->id); -// $this->assertEquals(2, $orders[0]->books[1]->id); -// $this->assertEquals(array(), $orders[1]->books); -// $this->assertEquals(1, count($orders[2]->books)); -// $this->assertEquals(2, $orders[2]->books[0]->id); -// -// // customized relation query -// $customer = Customer::find(2); -// $orders = $customer->orders(array( -// 'where' => '@.id = 3', -// )); -// $this->assertEquals(1, count($orders)); -// $this->assertEquals(3, $orders[0]->id); -// -// // original results are kept after customized query -// $orders = $customer->orders; -// $this->assertEquals(2, count($orders)); -// $this->assertEquals(2, $orders[0]->id); -// $this->assertEquals(3, $orders[1]->id); -// -// // as array -// $orders = $customer->orders(array( -// 'asArray' => true, -// )); -// $this->assertEquals(2, count($orders)); -// $this->assertTrue(is_array($orders[0])); -// $this->assertEquals(2, $orders[0]['id']); -// $this->assertEquals(3, $orders[1]['id']); -// -// // using anonymous function to customize query condition -// $orders = $customer->orders(function($q) { -// $q->orderBy('@.id DESC')->asArray(); -// }); -// $this->assertEquals(2, count($orders)); -// $this->assertTrue(is_array($orders[0])); -// $this->assertEquals(3, $orders[0]['id']); -// $this->assertEquals(2, $orders[1]['id']); -// } + } \ No newline at end of file