Commit 211c3fb8 by Qiang Xue

AR WIP

parent 58490ae4
......@@ -66,27 +66,38 @@ class ActiveQuery extends BaseQuery
{
$command = $this->createCommand();
$rows = $command->queryAll();
if ($rows === array()) {
return array();
}
$models = $this->createModels($rows);
if (empty($this->with)) {
return $models;
if (!empty($this->with)) {
$this->fetchRelatedModels($models, $this->with);
}
return $models;
}
/**
* 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]],
* the query result may be either an array or an ActiveRecord object. False will be returned
* @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. Null will be returned
* if the query results in nothing.
*/
public function one()
{
$command = $this->createCommand();
$row = $command->queryRow();
if ($row === false || $this->asArray) {
if ($row === false) {
return false;
} elseif ($this->asArray) {
return $row;
} else {
/** @var $class ActiveRecord */
$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
*/
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
*/
public function createCommand($db = null)
{
/** @var $modelClass ActiveRecord */
$modelClass = $this->modelClass;
if ($db === null) {
$db = $modelClass::getDbConnection();
......@@ -139,10 +152,9 @@ class ActiveQuery extends BaseQuery
}
/** @var $qb QueryBuilder */
$qb = $db->getQueryBuilder();
return $db->createCommand($qb->build($this), $this->params);
} else {
return $db->createCommand($this->sql, $this->params);
$this->sql = $qb->build($this);
}
return $db->createCommand($this->sql, $this->params);
}
public function asArray($value = true)
......@@ -173,55 +185,6 @@ class ActiveQuery extends BaseQuery
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)
{
$models = array();
......@@ -248,4 +211,26 @@ class ActiveQuery extends BaseQuery
}
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
* corresponding record.
* - 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
* 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)
{
......
......@@ -19,22 +19,18 @@ namespace yii\db\ar;
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ActiveRelation extends BaseActiveQuery
class ActiveRelation extends ActiveQuery
{
/**
* @var string the class name of the ActiveRecord instances that this relation
* should create and populate query results into
*/
public $modelClass;
/**
* @var ActiveRecord the primary record that this relation is associated with.
* @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 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.
* The array keys must be columns of the table for this relation, and the array values
......@@ -47,78 +43,85 @@ class ActiveRelation extends BaseActiveQuery
*/
public $via;
public function one()
public function createCommand($db = null)
{
$models = $this->all();
return isset($models[0]) ? $models[0] : null;
if ($this->primaryModel !== null) {
$this->filterByPrimaryModels(array($this->primaryModel));
}
public function all()
{
$models = array();
return $models;
return parent::createCommand($db);
}
public function findWith($name, &$primaryRecords)
public function findWith($name, &$primaryModels)
{
if (empty($this->link) || !is_array($this->link)) {
throw new \yii\base\Exception('invalid link');
}
$this->addLinkCondition($primaryRecords);
$records = $this->find();
$this->filterByPrimaryModels($primaryModels);
/** @var array $map mapping key(s) to index of $primaryRecords */
$index = $this->buildRecordIndex($primaryRecords, array_values($this->link));
$this->initRecordRelation($primaryRecords, $name);
foreach ($records as $record) {
$key = $this->getRecordKey($record, array_keys($this->link));
if (isset($index[$key])) {
$primaryRecords[$map[$key]][$name] = $record;
if (count($primaryModels) === 1 && !$this->multiple) {
foreach ($primaryModels as $i => $primaryModel) {
$primaryModels[$i][$name] = $this->one();
}
} else {
$models = $this->all();
// 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();
foreach ($attributes as $attribute) {
$key[] = is_array($record) ? $record[$attribute] : $record->$attribute;
$key[] = $model[$attribute];
}
return serialize($key);
} else {
$attribute = $attributes[0];
return is_array($record) ? $record[$attribute] : $record->$attribute;
}
}
protected function buildRecordIndex($records, $attributes)
{
$map = array();
foreach ($records as $i => $record) {
$map[$this->getRecordKey($record, $attributes)] = $i;
$attribute = reset($attributes);
return $model[$attribute];
}
return $map;
}
protected function addLinkCondition($primaryRecords)
protected function filterByPrimaryModels($primaryModels)
{
$attributes = array_keys($this->link);
$values = array();
if (isset($links[1])) {
// composite keys
foreach ($primaryRecords as $record) {
foreach ($primaryModels as $model) {
$v = array();
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;
}
} else {
// single key
$attribute = $this->link[$links[0]];
foreach ($primaryRecords as $record) {
$values[] = is_array($record) ? $record[$attribute] : $record->$attribute;
foreach ($primaryModels as $model) {
$values[] = is_array($model) ? $model[$attribute] : $model->$attribute;
}
}
$this->andWhere(array('in', $attributes, $values));
......
......@@ -544,6 +544,7 @@ class BaseQuery extends \yii\base\Component
'where', 'limit', 'offset', 'orderBy', 'groupBy',
'join', 'having', 'union', 'params',
);
// todo: incorrect, do we need it? should we provide a configure() method instead?
foreach ($properties as $name => $value) {
if ($value !== null) {
$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