Commit 5ee50a84 by Carsten Brandt

Merge pull request #2497 from yiisoft/merge-query-and-relation2

WIP merge ActiveRelation into ActiveQuery
parents 6ebcd9ac 505762d4
......@@ -36,7 +36,7 @@ before_script:
script:
# - vendor/bin/phpunit --coverage-clover tests/unit/runtime/coveralls/clover.xml --verbose --exclude-group mssql,oci,wincache,xcache,zenddata,vendor
- vendor/bin/phpunit --verbose --exclude-group mssql,oci,wincache,xcache,zenddata,vendor
- vendor/bin/phpunit --verbose --exclude-group mssql,oci,wincache,xcache,zenddata
- cd apps/basic && php vendor/bin/codecept run
#after_script:
......
......@@ -80,7 +80,9 @@
"require-dev": {
"phpunit/phpunit": "3.7.*",
"twig/twig": "*",
"smarty/smarty": "*"
"smarty/smarty": "*",
"imagine/imagine": "v0.5.0",
"swiftmailer/swiftmailer": "*"
},
"suggest": {
"phpdocumentor/reflection": "required by yii2-apidoc extension",
......
......@@ -21,7 +21,7 @@ $customer->save(); // a new row is inserted into tbl_customer
Declaring ActiveRecord Classes
------------------------------
To declare an ActiveRecord class you need to extend [[\yii\db\ActiveRecord]] and
To declare an ActiveRecord class you need to extend [[yii\db\ActiveRecord]] and
implement the `tableName` method:
```php
......@@ -197,7 +197,7 @@ Customer::updateAllCounters(['age' => 1]);
Data Input and Validation
-------------------------
ActiveRecord inherits data validation and data input features from [[\yii\base\Model]]. Data validation is called
ActiveRecord inherits data validation and data input features from [[yii\base\Model]]. Data validation is called
automatically when `save()` is performed. If data validation fails, the saving operation will be cancelled.
For more details refer to the [Model](model.md) section of this guide.
......@@ -205,12 +205,15 @@ For more details refer to the [Model](model.md) section of this guide.
Querying Relational Data
------------------------
You can use ActiveRecord to also query a table's relational data (i.e., selection of data from Table A can also pull in related data from Table B). Thanks to ActiveRecord, the relational data returned can be accessed like a property of the ActiveRecord object associated with the primary table.
You can use ActiveRecord to also query a table's relational data (i.e., selection of data from Table A can also pull
in related data from Table B). Thanks to ActiveRecord, the relational data returned can be accessed like a property
of the ActiveRecord object associated with the primary table.
For example, with an appropriate relation declaration, by accessing `$customer->orders` you may obtain
an array of `Order` objects which represent the orders placed by the specified customer.
To declare a relation, define a getter method which returns an [[yii\db\ActiveRelation]] object. For example,
To declare a relation, define a getter method which returns an [[yii\db\ActiveQuery]] object that has relation
information about the relation context and thus will only query for related records. For example,
```php
class Customer extends \yii\db\ActiveRecord
......@@ -235,7 +238,7 @@ class Order extends \yii\db\ActiveRecord
The methods [[yii\db\ActiveRecord::hasMany()]] and [[yii\db\ActiveRecord::hasOne()]] used in the above
are used to model the many-one relationship and one-one relationship in a relational database.
For example, a customer has many orders, and an order has one customer.
Both methods take two parameters and return an [[yii\db\ActiveRelation]] object:
Both methods take two parameters and return an [[yii\db\ActiveQuery]] object:
- `$class`: the name of the class of the related model(s). This should be a fully qualified class name.
- `$link`: the association between columns from the two tables. This should be given as an array.
......@@ -259,8 +262,8 @@ SELECT * FROM tbl_customer WHERE id=1;
SELECT * FROM tbl_order WHERE customer_id=1;
```
> Tip: If you access the expression `$customer->orders` again, will it perform the second SQL query again?
Nope. The SQL query is only performed the first time when this expression is accessed. Any further
> Tip: If you access the expression `$customer->orders` again, it will not perform the second SQL query again.
The SQL query is only performed the first time when this expression is accessed. Any further
accesses will only return the previously fetched results that are cached internally. If you want to re-query
the relational data, simply unset the existing one first: `unset($customer->orders);`.
......@@ -280,8 +283,8 @@ class Customer extends \yii\db\ActiveRecord
}
```
Remember that `hasMany()` returns an [[yii\db\ActiveRelation]] object which extends from [[yii\db\ActiveQuery]]
and thus supports the same set of querying methods as [[yii\db\ActiveQuery]].
Remember that `hasMany()` returns an [[yii\db\ActiveQuery]] object which allows you to customize the query by
calling the methods of [[yii\db\ActiveQuery]].
With the above declaration, if you access `$customer->bigOrders`, it will only return the orders
whose subtotal is greater than 100. To specify a different threshold value, use the following code:
......@@ -290,20 +293,19 @@ whose subtotal is greater than 100. To specify a different threshold value, use
$orders = $customer->getBigOrders(200)->all();
```
> Note: A relation method returns an instance of [[yii\db\ActiveRelation]]. If you access the relation like
an attribute, the return value will be the query result of the relation, which could be an instance of `ActiveRecord`,
> Note: A relation method returns an instance of [[yii\db\ActiveQuery]]. If you access the relation like
an attribute (i.e. a class property), the return value will be the query result of the relation, which could be an instance of [[yii\db\ActiveRecord]],
an array of that, or null, depending the multiplicity of the relation. For example, `$customer->getOrders()` returns
an `ActiveRelation` instance, while `$customer->orders` returns an array of `Order` objects (or an empty array if
an `ActiveQuery` instance, while `$customer->orders` returns an array of `Order` objects (or an empty array if
the query results in nothing).
Relations with Pivot Table
--------------------------
Sometimes, two tables are related together via an intermediary table called
[pivot table](http://en.wikipedia.org/wiki/Pivot_table). To declare such relations, we can customize
the [[yii\db\ActiveRelation]] object by calling its [[yii\db\ActiveRelation::via()]] or [[yii\db\ActiveRelation::viaTable()]]
method.
Sometimes, two tables are related together via an intermediary table called [pivot table][]. To declare such relations,
we can customize the [[yii\db\ActiveQuery]] object by calling its [[yii\db\ActiveQuery::via()|via()]] or
[[yii\db\ActiveQuery::viaTable()|viaTable()]] method.
For example, if table `tbl_order` and table `tbl_item` are related via pivot table `tbl_order_item`,
we can declare the `items` relation in the `Order` class like the following:
......@@ -319,8 +321,8 @@ class Order extends \yii\db\ActiveRecord
}
```
[[yii\db\ActiveRelation::via()]] method is similar to [[yii\db\ActiveRelation::viaTable()]] except that
the first parameter of [[yii\db\ActiveRelation::via()]] takes a relation name declared in the ActiveRecord class
The [[yii\db\ActiveQuery::via()|via()]] method is similar to [[yii\db\ActiveQuery::viaTable()|viaTable()]] except that
the first parameter of [[yii\db\ActiveQuery::via()|via()]] takes a relation name declared in the ActiveRecord class
instead of the pivot table name. For example, the above `items` relation can be equivalently declared as follows:
```php
......@@ -339,6 +341,8 @@ class Order extends \yii\db\ActiveRecord
}
```
[pivot table]: http://en.wikipedia.org/wiki/Pivot_table "Pivot table on Wikipedia"
Lazy and Eager Loading
----------------------
......@@ -457,7 +461,7 @@ if ($customer->orders[0]->customer === $customer) {
```
To avoid the redundant execution of the last SQL statement, we could declare the inverse relations for the `customer`
and the `orders` relations by calling the `inverseOf()` method, like the following:
and the `orders` relations by calling the [[yii\db\ActiveQuery::inverseOf()|inverseOf()]] method, like the following:
```php
class Customer extends ActiveRecord
......@@ -500,7 +504,8 @@ if ($customers[0]->orders[0]->customer === $customers[0]) {
```
> Note: Inverse relation cannot be defined with a relation that involves pivoting tables.
> That is, if your relation is defined with `via()` or `viaTable()`, you cannot call `inverseOf()` further.
> That is, if your relation is defined with [[yii\db\ActiveQuery::via()|via()]] or [[yii\db\ActiveQuery::viaTable()|viaTable()]],
> you cannot call [[yii\db\ActiveQuery::inverseOf()]] further.
Joining with Relations
......@@ -571,7 +576,7 @@ $orders = Order::find()->joinWith('books', false, 'INNER JOIN')->all();
```
Sometimes when joining two tables, you may need to specify some extra condition in the ON part of the JOIN query.
This can be done by calling the [[\yii\db\ActiveRelation::onCondition()]] method like the following:
This can be done by calling the [[yii\db\ActiveQuery::onCondition()]] method like the following:
```php
class User extends ActiveRecord
......@@ -583,7 +588,8 @@ class User extends ActiveRecord
}
```
In the above, the `hasMany()` method returns an `ActiveRelation` instance, upon which `onCondition()` is called
In the above, the [[yii\db\ActiveRecord::hasMany()|hasMany()]] method returns an [[yii\db\ActiveQuery]] instance,
upon which [[yii\db\ActiveQuery::onCondition()|onCondition()]] is called
to specify that only items whose `category_id` is 1 should be returned.
When you perform query using [[yii\db\ActiveQuery::joinWith()|joinWith()]], the on-condition will be put in the ON part
......@@ -665,8 +671,10 @@ Finally when calling [[yii\db\ActiveRecord::delete()|delete()]] to delete an Act
Scopes
------
When [[yii\db\ActiveRecord::find()|find()]] or [[yii\db\ActiveRecord::findBySql()|findBySql()]], it returns an [[yii\db\ActiveRecord::yii\db\ActiveQuery|yii\db\ActiveQuery]]
instance. You may call additional query methods, such as `where()`, `orderBy()`, to further specify the query conditions, etc.
When you call [[yii\db\ActiveRecord::find()|find()]] or [[yii\db\ActiveRecord::findBySql()|findBySql()]], it returns an
[[yii\db\ActiveQuery|ActiveQuery]] instance.
You may call additional query methods, such as [[yii\db\ActiveQuery::where()|where()]], [[yii\db\ActiveQuery::orderBy()|orderBy()]],
to further specify the query conditions.
It is possible that you may want to call the same set of query methods in different places. If this is the case,
you should consider defining the so-called *scopes*. A scope is essentially a method defined in a custom query class that
......@@ -933,4 +941,4 @@ See also
--------
- [Model](model.md)
- [[\yii\db\ActiveRecord]]
- [[yii\db\ActiveRecord]]
......@@ -9,11 +9,18 @@ namespace yii\elasticsearch;
use yii\db\ActiveQueryInterface;
use yii\db\ActiveQueryTrait;
use yii\db\ActiveRelationTrait;
/**
* ActiveQuery represents a [[Query]] associated with an [[ActiveRecord]] class.
*
* An ActiveQuery can be a normal query or be used in a relational context.
*
* ActiveQuery instances are usually created by [[ActiveRecord::find()]].
* Relational queries are created by [[ActiveRecord::hasOne()]] and [[ActiveRecord::hasMany()]].
*
* Normal Query
* ------------
*
* ActiveQuery mainly provides the following methods to retrieve the query results:
*
......@@ -35,12 +42,32 @@ use yii\db\ActiveQueryTrait;
*
* These options can be configured using methods of the same name. For example:
*
* ~~~
* ```php
* $customers = Customer::find()->with('orders')->asArray()->all();
* ~~~
* ```
* > NOTE: elasticsearch limits the number of records returned to 10 records by default.
* > If you expect to get more records you should specify limit explicitly.
*
* Relational query
* ----------------
*
* In relational context ActiveQuery represents a relation between two Active Record classes.
*
* Relational ActiveQuery instances are usually created by calling [[ActiveRecord::hasOne()]] and
* [[ActiveRecord::hasMany()]]. An Active Record class declares a relation by defining
* a getter method which calls one of the above methods and returns the created ActiveQuery object.
*
* A relation is specified by [[link]] which represents the association between columns
* of different tables; and the multiplicity of the relation is indicated by [[multiple]].
*
* NOTE: elasticsearch limits the number of records returned to 10 records by default.
* If you expect to get more records you should specify limit explicitly.
* If a relation involves a pivot table, it may be specified by [[via()]].
* This methods may only be called in a relational context. Same is true for [[inverseOf()]], which
* marks a relation as inverse of another relation.
*
* > NOTE: elasticsearch limits the number of records returned by any query to 10 records by default.
* > If you expect to get more records you should specify limit explicitly in relation definition.
* > This is also important for relations that use [[via()]] so that if via records are limited to 10
* > the relations records can also not be more than 10.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
......@@ -48,6 +75,7 @@ use yii\db\ActiveQueryTrait;
class ActiveQuery extends Query implements ActiveQueryInterface
{
use ActiveQueryTrait;
use ActiveRelationTrait;
/**
* Creates a DB command that can be used to execute this query.
......@@ -57,6 +85,26 @@ class ActiveQuery extends Query implements ActiveQueryInterface
*/
public function createCommand($db = null)
{
if ($this->primaryModel !== null) {
// lazy loading
if (is_array($this->via)) {
// via relation
/** @var ActiveQuery $viaQuery */
list($viaName, $viaQuery) = $this->via;
if ($viaQuery->multiple) {
$viaModels = $viaQuery->all();
$this->primaryModel->populateRelation($viaName, $viaModels);
} else {
$model = $viaQuery->one();
$this->primaryModel->populateRelation($viaName, $model);
$viaModels = $model === null ? [] : [$model];
}
$this->filterByModels($viaModels);
} else {
$this->filterByModels([$this->primaryModel]);
}
}
/** @var ActiveRecord $modelClass */
$modelClass = $this->modelClass;
if ($db === null) {
......
......@@ -138,19 +138,32 @@ class ActiveRecord extends BaseActiveRecord
// TODO add percolate functionality http://www.elasticsearch.org/guide/en/elasticsearch/reference/current/search-percolate.html
/**
* @inheritdoc
*/
public static function createQuery()
{
return new ActiveQuery(['modelClass' => get_called_class()]);
}
/**
* @inheritdoc
* Creates an [[ActiveQuery]] instance.
*
* This method is called by [[find()]], [[findBySql()]] to start a SELECT query but also
* by [[hasOne()]] and [[hasMany()]] to create a relational query.
* You may override this method to return a customized query (e.g. `CustomerQuery` specified
* written for querying `Customer` purpose.)
*
* You may also define default conditions that should apply to all queries unless overridden:
*
* ```php
* public static function createQuery($config = [])
* {
* return parent::createQuery($config)->where(['deleted' => false]);
* }
* ```
*
* Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
* default condition. Using [[Query::where()]] will override the default condition.
*
* @param array $config the configuration passed to the ActiveQuery class.
* @return ActiveQuery the newly created [[ActiveQuery]] instance.
*/
public static function createRelation($config = [])
public static function createQuery($config = [])
{
return new ActiveRelation($config);
$config['modelClass'] = get_called_class();
return new ActiveQuery($config);
}
// TODO implement copy and move as pk change is not possible
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\elasticsearch;
use yii\db\ActiveRelationInterface;
use yii\db\ActiveRelationTrait;
/**
* ActiveRelation represents a relation between two Active Record classes.
*
* ActiveRelation instances are usually created by calling [[ActiveRecord::hasOne()]] and
* [[ActiveRecord::hasMany()]]. An Active Record class declares a relation by defining
* a getter method which calls one of the above methods and returns the created ActiveRelation object.
*
* A relation is specified by [[link]] which represents the association between columns
* of different tables; and the multiplicity of the relation is indicated by [[multiple]].
*
* If a relation involves a pivot table, it may be specified by [[via()]] method.
*
* NOTE: elasticsearch limits the number of records returned by any query to 10 records by default.
* If you expect to get more records you should specify limit explicitly in relation definition.
* This is also important for relations that use [[via()]] so that if via records are limited to 10
* the relations records can also not be more than 10.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class ActiveRelation extends ActiveQuery implements ActiveRelationInterface
{
use ActiveRelationTrait;
/**
* Creates a DB command that can be used to execute this query.
* @param Connection $db the DB connection used to create the DB command.
* If null, the DB connection returned by [[modelClass]] will be used.
* @return Command the created DB command instance.
*/
public function createCommand($db = null)
{
if ($this->primaryModel !== null) {
// lazy loading
if (is_array($this->via)) {
// via relation
/** @var ActiveRelation $viaQuery */
list($viaName, $viaQuery) = $this->via;
if ($viaQuery->multiple) {
$viaModels = $viaQuery->all();
$this->primaryModel->populateRelation($viaName, $viaModels);
} else {
$model = $viaQuery->one();
$this->primaryModel->populateRelation($viaName, $model);
$viaModels = $model === null ? [] : [$model];
}
$this->filterByModels($viaModels);
} else {
$this->filterByModels([$this->primaryModel]);
}
}
return parent::createCommand($db);
}
}
......@@ -11,6 +11,9 @@ Yii Framework 2 elasticsearch extension Change Log
- Enh #1765: Added support for primary key path mapping, pk can now be part of the attributes when mapping is defined (cebe)
- Chg #1765: Changed handling of ActiveRecord primary keys, removed getId(), use getPrimaryKey() instead (cebe)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
- Chg #2146: Removed `ActiveRelation` class and moved the functionality to `ActiveQuery`.
All relational queries are now directly served by `ActiveQuery` allowing to use
custom scopes in relations (cebe)
2.0.0 alpha, December 1, 2013
-----------------------------
......
......@@ -84,7 +84,7 @@ class Customer extends \yii\elasticsearch\ActiveRecord
}
/**
* @return ActiveRelation defines a relation to the Order record (can be in other database, e.g. redis or sql)
* @return ActiveQuery defines a relation to the Order record (can be in other database, e.g. redis or sql)
*/
public function getOrders()
{
......
......@@ -62,7 +62,7 @@ class <?= $className ?> extends <?= '\\' . ltrim($generator->baseClass, '\\') .
<?php foreach ($relations as $name => $relation): ?>
/**
* @return \yii\db\ActiveRelation
* @return \yii\db\ActiveQuery
*/
public function get<?= $name ?>()
{
......
......@@ -9,10 +9,19 @@ namespace yii\mongodb;
use yii\db\ActiveQueryInterface;
use yii\db\ActiveQueryTrait;
use yii\db\ActiveRelationTrait;
/**
* ActiveQuery represents a Mongo query associated with an Active Record class.
*
* An ActiveQuery can be a normal query or be used in a relational context.
*
* ActiveQuery instances are usually created by [[ActiveRecord::find()]].
* Relational queries are created by [[ActiveRecord::hasOne()]] and [[ActiveRecord::hasMany()]].
*
* Normal Query
* ------------
*
* ActiveQuery instances are usually created by [[ActiveRecord::find()]].
*
* Because ActiveQuery extends from [[Query]], one can use query methods, such as [[where()]],
......@@ -25,9 +34,25 @@ use yii\db\ActiveQueryTrait;
*
* These options can be configured using methods of the same name. For example:
*
* ~~~
* ```php
* $customers = Customer::find()->with('orders')->asArray()->all();
* ~~~
* ```
*
* Relational query
* ----------------
*
* In relational context ActiveQuery represents a relation between two Active Record classes.
*
* Relational ActiveQuery instances are usually created by calling [[ActiveRecord::hasOne()]] and
* [[ActiveRecord::hasMany()]]. An Active Record class declares a relation by defining
* a getter method which calls one of the above methods and returns the created ActiveQuery object.
*
* A relation is specified by [[link]] which represents the association between columns
* of different tables; and the multiplicity of the relation is indicated by [[multiple]].
*
* If a relation involves a pivot table, it may be specified by [[via()]].
* This methods may only be called in a relational context. Same is true for [[inverseOf()]], which
* marks a relation as inverse of another relation.
*
* @property Collection $collection Collection instance. This property is read-only.
*
......@@ -37,6 +62,38 @@ use yii\db\ActiveQueryTrait;
class ActiveQuery extends Query implements ActiveQueryInterface
{
use ActiveQueryTrait;
use ActiveRelationTrait;
/**
* @inheritdoc
*/
protected function buildCursor($db = null)
{
if ($this->primaryModel !== null) {
// lazy loading
if ($this->via instanceof self) {
// via pivot collection
$viaModels = $this->via->findPivotRows([$this->primaryModel]);
$this->filterByModels($viaModels);
} elseif (is_array($this->via)) {
// via relation
/** @var ActiveQuery $viaQuery */
list($viaName, $viaQuery) = $this->via;
if ($viaQuery->multiple) {
$viaModels = $viaQuery->all();
$this->primaryModel->populateRelation($viaName, $viaModels);
} else {
$model = $viaQuery->one();
$this->primaryModel->populateRelation($viaName, $model);
$viaModels = $model === null ? [] : [$model];
}
$this->filterByModels($viaModels);
} else {
$this->filterByModels([$this->primaryModel]);
}
}
return parent::buildCursor($db);
}
/**
* Executes query and returns all results as an array.
......
......@@ -93,14 +93,31 @@ abstract class ActiveRecord extends BaseActiveRecord
/**
* Creates an [[ActiveQuery]] instance.
* This method is called by [[find()]] to start a "find" command.
*
* This method is called by [[find()]], [[findBySql()]] to start a SELECT query but also
* by [[hasOne()]] and [[hasMany()]] to create a relational query.
* You may override this method to return a customized query (e.g. `CustomerQuery` specified
* written for querying `Customer` purpose.)
*
* You may also define default conditions that should apply to all queries unless overridden:
*
* ```php
* public static function createQuery($config = [])
* {
* return parent::createQuery($config)->where(['deleted' => false]);
* }
* ```
*
* Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
* default condition. Using [[Query::where()]] will override the default condition.
*
* @param array $config the configuration passed to the ActiveQuery class.
* @return ActiveQuery the newly created [[ActiveQuery]] instance.
*/
public static function createQuery()
public static function createQuery($config = [])
{
return new ActiveQuery(['modelClass' => get_called_class()]);
$config['modelClass'] = get_called_class();
return new ActiveQuery($config);
}
/**
......@@ -142,18 +159,6 @@ abstract class ActiveRecord extends BaseActiveRecord
}
/**
* Creates an [[ActiveRelation]] instance.
* This method is called by [[hasOne()]] and [[hasMany()]] to create a relation instance.
* You may override this method to return a customized relation.
* @param array $config the configuration passed to the ActiveRelation class.
* @return ActiveRelation the newly created [[ActiveRelation]] instance.
*/
public static function createRelation($config = [])
{
return new ActiveRelation($config);
}
/**
* Returns the list of all attribute names of the model.
* This method must be overridden by child classes to define available attributes.
* Note: primary key attribute "_id" should be always present in returned array.
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\mongodb;
use yii\db\ActiveRelationInterface;
use yii\db\ActiveRelationTrait;
/**
* ActiveRelation represents a relation to Mongo Active Record class.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class ActiveRelation extends ActiveQuery implements ActiveRelationInterface
{
use ActiveRelationTrait;
/**
* @inheritdoc
*/
protected function buildCursor($db = null)
{
if ($this->primaryModel !== null) {
// lazy loading
if ($this->via instanceof self) {
// via pivot collection
$viaModels = $this->via->findPivotRows([$this->primaryModel]);
$this->filterByModels($viaModels);
} elseif (is_array($this->via)) {
// via relation
/** @var ActiveRelation $viaQuery */
list($viaName, $viaQuery) = $this->via;
if ($viaQuery->multiple) {
$viaModels = $viaQuery->all();
$this->primaryModel->populateRelation($viaName, $viaModels);
} else {
$model = $viaQuery->one();
$this->primaryModel->populateRelation($viaName, $model);
$viaModels = $model === null ? [] : [$model];
}
$this->filterByModels($viaModels);
} else {
$this->filterByModels([$this->primaryModel]);
}
}
return parent::buildCursor($db);
}
}
\ No newline at end of file
......@@ -9,6 +9,7 @@ namespace yii\mongodb\file;
use yii\db\ActiveQueryInterface;
use yii\db\ActiveQueryTrait;
use yii\db\ActiveRelationTrait;
/**
* ActiveQuery represents a Mongo query associated with an file Active Record class.
......@@ -37,6 +38,7 @@ use yii\db\ActiveQueryTrait;
class ActiveQuery extends Query implements ActiveQueryInterface
{
use ActiveQueryTrait;
use ActiveRelationTrait;
/**
* Executes query and returns all results as an array.
......
......@@ -46,14 +46,31 @@ abstract class ActiveRecord extends \yii\mongodb\ActiveRecord
{
/**
* Creates an [[ActiveQuery]] instance.
* This method is called by [[find()]] to start a "find" command.
* You may override this method to return a customized query (e.g. `ImageFileQuery` specified
* written for querying `ImageFile` purpose.)
*
* This method is called by [[find()]], [[findBySql()]] to start a SELECT query but also
* by [[hasOne()]] and [[hasMany()]] to create a relational query.
* You may override this method to return a customized query (e.g. `CustomerQuery` specified
* written for querying `Customer` purpose.)
*
* You may also define default conditions that should apply to all queries unless overridden:
*
* ```php
* public static function createQuery($config = [])
* {
* return parent::createQuery($config)->where(['deleted' => false]);
* }
* ```
*
* Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
* default condition. Using [[Query::where()]] will override the default condition.
*
* @param array $config the configuration passed to the ActiveQuery class.
* @return ActiveQuery the newly created [[ActiveQuery]] instance.
*/
public static function createQuery()
public static function createQuery($config = [])
{
return new ActiveQuery(['modelClass' => get_called_class()]);
$config['modelClass'] = get_called_class();
return new ActiveQuery($config);
}
/**
......@@ -66,18 +83,6 @@ abstract class ActiveRecord extends \yii\mongodb\ActiveRecord
}
/**
* Creates an [[ActiveRelation]] instance.
* This method is called by [[hasOne()]] and [[hasMany()]] to create a relation instance.
* You may override this method to return a customized relation.
* @param array $config the configuration passed to the ActiveRelation class.
* @return ActiveRelation the newly created [[ActiveRelation]] instance.
*/
public static function createRelation($config = [])
{
return new ActiveRelation($config);
}
/**
* Returns the list of all attribute names of the model.
* This method could be overridden by child classes to define available attributes.
* Note: all attributes defined in base Active Record class should be always present
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\mongodb\file;
use yii\db\ActiveRelationInterface;
use yii\db\ActiveRelationTrait;
/**
* ActiveRelation represents a relation to Mongo GridFS Active Record class.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class ActiveRelation extends ActiveQuery implements ActiveRelationInterface
{
use ActiveRelationTrait;
}
\ No newline at end of file
......@@ -6,17 +6,24 @@
*/
namespace yii\redis;
use yii\base\InvalidParamException;
use yii\base\NotSupportedException;
use yii\db\ActiveQueryInterface;
use yii\db\ActiveQueryTrait;
use yii\db\ActiveRelationTrait;
use yii\db\QueryTrait;
/**
* ActiveQuery represents a query associated with an Active Record class.
*
* ActiveQuery instances are usually created by [[ActiveRecord::find()]]
* and [[ActiveRecord::count()]].
* An ActiveQuery can be a normal query or be used in a relational context.
*
* ActiveQuery instances are usually created by [[ActiveRecord::find()]].
* Relational queries are created by [[ActiveRecord::hasOne()]] and [[ActiveRecord::hasMany()]].
*
* Normal Query
* ------------
*
* ActiveQuery mainly provides the following methods to retrieve the query results:
*
......@@ -40,9 +47,25 @@ use yii\db\QueryTrait;
*
* These options can be configured using methods of the same name. For example:
*
* ~~~
* ```php
* $customers = Customer::find()->with('orders')->asArray()->all();
* ~~~
* ```
*
* Relational query
* ----------------
*
* In relational context ActiveQuery represents a relation between two Active Record classes.
*
* Relational ActiveQuery instances are usually created by calling [[ActiveRecord::hasOne()]] and
* [[ActiveRecord::hasMany()]]. An Active Record class declares a relation by defining
* a getter method which calls one of the above methods and returns the created ActiveQuery object.
*
* A relation is specified by [[link]] which represents the association between columns
* of different tables; and the multiplicity of the relation is indicated by [[multiple]].
*
* If a relation involves a pivot table, it may be specified by [[via()]].
* This methods may only be called in a relational context. Same is true for [[inverseOf()]], which
* marks a relation as inverse of another relation.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
......@@ -51,6 +74,7 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface
{
use QueryTrait;
use ActiveQueryTrait;
use ActiveRelationTrait;
/**
* Executes the query and returns all results as an array.
......@@ -252,6 +276,30 @@ class ActiveQuery extends \yii\base\Component implements ActiveQueryInterface
*/
protected function executeScript($db, $type, $columnName = null)
{
if ($this->primaryModel !== null) {
// lazy loading
if ($this->via instanceof self) {
// via pivot table
$viaModels = $this->via->findPivotRows([$this->primaryModel]);
$this->filterByModels($viaModels);
} elseif (is_array($this->via)) {
// via relation
/** @var ActiveQuery $viaQuery */
list($viaName, $viaQuery) = $this->via;
if ($viaQuery->multiple) {
$viaModels = $viaQuery->all();
$this->primaryModel->populateRelation($viaName, $viaModels);
} else {
$model = $viaQuery->one();
$this->primaryModel->populateRelation($viaName, $model);
$viaModels = $model === null ? [] : [$model];
}
$this->filterByModels($viaModels);
} else {
$this->filterByModels([$this->primaryModel]);
}
}
if (!empty($this->orderBy)) {
throw new NotSupportedException('orderBy is currently not supported by redis ActiveRecord.');
}
......
......@@ -49,19 +49,32 @@ class ActiveRecord extends BaseActiveRecord
}
/**
* @inheritdoc
*/
public static function createQuery()
{
return new ActiveQuery(['modelClass' => get_called_class()]);
}
/**
* @inheritdoc
* Creates an [[ActiveQuery]] instance.
*
* This method is called by [[find()]], [[findBySql()]] to start a SELECT query but also
* by [[hasOne()]] and [[hasMany()]] to create a relational query.
* You may override this method to return a customized query (e.g. `CustomerQuery` specified
* written for querying `Customer` purpose.)
*
* You may also define default conditions that should apply to all queries unless overridden:
*
* ```php
* public static function createQuery($config= [])
* {
* return parent::createQuery($config)->where(['deleted' => false]);
* }
* ```
*
* Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
* default condition. Using [[Query::where()]] will override the default condition.
*
* @param array $config the configuration passed to the ActiveQuery class.
* @return ActiveQuery the newly created [[ActiveQuery]] instance.
*/
public static function createRelation($config = [])
public static function createQuery($config = [])
{
return new ActiveRelation($config);
$config['modelClass'] = get_called_class();
return new ActiveQuery($config);
}
/**
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\redis;
use yii\db\ActiveRelationInterface;
use yii\db\ActiveRelationTrait;
/**
* ActiveRelation represents a relation between two Active Record classes.
*
* ActiveRelation instances are usually created by calling [[ActiveRecord::hasOne()]] and
* [[ActiveRecord::hasMany()]]. An Active Record class declares a relation by defining
* a getter method which calls one of the above methods and returns the created ActiveRelation object.
*
* A relation is specified by [[link]] which represents the association between columns
* of different tables; and the multiplicity of the relation is indicated by [[multiple]].
*
* If a relation involves a pivot table, it may be specified by [[via()]] or [[viaTable()]] method.
*
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class ActiveRelation extends ActiveQuery implements ActiveRelationInterface
{
use ActiveRelationTrait;
/**
* Executes a script created by [[LuaScriptBuilder]]
* @param Connection $db the database connection used to execute the query.
* If this parameter is not given, the `db` application component will be used.
* @param string $type the type of the script to generate
* @param null $column
* @return array|bool|null|string
*/
protected function executeScript($db, $type, $column=null)
{
if ($this->primaryModel !== null) {
// lazy loading
if ($this->via instanceof self) {
// via pivot table
$viaModels = $this->via->findPivotRows([$this->primaryModel]);
$this->filterByModels($viaModels);
} elseif (is_array($this->via)) {
// via relation
/** @var ActiveRelation $viaQuery */
list($viaName, $viaQuery) = $this->via;
if ($viaQuery->multiple) {
$viaModels = $viaQuery->all();
$this->primaryModel->populateRelation($viaName, $viaModels);
} else {
$model = $viaQuery->one();
$this->primaryModel->populateRelation($viaName, $model);
$viaModels = $model === null ? [] : [$model];
}
$this->filterByModels($viaModels);
} else {
$this->filterByModels([$this->primaryModel]);
}
}
return parent::executeScript($db, $type, $column);
}
}
......@@ -7,6 +7,9 @@ Yii Framework 2 redis extension Change Log
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder)
- Enh #1773: keyPrefix property of Session and Cache is not restricted to alnum characters anymore (cebe)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
- Chg #2146: Removed `ActiveRelation` class and moved the functionality to `ActiveQuery`.
All relational queries are now directly served by `ActiveQuery` allowing to use
custom scopes in relations (cebe)
2.0.0 alpha, December 1, 2013
-----------------------------
......
......@@ -148,7 +148,7 @@ class Customer extends \yii\redis\ActiveRecord
}
/**
* @return ActiveRelation defines a relation to the Order record (can be in other database, e.g. elasticsearch or sql)
* @return ActiveQuery defines a relation to the Order record (can be in other database, e.g. elasticsearch or sql)
*/
public function getOrders()
{
......
......@@ -10,11 +10,18 @@ namespace yii\sphinx;
use yii\base\InvalidCallException;
use yii\db\ActiveQueryInterface;
use yii\db\ActiveQueryTrait;
use yii\db\ActiveRelationTrait;
/**
* ActiveQuery represents a Sphinx query associated with an Active Record class.
*
* An ActiveQuery can be a normal query or be used in a relational context.
*
* ActiveQuery instances are usually created by [[ActiveRecord::find()]] and [[ActiveRecord::findBySql()]].
* Relational queries are created by [[ActiveRecord::hasOne()]] and [[ActiveRecord::hasMany()]].
*
* Normal Query
* ------------
*
* Because ActiveQuery extends from [[Query]], one can use query methods, such as [[where()]],
* [[orderBy()]] to customize the query options.
......@@ -54,12 +61,29 @@ use yii\db\ActiveQueryTrait;
* $articles = Article::find()->with('source')->snippetByModel()->all();
* ~~~
*
* Relational query
* ----------------
*
* In relational context ActiveQuery represents a relation between two Active Record classes.
*
* Relational ActiveQuery instances are usually created by calling [[ActiveRecord::hasOne()]] and
* [[ActiveRecord::hasMany()]]. An Active Record class declares a relation by defining
* a getter method which calls one of the above methods and returns the created ActiveQuery object.
*
* A relation is specified by [[link]] which represents the association between columns
* of different tables; and the multiplicity of the relation is indicated by [[multiple]].
*
* If a relation involves a pivot table, it may be specified by [[via()]].
* This methods may only be called in a relational context. Same is true for [[inverseOf()]], which
* marks a relation as inverse of another relation.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class ActiveQuery extends Query implements ActiveQueryInterface
{
use ActiveQueryTrait;
use ActiveRelationTrait;
/**
* @var string the SQL statement to be executed for retrieving AR records.
......@@ -165,6 +189,30 @@ class ActiveQuery extends Query implements ActiveQueryInterface
*/
public function createCommand($db = null)
{
if ($this->primaryModel !== null) {
// lazy loading a relational query
if ($this->via instanceof self) {
// via pivot index
$viaModels = $this->via->findPivotRows([$this->primaryModel]);
$this->filterByModels($viaModels);
} elseif (is_array($this->via)) {
// via relation
/** @var ActiveQuery $viaQuery */
list($viaName, $viaQuery) = $this->via;
if ($viaQuery->multiple) {
$viaModels = $viaQuery->all();
$this->primaryModel->populateRelation($viaName, $viaModels);
} else {
$model = $viaQuery->one();
$this->primaryModel->populateRelation($viaName, $model);
$viaModels = $model === null ? [] : [$model];
}
$this->filterByModels($viaModels);
} else {
$this->filterByModels([$this->primaryModel]);
}
}
$this->setConnection($db);
$db = $this->getConnection();
......
......@@ -9,7 +9,6 @@ namespace yii\sphinx;
use yii\base\InvalidConfigException;
use yii\base\NotSupportedException;
use yii\db\ActiveRelationInterface;
use yii\db\BaseActiveRecord;
use yii\db\StaleObjectException;
use yii\helpers\Inflector;
......@@ -135,14 +134,31 @@ abstract class ActiveRecord extends BaseActiveRecord
/**
* Creates an [[ActiveQuery]] instance.
* This method is called by [[find()]], [[findBySql()]] and [[count()]] to start a SELECT query.
* You may override this method to return a customized query (e.g. `ArticleQuery` specified
* written for querying `Article` purpose.)
*
* This method is called by [[find()]], [[findBySql()]] to start a SELECT query but also
* by [[hasOne()]] and [[hasMany()]] to create a relational query.
* You may override this method to return a customized query (e.g. `CustomerQuery` specified
* written for querying `Customer` purpose.)
*
* You may also define default conditions that should apply to all queries unless overridden:
*
* ```php
* public static function createQuery($config = [])
* {
* return parent::createQuery($config)->where(['deleted' => false]);
* }
* ```
*
* Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
* default condition. Using [[Query::where()]] will override the default condition.
*
* @param array $config the configuration passed to the ActiveQuery class.
* @return ActiveQuery the newly created [[ActiveQuery]] instance.
*/
public static function createQuery()
public static function createQuery($config = [])
{
return new ActiveQuery(['modelClass' => get_called_class()]);
$config['modelClass'] = get_called_class();
return new ActiveQuery($config);
}
/**
......@@ -304,18 +320,6 @@ abstract class ActiveRecord extends BaseActiveRecord
}
/**
* Creates an [[ActiveRelationInterface]] instance.
* This method is called by [[hasOne()]] and [[hasMany()]] to create a relation instance.
* You may override this method to return a customized relation.
* @param array $config the configuration passed to the ActiveRelation class.
* @return ActiveRelationInterface the newly created [[ActiveRelation]] instance.
*/
public static function createRelation($config = [])
{
return new ActiveRelation($config);
}
/**
* Returns the list of all attribute names of the model.
* The default implementation will return all column names of the table associated with this AR class.
* @return array list of attribute names.
......
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\sphinx;
use yii\db\ActiveRelationInterface;
use yii\db\ActiveRelationTrait;
/**
* ActiveRelation represents a relation to Sphinx Active Record class.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0
*/
class ActiveRelation extends ActiveQuery implements ActiveRelationInterface
{
use ActiveRelationTrait;
/**
* @inheritdoc
*/
public function createCommand($db = null)
{
if ($this->primaryModel !== null) {
// lazy loading
if ($this->via instanceof self) {
// via pivot index
$viaModels = $this->via->findPivotRows([$this->primaryModel]);
$this->filterByModels($viaModels);
} elseif (is_array($this->via)) {
// via relation
/** @var ActiveRelation $viaQuery */
list($viaName, $viaQuery) = $this->via;
if ($viaQuery->multiple) {
$viaModels = $viaQuery->all();
$this->primaryModel->populateRelation($viaName, $viaModels);
} else {
$model = $viaQuery->one();
$this->primaryModel->populateRelation($viaName, $model);
$viaModels = $model === null ? [] : [$model];
}
$this->filterByModels($viaModels);
} else {
$this->filterByModels([$this->primaryModel]);
}
}
return parent::createCommand($db);
}
}
\ No newline at end of file
......@@ -5,9 +5,12 @@ Yii Framework 2 sphinx extension Change Log
----------------------------
- Bug #1993: afterFind event in AR is now called after relations have been populated (cebe, creocoder)
- Bug #2160: SphinxQL does not support OFFSET (qiangxue, romeo7)
- Bug #2160: SphinxQL does not support `OFFSET` (qiangxue, romeo7)
- Enh #1398: Refactor ActiveRecord to use BaseActiveRecord class of the framework (klimov-paul)
- Chg #2281: Renamed `ActiveRecord::create()` to `populateRecord()` and changed signature. This method will not call instantiate() anymore (cebe)
- Chg #2146: Removed `ActiveRelation` class and moved the functionality to `ActiveQuery`.
All relational queries are now directly served by `ActiveQuery` allowing to use
custom scopes in relations (cebe)
2.0.0 alpha, December 1, 2013
-----------------------------
......
......@@ -176,7 +176,10 @@ Yii Framework 2 Change Log
- Chg: Advanced app template: moved database connection DSN, login and password to `-local` config not to expose it to VCS (samdark)
- Chg: Renamed `yii\web\Request::acceptedLanguages` to `acceptableLanguages` (qiangxue)
- Chg: Removed implementation of `Arrayable` from `yii\Object` (qiangxue)
- Chg: Renamed `ActiveRecordInterface::createActiveRelation()` to `createRelation()` (qiangxue)
- Chg #2146: Removed `ActiveRelation` class and `ActiveRelationInterface`, moved the functionality to `ActiveQuery`.
All relational queries are now directly served by `ActiveQuery` allowing to use custom scopes in relations
and also to declare arbitrary queries as relations.
Also removed `ActiveRecordInterface::createActiveRelation()` (cebe)
- Chg: The scripts in asset bundles are now registered in `View` at the end of `endBody()`. It was done in `endPage()` previously (qiangxue)
- Chg: Renamed `csrf-var` to `csrf-param` for CSRF header name (Dilip)
- Chg: The directory holding email templates is renamed from `mails` to `mail` (qiangxue)
......
......@@ -11,7 +11,13 @@ namespace yii\db;
/**
* ActiveQuery represents a DB query associated with an Active Record class.
*
* An ActiveQuery can be a normal query or be used in a relational context.
*
* ActiveQuery instances are usually created by [[ActiveRecord::find()]] and [[ActiveRecord::findBySql()]].
* Relational queries are created by [[ActiveRecord::hasOne()]] and [[ActiveRecord::hasMany()]].
*
* Normal Query
* ------------
*
* ActiveQuery mainly provides the following methods to retrieve the query results:
*
......@@ -37,9 +43,26 @@ namespace yii\db;
*
* These options can be configured using methods of the same name. For example:
*
* ~~~
* ```php
* $customers = Customer::find()->with('orders')->asArray()->all();
* ~~~
* ```
*
* Relational query
* ----------------
*
* In relational context ActiveQuery represents a relation between two Active Record classes.
*
* Relational ActiveQuery instances are usually created by calling [[ActiveRecord::hasOne()]] and
* [[ActiveRecord::hasMany()]]. An Active Record class declares a relation by defining
* a getter method which calls one of the above methods and returns the created ActiveQuery object.
*
* A relation is specified by [[link]] which represents the association between columns
* of different tables; and the multiplicity of the relation is indicated by [[multiple]].
*
* If a relation involves a pivot table, it may be specified by [[via()]] or [[viaTable()]] method.
* These methods may only be called in a relational context. Same is true for [[inverseOf()]], which
* marks a relation as inverse of another relation and [[onCondition()]] which adds a condition that
* is to be added to relational querys join condition.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
......@@ -48,12 +71,21 @@ namespace yii\db;
class ActiveQuery extends Query implements ActiveQueryInterface
{
use ActiveQueryTrait;
use ActiveRelationTrait;
/**
* @var string the SQL statement to be executed for retrieving AR records.
* This is set by [[ActiveRecord::findBySql()]].
*/
public $sql;
/**
* @var string|array the join condition to be used when this query is used in a relational context.
* The condition will be used in the ON part when [[ActiveQuery::joinWith()]] is called.
* Otherwise, the condition will be used in the WHERE part of a query.
* Please refer to [[Query::where()]] on how to specify this parameter.
* @see onCondition()
*/
public $on;
/**
......@@ -175,6 +207,31 @@ class ActiveQuery extends Query implements ActiveQueryInterface
*/
public function createCommand($db = null)
{
if ($this->primaryModel === null) {
// not a relational context or eager loading
if (!empty($this->on)) {
$where = $this->where;
$this->andWhere($this->on);
$command = $this->createCommandInternal($db);
$this->where = $where;
return $command;
} else {
return $this->createCommandInternal($db);
}
} else {
// lazy loading of a relation
return $this->createRelationalCommand($db);
}
}
/**
* Creates a DB command that can be used to execute this query.
* @param Connection $db the DB connection used to create the DB command.
* If null, the DB connection returned by [[modelClass]] will be used.
* @return Command the created DB command instance.
*/
protected function createCommandInternal($db)
{
/** @var ActiveRecord $modelClass */
$modelClass = $this->modelClass;
if ($db === null) {
......@@ -191,6 +248,47 @@ class ActiveQuery extends Query implements ActiveQueryInterface
}
/**
* Creates a command for lazy loading of a relation.
* @param Connection $db the DB connection used to create the DB command.
* @return Command the created DB command instance.
*/
private function createRelationalCommand($db = null)
{
$where = $this->where;
if ($this->via instanceof self) {
// via pivot table
$viaModels = $this->via->findPivotRows([$this->primaryModel]);
$this->filterByModels($viaModels);
} elseif (is_array($this->via)) {
// via relation
/** @var ActiveQuery $viaQuery */
list($viaName, $viaQuery) = $this->via;
if ($viaQuery->multiple) {
$viaModels = $viaQuery->all();
$this->primaryModel->populateRelation($viaName, $viaModels);
} else {
$model = $viaQuery->one();
$this->primaryModel->populateRelation($viaName, $model);
$viaModels = $model === null ? [] : [$model];
}
$this->filterByModels($viaModels);
} else {
$this->filterByModels([$this->primaryModel]);
}
if (!empty($this->on)) {
$this->andWhere($this->on);
}
$command = $this->createCommandInternal($db);
$this->where = $where;
return $command;
}
/**
* Joins with the specified relations.
*
* This method allows you to reuse existing relation definitions to perform JOIN queries.
......@@ -355,14 +453,14 @@ class ActiveQuery extends Query implements ActiveQueryInterface
* Joins a parent query with a child query.
* The current query object will be modified accordingly.
* @param ActiveQuery $parent
* @param ActiveRelation $child
* @param ActiveQuery $child
* @param string $joinType
*/
private function joinWithRelation($parent, $child, $joinType)
{
$via = $child->via;
$child->via = null;
if ($via instanceof ActiveRelation) {
if ($via instanceof ActiveQuery) {
// via table
$this->joinWithRelation($parent, $via, $joinType);
$this->joinWithRelation($via, $child, $joinType);
......@@ -426,4 +524,67 @@ class ActiveQuery extends Query implements ActiveQueryInterface
}
}
}
/**
* Sets the ON condition for a relational query.
* The condition will be used in the ON part when [[ActiveQuery::joinWith()]] is called.
* Otherwise, the condition will be used in the WHERE part of a query.
*
* Use this method to specify additional conditions when declaring a relation in the [[ActiveRecord]] class:
*
* ```php
* public function getActiveUsers()
* {
* return $this->hasMany(User::className(), ['id' => 'user_id'])->onCondition(['active' => true]);
* }
* ```
*
* @param string|array $condition the ON condition. Please refer to [[Query::where()]] on how to specify this parameter.
* @param array $params the parameters (name => value) to be bound to the query.
* @return static the query object itself
*/
public function onCondition($condition, $params = [])
{
$this->on = $condition;
$this->addParams($params);
return $this;
}
/**
* Specifies the pivot table for a relational query.
*
* Use this method to specify a pivot table when declaring a relation in the [[ActiveRecord]] class:
*
* ```php
* public function getItems()
* {
* return $this->hasMany(Item::className(), ['id' => 'item_id'])
* ->viaTable('tbl_order_item', ['order_id' => 'id']);
* }
* ```
*
* @param string $tableName the name of the pivot table.
* @param array $link the link between the pivot table and the table associated with [[primaryModel]].
* The keys of the array represent the columns in the pivot table, and the values represent the columns
* in the [[primaryModel]] table.
* @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
* Its signature should be `function($query)`, where `$query` is the query to be customized.
* @return static
* @see via()
*/
public function viaTable($tableName, $link, $callable = null)
{
$relation = new ActiveQuery([
'modelClass' => get_class($this->primaryModel),
'from' => [$tableName],
'link' => $link,
'multiple' => true,
'asArray' => true,
]);
$this->via = $relation;
if ($callable !== null) {
call_user_func($callable, $relation);
}
return $this;
}
}
......@@ -10,7 +10,11 @@ namespace yii\db;
/**
* ActiveQueryInterface defines the common interface to be implemented by active record query classes.
*
* A class implementing this interface should also use [[ActiveQueryTrait]].
* That are methods for either normal queries that return active records but also relational queries
* in which the query represents a relation between two active record classes and will return related
* records only.
*
* A class implementing this interface should also use [[ActiveQueryTrait]] and [[ActiveRelationTrait]].
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
......@@ -74,4 +78,22 @@ interface ActiveQueryInterface extends QueryInterface
* @return static the query object itself
*/
public function with();
/**
* Specifies the relation associated with the pivot table for use in relational query.
* @param string $relationName the relation name. This refers to a relation declared in the [[ActiveRelationTrait::primaryModel|primaryModel]] of the relation.
* @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
* Its signature should be `function($query)`, where `$query` is the query to be customized.
* @return static the relation object itself.
*/
public function via($relationName, $callable = null);
/**
* Finds the related records for the specified primary record.
* This method is invoked when a relation of an ActiveRecord is being accessed in a lazy fashion.
* @param string $name the relation name
* @param ActiveRecordInterface $model the primary model
* @return mixed the related record(s)
*/
public function findFor($name, $model);
}
......@@ -169,7 +169,7 @@ trait ActiveQueryTrait
/**
* @param ActiveRecord $model
* @param array $with
* @return ActiveRelationInterface[]
* @return ActiveQueryInterface[]
*/
private function normalizeRelations($model, $with)
{
......
......@@ -151,27 +151,30 @@ class ActiveRecord extends BaseActiveRecord
/**
* Creates an [[ActiveQuery]] instance.
*
* This method is called by [[find()]], [[findBySql()]] to start a SELECT query.
* This method is called by [[find()]], [[findBySql()]] to start a SELECT query but also
* by [[hasOne()]] and [[hasMany()]] to create a relational query.
* You may override this method to return a customized query (e.g. `CustomerQuery` specified
* written for querying `Customer` purpose.)
*
* You may also define default conditions that should apply to all queries unless overridden:
*
* ```php
* public static function createQuery()
* public static function createQuery($config = [])
* {
* return parent::createQuery()->where(['deleted' => false]);
* return parent::createQuery($config)->where(['deleted' => false]);
* }
* ```
*
* Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
* default condition. Using [[Query::where()]] will override the default condition.
*
* @param array $config the configuration passed to the ActiveQuery class.
* @return ActiveQuery the newly created [[ActiveQuery]] instance.
*/
public static function createQuery()
public static function createQuery($config = [])
{
return new ActiveQuery(['modelClass' => get_called_class()]);
$config['modelClass'] = get_called_class();
return new ActiveQuery($config);
}
/**
......@@ -263,18 +266,6 @@ class ActiveRecord extends BaseActiveRecord
}
/**
* Creates an [[ActiveRelation]] instance.
* This method is called by [[hasOne()]] and [[hasMany()]] to create a relation instance.
* You may override this method to return a customized relation.
* @param array $config the configuration passed to the ActiveRelation class.
* @return ActiveRelation the newly created [[ActiveRelation]] instance.
*/
public static function createRelation($config = [])
{
return new ActiveRelation($config);
}
/**
* @inheritdoc
*/
public static function populateRecord($record, $row)
......
......@@ -112,25 +112,29 @@ interface ActiveRecordInterface
/**
* Creates an [[ActiveQueryInterface|ActiveQuery]] instance.
*
* This method is called by [[find()]] to start a SELECT query.
* This method is called by [[find()]] to start a SELECT query but also
* by [[BaseActiveRecord::hasOne()]] and [[BaseActiveRecord::hasMany()]] to
* create a relational query.
*
* You may override this method to return a customized query (e.g. `CustomerQuery` specified
* written for querying `Customer` purpose.)
*
* You may also define default conditions that should apply to all queries unless overridden:
*
* ```php
* public static function createQuery()
* public static function createQuery($config = [])
* {
* return parent::createQuery()->where(['deleted' => false]);
* return parent::createQuery($config)->where(['deleted' => false]);
* }
* ```
*
* Note that all queries should use [[Query::andWhere()]] and [[Query::orWhere()]] to keep the
* default condition. Using [[Query::where()]] will override the default condition.
*
* @param array $config the configuration passed to the ActiveQuery class.
* @return ActiveQueryInterface the newly created [[ActiveQueryInterface|ActiveQuery]] instance.
*/
public static function createQuery();
public static function createQuery($config = []);
/**
* Updates records using the provided attribute values and conditions.
......@@ -256,21 +260,12 @@ interface ActiveRecordInterface
public function equals($record);
/**
* Creates an [[ActiveRelationInterface|ActiveRelation]] instance.
* This method is called by [[BaseActiveRecord::hasOne()]] and [[BaseActiveRecord::hasMany()]] to
* create a relation instance.
* You may override this method to return a customized relation.
* @param array $config the configuration passed to the ActiveRelation class.
* @return ActiveRelation the newly created [[ActiveRelation]] instance.
*/
public static function createRelation($config = []);
/**
* Returns the relation object with the specified name.
* A relation is defined by a getter method which returns an [[ActiveRelationInterface|ActiveRelation]] object.
* A relation is defined by a getter method which returns an object implementing the [[ActiveQueryInterface]]
* (normally this would be a relational [[ActiveQuery]] object).
* It can be declared in either the ActiveRecord class itself or one of its behaviors.
* @param string $name the relation name
* @return ActiveRelation the relation object
* @return ActiveQueryInterface the relational query object
*/
public function getRelation($name);
......@@ -290,7 +285,7 @@ interface ActiveRecordInterface
* @param static $model the record to be linked with the current one.
* @param array $extraColumns additional column values to be saved into the pivot table.
* This parameter is only meaningful for a relationship involving a pivot table
* (i.e., a relation set with `[[ActiveRelationInterface::via()]]`.)
* (i.e., a relation set with `[[ActiveQueryInterface::via()]]`.)
*/
public function link($name, $model, $extraColumns = []);
......
<?php
/**
* @author Qiang Xue <qiang.xue@gmail.com>
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db;
/**
* ActiveRelation represents a relation between two Active Record classes.
*
* ActiveRelation instances are usually created by calling [[ActiveRecord::hasOne()]] and
* [[ActiveRecord::hasMany()]]. An Active Record class declares a relation by defining
* a getter method which calls one of the above methods and returns the created ActiveRelation object.
*
* A relation is specified by [[link]] which represents the association between columns
* of different tables; and the multiplicity of the relation is indicated by [[multiple]].
*
* If a relation involves a pivot table, it may be specified by [[via()]] or [[viaTable()]] method.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
class ActiveRelation extends ActiveQuery implements ActiveRelationInterface
{
use ActiveRelationTrait;
/**
* @var string|array the join condition. Please refer to [[Query::where()]] on how to specify this parameter.
* The condition will be used in the ON part when [[ActiveQuery::joinWith()]] is called.
* Otherwise, the condition will be used in the WHERE part of a query.
*/
public $on;
/**
* Sets the ON condition for the query.
* The condition will be used in the ON part when [[ActiveQuery::joinWith()]] is called.
* Otherwise, the condition will be used in the WHERE part of a query.
* @param string|array $condition the ON condition. Please refer to [[Query::where()]] on how to specify this parameter.
* @param array $params the parameters (name => value) to be bound to the query.
* @return static the query object itself
*/
public function onCondition($condition, $params = [])
{
$this->on = $condition;
$this->addParams($params);
return $this;
}
/**
* Specifies the pivot table.
* @param string $tableName the name of the pivot table.
* @param array $link the link between the pivot table and the table associated with [[primaryModel]].
* The keys of the array represent the columns in the pivot table, and the values represent the columns
* in the [[primaryModel]] table.
* @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
* Its signature should be `function($query)`, where `$query` is the query to be customized.
* @return static
*/
public function viaTable($tableName, $link, $callable = null)
{
$relation = new ActiveRelation([
'modelClass' => get_class($this->primaryModel),
'from' => [$tableName],
'link' => $link,
'multiple' => true,
'asArray' => true,
]);
$this->via = $relation;
if ($callable !== null) {
call_user_func($callable, $relation);
}
return $this;
}
/**
* Creates a DB command that can be used to execute this query.
* @param Connection $db the DB connection used to create the DB command.
* If null, the DB connection returned by [[modelClass]] will be used.
* @return Command the created DB command instance.
*/
public function createCommand($db = null)
{
if ($this->primaryModel === null) {
// eager loading
if (!empty($this->on)) {
$where = $this->where;
$this->andWhere($this->on);
$command = parent::createCommand($db);
$this->where = $where;
return $command;
} else {
return parent::createCommand($db);
}
}
// lazy loading
$where = $this->where;
if ($this->via instanceof self) {
// via pivot table
$viaModels = $this->via->findPivotRows([$this->primaryModel]);
$this->filterByModels($viaModels);
} elseif (is_array($this->via)) {
// via relation
/** @var ActiveRelation $viaQuery */
list($viaName, $viaQuery) = $this->via;
if ($viaQuery->multiple) {
$viaModels = $viaQuery->all();
$this->primaryModel->populateRelation($viaName, $viaModels);
} else {
$model = $viaQuery->one();
$this->primaryModel->populateRelation($viaName, $model);
$viaModels = $model === null ? [] : [$model];
}
$this->filterByModels($viaModels);
} else {
$this->filterByModels([$this->primaryModel]);
}
if (!empty($this->on)) {
$this->andWhere($this->on);
}
$command = parent::createCommand($db);
$this->where = $where;
return $command;
}
}
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\db;
/**
* ActiveRelationInterface defines the common interface to be implemented by active record relation classes.
*
* A class implementing this interface should also use [[ActiveRelationTrait]].
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
* @since 2.0
*/
interface ActiveRelationInterface extends ActiveQueryInterface
{
/**
* Specifies the relation associated with the pivot table.
* @param string $relationName the relation name. This refers to a relation declared in the [[ActiveRelationTrait::primaryModel|primaryModel]] of the relation.
* @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
* Its signature should be `function($query)`, where `$query` is the query to be customized.
* @return static the relation object itself.
*/
public function via($relationName, $callable = null);
}
......@@ -11,7 +11,7 @@ use yii\base\InvalidConfigException;
use yii\base\InvalidParamException;
/**
* ActiveRelationTrait implements the common methods and properties for active record relation classes.
* ActiveRelationTrait implements the common methods and properties for active record relational queries.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @author Carsten Brandt <mail@cebe.cc>
......@@ -20,25 +20,30 @@ use yii\base\InvalidParamException;
trait ActiveRelationTrait
{
/**
* @var boolean whether this relation should populate all query results into AR instances.
* If false, only the first row of the results will be retrieved.
* @var boolean whether this query represents a relation to more than one record.
* This property is only used in relational context. If true, this relation will
* populate all query results into AR instances using [[all()]].
* If false, only the first row of the results will be retrieved using [[one()]].
*/
public $multiple;
/**
* @var ActiveRecord the primary model that this relation is associated with.
* @var ActiveRecord the primary model of a relational query.
* This is used only in lazy loading with dynamic query options.
*/
public $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 a 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 this will be done automatically by Yii.
* This property is only used in relational context.
*/
public $link;
/**
* @var array the query associated with the pivot table. Please call [[via()]]
* to set this property instead of directly setting it.
* This property is only used in relational context.
* @see via()
*/
public $via;
/**
......@@ -48,9 +53,12 @@ trait ActiveRelationTrait
* If this property is set, the primary record(s) will be referenced through the specified relation.
* For example, `$customer->orders[0]->customer` and `$customer` will be the same object,
* and accessing the customer of an order will not trigger new DB query.
* This property is only used in relational context.
* @see inverseOf()
*/
public $inverseOf;
/**
* Clones internal objects.
*/
......@@ -66,6 +74,22 @@ trait ActiveRelationTrait
/**
* Specifies the relation associated with the pivot table.
*
* Use this method to specify a pivot record/table when declaring a relation in the [[ActiveRecord]] class:
*
* ```php
* public function getOrders()
* {
* return $this->hasOne(Order::className(), ['id' => 'order_id']);
* }
*
* public function getOrderItems()
* {
* return $this->hasMany(Item::className(), ['id' => 'item_id'])
* ->via('orders', ['order_id' => 'id']);
* }
* ```
*
* @param string $relationName the relation name. This refers to a relation declared in [[primaryModel]].
* @param callable $callable a PHP callback for customizing the relation associated with the pivot table.
* Its signature should be `function($query)`, where `$query` is the query to be customized.
......@@ -87,7 +111,17 @@ trait ActiveRelationTrait
* is the "orders", and the inverse of the "orders" relation is the "customer".
* If this property is set, the primary record(s) will be referenced through the specified relation.
* For example, `$customer->orders[0]->customer` and `$customer` will be the same object,
* and accessing the customer of an order will not trigger new DB query.
* and accessing the customer of an order will not trigger a new DB query.
*
* Use this method when declaring a relation in the [[ActiveRecord]] class:
*
* ```php
* public function getOrders()
* {
* return $this->hasMany(Order::className(), ['customer_id' => 'id'])->inverseOf('customer');
* }
* ```
*
* @param string $relationName the name of the relation that is the inverse of this relation.
* @return static the relation object itself.
*/
......
......@@ -255,7 +255,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
return $this->_related[$name];
}
$value = parent::__get($name);
if ($value instanceof ActiveRelationInterface) {
if ($value instanceof ActiveQueryInterface) {
return $this->_related[$name] = $value->findFor($name, $this);
} else {
return $value;
......@@ -314,7 +314,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
/**
* Declares a `has-one` relation.
* The declaration is returned in terms of an [[ActiveRelation]] instance
* The declaration is returned in terms of a relational [[ActiveQuery]] instance
* through which the related record can be queried and retrieved back.
*
* A `has-one` relation means that there is at most one related record matching
......@@ -334,18 +334,18 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
* in the related class `Country`, while the 'country_id' value refers to an attribute name
* in the current AR class.
*
* Call methods declared in [[ActiveRelation]] to further customize the relation.
* Call methods declared in [[ActiveQuery]] to further customize the relation.
*
* @param string $class the class name of the related record
* @param array $link the primary-foreign key constraint. The keys of the array refer to
* the attributes of the record associated with the `$class` model, while the values of the
* array refer to the corresponding attributes in **this** AR class.
* @return ActiveRelationInterface the relation object.
* @return ActiveQueryInterface the relational query object.
*/
public function hasOne($class, $link)
{
/** @var ActiveRecord $class */
return $class::createRelation([
return $class::createQuery([
'modelClass' => $class,
'primaryModel' => $this,
'link' => $link,
......@@ -355,7 +355,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
/**
* Declares a `has-many` relation.
* The declaration is returned in terms of an [[ActiveRelation]] instance
* The declaration is returned in terms of a relational [[ActiveQuery]] instance
* through which the related record can be queried and retrieved back.
*
* A `has-many` relation means that there are multiple related records matching
......@@ -375,16 +375,18 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
* an attribute name in the related class `Order`, while the 'id' value refers to
* an attribute name in the current AR class.
*
* Call methods declared in [[ActiveQuery]] to further customize the relation.
*
* @param string $class the class name of the related record
* @param array $link the primary-foreign key constraint. The keys of the array refer to
* the attributes of the record associated with the `$class` model, while the values of the
* array refer to the corresponding attributes in **this** AR class.
* @return ActiveRelationInterface the relation object.
* @return ActiveQueryInterface the relational query object.
*/
public function hasMany($class, $link)
{
/** @var ActiveRecord $class */
return $class::createRelation([
return $class::createQuery([
'modelClass' => $class,
'primaryModel' => $this,
'link' => $link,
......@@ -1060,10 +1062,10 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
/**
* Returns the relation object with the specified name.
* A relation is defined by a getter method which returns an [[ActiveRelation]] object.
* A relation is defined by a getter method which returns an [[ActiveQueryInterface]] object.
* It can be declared in either the Active Record class itself or one of its behaviors.
* @param string $name the relation name
* @return ActiveRelation the relation object
* @return ActiveQueryInterface|ActiveQuery the relational query object
* @throws InvalidParamException if the named relation does not exist.
*/
public function getRelation($name)
......@@ -1075,7 +1077,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
} catch (UnknownMethodException $e) {
throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".', 0, $e);
}
if (!$relation instanceof ActiveRelationInterface) {
if (!$relation instanceof ActiveQueryInterface) {
throw new InvalidParamException(get_class($this) . ' has no relation named "' . $name . '".');
}
......@@ -1107,7 +1109,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
* @param ActiveRecord $model the model to be linked with the current one.
* @param array $extraColumns additional column values to be saved into the pivot table.
* This parameter is only meaningful for a relationship involving a pivot table
* (i.e., a relation set with `[[ActiveRelation::via()]]` or `[[ActiveRelation::viaTable()]]`.)
* (i.e., a relation set with [[ActiveRelationTrait::via()]] or `[[ActiveQuery::viaTable()]]`.)
* @throws InvalidCallException if the method is unable to link two models.
*/
public function link($name, $model, $extraColumns = [])
......@@ -1119,7 +1121,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
throw new InvalidCallException('Unable to link models: both models must NOT be newly created.');
}
if (is_array($relation->via)) {
/** @var ActiveRelation $viaRelation */
/** @var ActiveQuery $viaRelation */
list($viaName, $viaRelation) = $relation->via;
$viaClass = $viaRelation->modelClass;
// unset $viaName so that it can be reloaded to reflect the change
......@@ -1203,7 +1205,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
if ($relation->via !== null) {
if (is_array($relation->via)) {
/** @var ActiveRelation $viaRelation */
/** @var ActiveQuery $viaRelation */
list($viaName, $viaRelation) = $relation->via;
$viaClass = $viaRelation->modelClass;
unset($this->_related[$viaName]);
......
......@@ -41,8 +41,9 @@ class Customer extends ActiveRecord
parent::afterSave($insert);
}
public static function createQuery()
public static function createQuery($config = [])
{
return new CustomerQuery(['modelClass' => get_called_class()]);
$config['modelClass'] = get_called_class();
return new CustomerQuery($config);
}
}
......@@ -63,8 +63,9 @@ class Customer extends ActiveRecord
}
public static function createQuery()
public static function createQuery($config = [])
{
return new CustomerQuery(['modelClass' => get_called_class()]);
$config['modelClass'] = get_called_class();
return new CustomerQuery($config);
}
}
......@@ -25,8 +25,9 @@ class Customer extends ActiveRecord
return $this->hasMany(CustomerOrder::className(), ['customer_id' => '_id']);
}
public static function createQuery()
public static function createQuery($config = [])
{
return new CustomerQuery(['modelClass' => get_called_class()]);
$config['modelClass'] = get_called_class();
return new CustomerQuery($config);
}
}
\ No newline at end of file
......@@ -20,8 +20,9 @@ class CustomerFile extends ActiveRecord
);
}
public static function createQuery()
public static function createQuery($config = [])
{
return new CustomerFileQuery(['modelClass' => get_called_class()]);
$config['modelClass'] = get_called_class();
return new CustomerFileQuery($config);
}
}
\ No newline at end of file
......@@ -17,7 +17,7 @@ class Customer extends ActiveRecord
}
/**
* @return \yii\redis\ActiveRelation
* @return \yii\redis\ActiveQuery
*/
public function getOrders()
{
......@@ -31,8 +31,9 @@ class Customer extends ActiveRecord
parent::afterSave($insert);
}
public static function createQuery()
public static function createQuery($config = [])
{
return new CustomerQuery(['modelClass' => get_called_class()]);
$config['modelClass'] = get_called_class();
return new CustomerQuery($config);
}
}
\ No newline at end of file
......@@ -2,7 +2,7 @@
namespace yiiunit\data\ar\sphinx;
use yii\sphinx\ActiveRelation;
use yii\sphinx\ActiveQuery;
use yiiunit\data\ar\ActiveRecord as ActiveRecordDb;
class ArticleDb extends ActiveRecordDb
......@@ -20,6 +20,6 @@ class ArticleDb extends ActiveRecordDb
'link' => ['id' => 'id'],
'multiple' => false,
];
return new ActiveRelation($config);
return new ActiveQuery($config);
}
}
\ No newline at end of file
......@@ -25,8 +25,9 @@ class ArticleIndex extends ActiveRecord
return $this->source->content;
}
public static function createQuery()
public static function createQuery($config = [])
{
return new ArticleIndexQuery(['modelClass' => get_called_class()]);
$config['modelClass'] = get_called_class();
return new ArticleIndexQuery($config);
}
}
\ No newline at end of file
......@@ -492,7 +492,7 @@ trait ActiveRecordTestTrait
}
/**
* Ensure ActiveRelation does preserve order of items on find via()
* Ensure ActiveRelationTrait does preserve order of items on find via()
* https://github.com/yiisoft/yii2/issues/1310
*/
public function testFindEagerViaRelationPreserveOrder()
......
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