Commit 211c3fb8 by Qiang Xue

AR WIP

parent 58490ae4
...@@ -66,27 +66,38 @@ class ActiveQuery extends BaseQuery ...@@ -66,27 +66,38 @@ class ActiveQuery extends BaseQuery
{ {
$command = $this->createCommand(); $command = $this->createCommand();
$rows = $command->queryAll(); $rows = $command->queryAll();
if ($rows === array()) {
return array();
}
$models = $this->createModels($rows); $models = $this->createModels($rows);
if (empty($this->with)) { if (!empty($this->with)) {
return $models; $this->fetchRelatedModels($models, $this->with);
} }
return $models;
} }
/** /**
* Executes query and returns a single row of result. * Executes query and returns a single row of result.
* @return ActiveRecord|array|boolean a single row of query result. Depending on the setting of [[asArray]], * @return ActiveRecord|array|null a single row of query result. Depending on the setting of [[asArray]],
* the query result may be either an array or an ActiveRecord object. False will be returned * the query result may be either an array or an ActiveRecord object. Null will be returned
* if the query results in nothing. * if the query results in nothing.
*/ */
public function one() public function one()
{ {
$command = $this->createCommand(); $command = $this->createCommand();
$row = $command->queryRow(); $row = $command->queryRow();
if ($row === false || $this->asArray) { if ($row === false) {
return false;
} elseif ($this->asArray) {
return $row; return $row;
} else { } else {
/** @var $class ActiveRecord */
$class = $this->modelClass; $class = $this->modelClass;
return $class::create($row); $model = $class::create($row);
if (!empty($this->with)) {
$this->fetchRelatedModels(array($model), $this->with);
}
return $model;
} }
} }
...@@ -107,7 +118,8 @@ class ActiveQuery extends BaseQuery ...@@ -107,7 +118,8 @@ class ActiveQuery extends BaseQuery
*/ */
public function exists() public function exists()
{ {
return $this->select(array(new Expression('1')))->value() !== false; $this->select = array(new Expression('1'));
return $this->value() !== false;
} }
/** /**
...@@ -118,6 +130,7 @@ class ActiveQuery extends BaseQuery ...@@ -118,6 +130,7 @@ class ActiveQuery extends BaseQuery
*/ */
public function createCommand($db = null) public function createCommand($db = null)
{ {
/** @var $modelClass ActiveRecord */
$modelClass = $this->modelClass; $modelClass = $this->modelClass;
if ($db === null) { if ($db === null) {
$db = $modelClass::getDbConnection(); $db = $modelClass::getDbConnection();
...@@ -139,10 +152,9 @@ class ActiveQuery extends BaseQuery ...@@ -139,10 +152,9 @@ class ActiveQuery extends BaseQuery
} }
/** @var $qb QueryBuilder */ /** @var $qb QueryBuilder */
$qb = $db->getQueryBuilder(); $qb = $db->getQueryBuilder();
return $db->createCommand($qb->build($this), $this->params); $this->sql = $qb->build($this);
} else {
return $db->createCommand($this->sql, $this->params);
} }
return $db->createCommand($this->sql, $this->params);
} }
public function asArray($value = true) public function asArray($value = true)
...@@ -173,55 +185,6 @@ class ActiveQuery extends BaseQuery ...@@ -173,55 +185,6 @@ class ActiveQuery extends BaseQuery
return $this; return $this;
} }
protected function find()
{
$modelClass = $this->modelClass;
/**
* @var \yii\db\dao\Connection $db
*/
$db = $modelClass::getDbConnection();
if ($this->sql === null) {
if ($this->from === null) {
$tableName = $modelClass::getTableSchema()->name;
$this->from = array($tableName);
}
foreach ($this->scopes as $name => $config) {
if (is_integer($name)) {
$modelClass::$config($this);
} else {
array_unshift($config, $this);
call_user_func_array(array($modelClass, $name), $config);
}
}
$this->sql = $db->getQueryBuilder()->build($this);
}
$command = $db->createCommand($this->sql, $this->params);
$rows = $command->queryAll();
$records = $this->createRecords($rows);
if ($records !== array()) {
foreach ($this->with as $name => $config) {
/** @var Relation $relation */
$relation = $model->$name();
foreach ($config as $p => $v) {
$relation->$p = $v;
}
if ($relation->asArray === null) {
// inherit asArray from parent query
$relation->asArray = $this->asArray;
}
$rs = $relation->findWith($records);
/*
foreach ($rs as $r) {
// find the matching parent record(s)
// insert into the parent records(s)
}
*/
}
}
return $records;
}
protected function createModels($rows) protected function createModels($rows)
{ {
$models = array(); $models = array();
...@@ -248,4 +211,26 @@ class ActiveQuery extends BaseQuery ...@@ -248,4 +211,26 @@ class ActiveQuery extends BaseQuery
} }
return $models; return $models;
} }
protected function fetchRelatedModels(&$models, $relations)
{
// todo: normalize $relations
$primaryModel = new $this->modelClass;
foreach ($relations as $name => $properties) {
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;
}
if ($relation->asArray === null) {
// inherit asArray from primary query
$relation->asArray = $this->asArray;
}
$relation->findWith($name, $models);
}
}
} }
...@@ -101,9 +101,9 @@ abstract class ActiveRecord extends Model ...@@ -101,9 +101,9 @@ abstract class ActiveRecord extends Model
* corresponding record. * corresponding record.
* - an array of name-value pairs: it will be used to configure the [[ActiveQuery]] object. * - an array of name-value pairs: it will be used to configure the [[ActiveQuery]] object.
* *
* @return ActiveQuery|ActiveRecord|null the [[ActiveQuery]] instance for query purpose, or * @return ActiveQuery|ActiveRecord|boolean the [[ActiveQuery]] instance for query purpose, or
* the ActiveRecord object when a scalar is passed to this method which is considered to be a * the ActiveRecord object when a scalar is passed to this method which is considered to be a
* primary key value (null will be returned if no record is found in this case.) * primary key value (false will be returned if no record is found in this case.)
*/ */
public static function find($q = null) public static function find($q = null)
{ {
......
...@@ -19,22 +19,18 @@ namespace yii\db\ar; ...@@ -19,22 +19,18 @@ namespace yii\db\ar;
* @author Qiang Xue <qiang.xue@gmail.com> * @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0 * @since 2.0
*/ */
class ActiveRelation extends BaseActiveQuery class ActiveRelation extends ActiveQuery
{ {
/** /**
* @var string the class name of the ActiveRecord instances that this relation * @var ActiveRecord the primary model that this relation is associated with.
* should create and populate query results into
*/
public $modelClass;
/**
* @var ActiveRecord the primary record that this relation is associated with.
* This is used only in lazy loading with dynamic query options. * This is used only in lazy loading with dynamic query options.
*/ */
public $primaryModel; public $primaryModel;
/** /**
* @var boolean whether this relation is a one-many relation * @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.
*/ */
public $hasMany; public $multiple;
/** /**
* @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
...@@ -47,78 +43,85 @@ class ActiveRelation extends BaseActiveQuery ...@@ -47,78 +43,85 @@ class ActiveRelation extends BaseActiveQuery
*/ */
public $via; public $via;
public function one() public function createCommand($db = null)
{
$models = $this->all();
return isset($models[0]) ? $models[0] : null;
}
public function all()
{ {
$models = array(); if ($this->primaryModel !== null) {
return $models; $this->filterByPrimaryModels(array($this->primaryModel));
}
return parent::createCommand($db);
} }
public function findWith($name, &$primaryRecords) public function findWith($name, &$primaryModels)
{ {
if (empty($this->link) || !is_array($this->link)) { if (empty($this->link) || !is_array($this->link)) {
throw new \yii\base\Exception('invalid link'); throw new \yii\base\Exception('invalid link');
} }
$this->addLinkCondition($primaryRecords); $this->filterByPrimaryModels($primaryModels);
$records = $this->find();
/** @var array $map mapping key(s) to index of $primaryRecords */ if (count($primaryModels) === 1 && !$this->multiple) {
$index = $this->buildRecordIndex($primaryRecords, array_values($this->link)); foreach ($primaryModels as $i => $primaryModel) {
$this->initRecordRelation($primaryRecords, $name); $primaryModels[$i][$name] = $this->one();
foreach ($records as $record) { }
$key = $this->getRecordKey($record, array_keys($this->link)); } else {
if (isset($index[$key])) { $models = $this->all();
$primaryRecords[$map[$key]][$name] = $record; // distribute models into buckets which are indexed by the link keys
$buckets = array();
foreach ($models as $i => $model) {
$key = $this->getModelKey($model, array_keys($this->link));
if ($this->index !== null) {
$buckets[$key][$i] = $model;
} else {
$buckets[$key][] = $model;
}
}
if (!$this->multiple) {
foreach ($buckets as $i => $bucket) {
$buckets[$i] = reset($bucket);
}
}
foreach ($primaryModels as $i => $primaryModel) {
$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;
}
} }
} }
} }
protected function getRecordKey($record, $attributes) protected function getModelKey($model, $attributes)
{ {
if (isset($attributes[1])) { if (count($attributes) > 1) {
$key = array(); $key = array();
foreach ($attributes as $attribute) { foreach ($attributes as $attribute) {
$key[] = is_array($record) ? $record[$attribute] : $record->$attribute; $key[] = $model[$attribute];
} }
return serialize($key); return serialize($key);
} else { } else {
$attribute = $attributes[0]; $attribute = reset($attributes);
return is_array($record) ? $record[$attribute] : $record->$attribute; return $model[$attribute];
}
}
protected function buildRecordIndex($records, $attributes)
{
$map = array();
foreach ($records as $i => $record) {
$map[$this->getRecordKey($record, $attributes)] = $i;
} }
return $map;
} }
protected function addLinkCondition($primaryRecords) protected function filterByPrimaryModels($primaryModels)
{ {
$attributes = array_keys($this->link); $attributes = array_keys($this->link);
$values = array(); $values = array();
if (isset($links[1])) { if (isset($links[1])) {
// composite keys // composite keys
foreach ($primaryRecords as $record) { foreach ($primaryModels as $model) {
$v = array(); $v = array();
foreach ($this->link as $attribute => $link) { foreach ($this->link as $attribute => $link) {
$v[$attribute] = is_array($record) ? $record[$link] : $record->$link; $v[$attribute] = is_array($model) ? $model[$link] : $model->$link;
} }
$values[] = $v; $values[] = $v;
} }
} else { } else {
// single key // single key
$attribute = $this->link[$links[0]]; $attribute = $this->link[$links[0]];
foreach ($primaryRecords as $record) { foreach ($primaryModels as $model) {
$values[] = is_array($record) ? $record[$attribute] : $record->$attribute; $values[] = is_array($model) ? $model[$attribute] : $model->$attribute;
} }
} }
$this->andWhere(array('in', $attributes, $values)); $this->andWhere(array('in', $attributes, $values));
......
...@@ -544,6 +544,7 @@ class BaseQuery extends \yii\base\Component ...@@ -544,6 +544,7 @@ class BaseQuery extends \yii\base\Component
'where', 'limit', 'offset', 'orderBy', 'groupBy', 'where', 'limit', 'offset', 'orderBy', 'groupBy',
'join', 'having', 'union', 'params', 'join', 'having', 'union', 'params',
); );
// todo: incorrect, do we need it? should we provide a configure() method instead?
foreach ($properties as $name => $value) { foreach ($properties as $name => $value) {
if ($value !== null) { if ($value !== null) {
$this->$name = $value; $this->$name = $value;
......
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