mirror of
https://github.com/yiisoft/yii2.git
synced 2025-11-21 00:54:53 +08:00
Merge pull request #2025 from yiisoft/remove-ability-to-declare-scopes-in-ar
Fixes #2016: removed ability to declare scopes in ActiveRecord leaving it to ActiveQuery
This commit is contained in:
@@ -547,32 +547,60 @@ Finally when calling [[delete()]] to delete an ActiveRecord, we will have the fo
|
|||||||
3. [[afterDelete()]]: will trigger an [[EVENT_AFTER_DELETE]] event
|
3. [[afterDelete()]]: will trigger an [[EVENT_AFTER_DELETE]] event
|
||||||
|
|
||||||
|
|
||||||
Scopes
|
Custom scopes
|
||||||
------
|
-------------
|
||||||
|
|
||||||
A scope is a method that customizes a given [[ActiveQuery]] object. Scope methods are static and are defined
|
When [[find()]] or [[findBySql()]] Active Record method is being called without parameters it returns an [[ActiveQuery]]
|
||||||
in the ActiveRecord classes. They can be invoked through the [[ActiveQuery]] object that is created
|
instance. This object holds all the parameters and conditions for a future query and also allows you to customize these
|
||||||
via [[find()]] or [[findBySql()]]. The following is an example:
|
using a set of methods that are called scopes. By deafault there is a good set of such methods some of which we've
|
||||||
|
already used above: `where`, `orderBy`, `limit` etc.
|
||||||
|
|
||||||
|
In many cases it is convenient to wrap extra conditions into custom scope methods. In order to do so you need two things.
|
||||||
|
First is creating a custom query class for your model. For example, a `Comment` may have a `CommentQuery`:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
class Comment extends \yii\db\ActiveRecord
|
namespace app\models;
|
||||||
{
|
|
||||||
// ...
|
|
||||||
|
|
||||||
/**
|
import yii\db\ActiveQuery;
|
||||||
* @param ActiveQuery $query
|
|
||||||
*/
|
class CommentQuery extends ActiveQuery
|
||||||
public static function active($query)
|
{
|
||||||
|
public function active($state = true)
|
||||||
{
|
{
|
||||||
$query->andWhere('status = 1');
|
$this->andWhere(['active' => $state]);
|
||||||
|
return $this;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$comments = Comment::find()->active()->all();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
In the above, the `active()` method is defined in `Comment` while we are calling it
|
Important points are:
|
||||||
through `ActiveQuery` returned by `Comment::find()`.
|
|
||||||
|
1. Class should extend from `yii\db\ActiveQuery` (or another `ActiveQuery` such as `yii\mongodb\ActiveQuery`).
|
||||||
|
2. A method should be `public` and should return `$this` in order to allow method chaining. It may accept parameters.
|
||||||
|
3. Check `ActiveQuery` methods that are very useful for modifying query conditions.
|
||||||
|
|
||||||
|
The second step is to use `CommentQuery` instead of regular `ActiveQuery` for `Comment` model:
|
||||||
|
|
||||||
|
```
|
||||||
|
namespace app\models;
|
||||||
|
|
||||||
|
use yii\db\ActiveRecord;
|
||||||
|
|
||||||
|
class Comment extends ActiveRecord
|
||||||
|
{
|
||||||
|
public static function createQuery()
|
||||||
|
{
|
||||||
|
return new CommentQuery(['modelClass' => get_called_class()]);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
That's it. Now you can use your custom scope methods:
|
||||||
|
|
||||||
|
```php
|
||||||
|
$comments = Comment::find()->active()->all();
|
||||||
|
$inactiveComments = Comment::find()->active(false)->all();
|
||||||
|
```
|
||||||
|
|
||||||
You can also use scopes when defining relations. For example,
|
You can also use scopes when defining relations. For example,
|
||||||
|
|
||||||
@@ -597,29 +625,32 @@ $posts = Post::find()->with([
|
|||||||
])->all();
|
])->all();
|
||||||
```
|
```
|
||||||
|
|
||||||
Scopes can be parameterized. For example, we can define and use the following `olderThan` scope:
|
### Making it IDE-friendly
|
||||||
|
|
||||||
|
In order to make most modern IDE autocomplete happy you need to override return types for some methods of both model
|
||||||
|
and query like the following:
|
||||||
|
|
||||||
```php
|
```php
|
||||||
class Customer extends \yii\db\ActiveRecord
|
/**
|
||||||
|
* @method \app\models\CommentQuery|static|null find($q = null) static
|
||||||
|
* @method \app\models\CommentQuery findBySql($sql, $params = []) static
|
||||||
|
*/
|
||||||
|
class Comment extends ActiveRecord
|
||||||
{
|
{
|
||||||
// ...
|
// ...
|
||||||
|
|
||||||
/**
|
|
||||||
* @param ActiveQuery $query
|
|
||||||
* @param integer $age
|
|
||||||
*/
|
|
||||||
public static function olderThan($query, $age = 30)
|
|
||||||
{
|
|
||||||
$query->andWhere('age > :age', [':age' => $age]);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
$customers = Customer::find()->olderThan(50)->all();
|
|
||||||
```
|
```
|
||||||
|
|
||||||
The parameters should follow after the `$query` parameter when defining the scope method, and they
|
```php
|
||||||
can take default values like shown above.
|
/**
|
||||||
|
* @method \app\models\Comment|array|null one($db = null)
|
||||||
|
* @method \app\models\Comment[]|array all($db = null)
|
||||||
|
*/
|
||||||
|
class CommentQuery extends ActiveQuery
|
||||||
|
{
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Transactional operations
|
Transactional operations
|
||||||
------------------------
|
------------------------
|
||||||
|
|||||||
@@ -459,6 +459,8 @@ By default, ActiveRecord now only saves dirty attributes. In 1.1, all attributes
|
|||||||
are saved to database when you call `save()`, regardless of having changed or not,
|
are saved to database when you call `save()`, regardless of having changed or not,
|
||||||
unless you explicitly list the attributes to save.
|
unless you explicitly list the attributes to save.
|
||||||
|
|
||||||
|
Scopes are now defined in a custom `ActiveQuery` class instead of model directly.
|
||||||
|
|
||||||
See [active record docs](active-record.md) for more details.
|
See [active record docs](active-record.md) for more details.
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -60,7 +60,7 @@ class ActiveQuery extends Query implements ActiveQueryInterface
|
|||||||
* Executes query and returns all results as an array.
|
* Executes query and returns all results as an array.
|
||||||
* @param Connection $db the DB connection used to create the DB command.
|
* @param Connection $db the DB connection used to create the DB command.
|
||||||
* If null, the DB connection returned by [[modelClass]] will be used.
|
* If null, the DB connection returned by [[modelClass]] will be used.
|
||||||
* @return array the query results. If the query results in nothing, an empty array will be returned.
|
* @return array|ActiveRecord[] the query results. If the query results in nothing, an empty array will be returned.
|
||||||
*/
|
*/
|
||||||
public function all($db = null)
|
public function all($db = null)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -31,32 +31,6 @@ trait ActiveQueryTrait
|
|||||||
*/
|
*/
|
||||||
public $asArray;
|
public $asArray;
|
||||||
|
|
||||||
|
|
||||||
/**
|
|
||||||
* PHP magic method.
|
|
||||||
* This method allows calling static method defined in [[modelClass]] via this query object.
|
|
||||||
* It is mainly implemented for supporting the feature of scope.
|
|
||||||
*
|
|
||||||
* @param string $name the method name to be called
|
|
||||||
* @param array $params the parameters passed to the method
|
|
||||||
* @throws \yii\base\InvalidCallException
|
|
||||||
* @return mixed the method return result
|
|
||||||
*/
|
|
||||||
public function __call($name, $params)
|
|
||||||
{
|
|
||||||
if (method_exists($this->modelClass, $name)) {
|
|
||||||
$method = new \ReflectionMethod($this->modelClass, $name);
|
|
||||||
if (!$method->isStatic() || !$method->isPublic()) {
|
|
||||||
throw new InvalidCallException("The scope method \"{$this->modelClass}::$name()\" must be public and static.");
|
|
||||||
}
|
|
||||||
array_unshift($params, $this);
|
|
||||||
call_user_func_array([$this->modelClass, $name], $params);
|
|
||||||
return $this;
|
|
||||||
} else {
|
|
||||||
return parent::__call($name, $params);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the [[asArray]] property.
|
* Sets the [[asArray]] property.
|
||||||
* @param boolean $value whether to return the query results in terms of arrays instead of Active Records.
|
* @param boolean $value whether to return the query results in terms of arrays instead of Active Records.
|
||||||
@@ -175,7 +149,7 @@ trait ActiveQueryTrait
|
|||||||
* Finds records corresponding to one or multiple relations and populates them into the primary models.
|
* Finds records corresponding to one or multiple relations and populates them into the primary models.
|
||||||
* @param array $with a list of relations that this query should be performed with. Please
|
* @param array $with a list of relations that this query should be performed with. Please
|
||||||
* refer to [[with()]] for details about specifying this parameter.
|
* refer to [[with()]] for details about specifying this parameter.
|
||||||
* @param array $models the primary models (can be either AR instances or arrays)
|
* @param array|ActiveRecord[] $models the primary models (can be either AR instances or arrays)
|
||||||
*/
|
*/
|
||||||
public function findWith($with, &$models)
|
public function findWith($with, &$models)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -102,7 +102,7 @@ abstract class BaseActiveRecord extends Model implements ActiveRecordInterface
|
|||||||
* - an array of name-value pairs: query by a set of column values and return a single record matching all of them.
|
* - an array of name-value pairs: query by a set of column values and return a single record matching all of them.
|
||||||
* - null: return a new [[ActiveQuery]] object for further query purpose.
|
* - null: return a new [[ActiveQuery]] object for further query purpose.
|
||||||
*
|
*
|
||||||
* @return ActiveQuery|ActiveRecord|null When `$q` is null, a new [[ActiveQuery]] instance
|
* @return ActiveQuery|static|null When `$q` is null, a new [[ActiveQuery]] instance
|
||||||
* is returned; when `$q` is a scalar or an array, an ActiveRecord object matching it will be
|
* is returned; when `$q` is a scalar or an array, an ActiveRecord object matching it will be
|
||||||
* returned (null will be returned if there is no matching).
|
* returned (null will be returned if there is no matching).
|
||||||
* @throws InvalidConfigException if the AR class does not have a primary key
|
* @throws InvalidConfigException if the AR class does not have a primary key
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
namespace yiiunit\data\ar;
|
namespace yiiunit\data\ar;
|
||||||
|
|
||||||
|
use yii\db\ActiveQuery;
|
||||||
use yiiunit\framework\db\ActiveRecordTest;
|
use yiiunit\framework\db\ActiveRecordTest;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -29,15 +30,15 @@ class Customer extends ActiveRecord
|
|||||||
return $this->hasMany(Order::className(), ['customer_id' => 'id'])->orderBy('id');
|
return $this->hasMany(Order::className(), ['customer_id' => 'id'])->orderBy('id');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function active($query)
|
|
||||||
{
|
|
||||||
$query->andWhere('status=1');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function afterSave($insert)
|
public function afterSave($insert)
|
||||||
{
|
{
|
||||||
ActiveRecordTest::$afterSaveInsert = $insert;
|
ActiveRecordTest::$afterSaveInsert = $insert;
|
||||||
ActiveRecordTest::$afterSaveNewRecord = $this->isNewRecord;
|
ActiveRecordTest::$afterSaveNewRecord = $this->isNewRecord;
|
||||||
parent::afterSave($insert);
|
parent::afterSave($insert);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function createQuery()
|
||||||
|
{
|
||||||
|
return new CustomerQuery(['modelClass' => get_called_class()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
tests/unit/data/ar/CustomerQuery.php
Normal file
16
tests/unit/data/ar/CustomerQuery.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
namespace yiiunit\data\ar;
|
||||||
|
use yii\db\ActiveQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CustomerQuery
|
||||||
|
*/
|
||||||
|
class CustomerQuery extends ActiveQuery
|
||||||
|
{
|
||||||
|
public function active()
|
||||||
|
{
|
||||||
|
$this->andWhere('status=1');
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -35,11 +35,6 @@ class Customer extends ActiveRecord
|
|||||||
return $this->hasMany(Order::className(), ['customer_id' => 'id'])->orderBy('created_at');
|
return $this->hasMany(Order::className(), ['customer_id' => 'id'])->orderBy('created_at');
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function active($query)
|
|
||||||
{
|
|
||||||
$query->andWhere(['status' => 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function afterSave($insert)
|
public function afterSave($insert)
|
||||||
{
|
{
|
||||||
ActiveRecordTest::$afterSaveInsert = $insert;
|
ActiveRecordTest::$afterSaveInsert = $insert;
|
||||||
@@ -67,4 +62,9 @@ class Customer extends ActiveRecord
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function createQuery()
|
||||||
|
{
|
||||||
|
return new CustomerQuery(['modelClass' => get_called_class()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
16
tests/unit/data/ar/elasticsearch/CustomerQuery.php
Normal file
16
tests/unit/data/ar/elasticsearch/CustomerQuery.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
namespace yiiunit\data\ar\elasticsearch;
|
||||||
|
use yii\elasticsearch\ActiveQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CustomerQuery
|
||||||
|
*/
|
||||||
|
class CustomerQuery extends ActiveQuery
|
||||||
|
{
|
||||||
|
public function active()
|
||||||
|
{
|
||||||
|
$this->andWhere(array('status' => 1));
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -2,6 +2,8 @@
|
|||||||
|
|
||||||
namespace yiiunit\data\ar\mongodb;
|
namespace yiiunit\data\ar\mongodb;
|
||||||
|
|
||||||
|
use yii\mongodb\ActiveQuery;
|
||||||
|
|
||||||
class Customer extends ActiveRecord
|
class Customer extends ActiveRecord
|
||||||
{
|
{
|
||||||
public static function collectionName()
|
public static function collectionName()
|
||||||
@@ -20,13 +22,13 @@ class Customer extends ActiveRecord
|
|||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function activeOnly($query)
|
|
||||||
{
|
|
||||||
$query->andWhere(['status' => 2]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getOrders()
|
public function getOrders()
|
||||||
{
|
{
|
||||||
return $this->hasMany(CustomerOrder::className(), ['customer_id' => '_id']);
|
return $this->hasMany(CustomerOrder::className(), ['customer_id' => '_id']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function createQuery()
|
||||||
|
{
|
||||||
|
return new CustomerQuery(['modelClass' => get_called_class()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
16
tests/unit/data/ar/mongodb/CustomerQuery.php
Normal file
16
tests/unit/data/ar/mongodb/CustomerQuery.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
namespace yiiunit\data\ar\mongodb;
|
||||||
|
use yii\mongodb\ActiveQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CustomerQuery
|
||||||
|
*/
|
||||||
|
class CustomerQuery extends ActiveQuery
|
||||||
|
{
|
||||||
|
public function activeOnly()
|
||||||
|
{
|
||||||
|
$this->andWhere(['status' => 2]);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -20,8 +20,8 @@ class CustomerFile extends ActiveRecord
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function activeOnly($query)
|
public static function createQuery()
|
||||||
{
|
{
|
||||||
$query->andWhere(['status' => 2]);
|
return new CustomerFileQuery(['modelClass' => get_called_class()]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
16
tests/unit/data/ar/mongodb/file/CustomerFileQuery.php
Normal file
16
tests/unit/data/ar/mongodb/file/CustomerFileQuery.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
namespace yiiunit\data\ar\mongodb\file;
|
||||||
|
use yii\mongodb\file\ActiveQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CustomerFileQuery
|
||||||
|
*/
|
||||||
|
class CustomerFileQuery extends ActiveQuery
|
||||||
|
{
|
||||||
|
public function activeOnly()
|
||||||
|
{
|
||||||
|
$this->andWhere(['status' => 2]);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace yiiunit\data\ar\redis;
|
namespace yiiunit\data\ar\redis;
|
||||||
|
|
||||||
|
use yii\redis\ActiveQuery;
|
||||||
use yiiunit\extensions\redis\ActiveRecordTest;
|
use yiiunit\extensions\redis\ActiveRecordTest;
|
||||||
|
|
||||||
class Customer extends ActiveRecord
|
class Customer extends ActiveRecord
|
||||||
@@ -24,15 +25,15 @@ class Customer extends ActiveRecord
|
|||||||
return $this->hasMany(Order::className(), ['customer_id' => 'id']);
|
return $this->hasMany(Order::className(), ['customer_id' => 'id']);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function active($query)
|
|
||||||
{
|
|
||||||
$query->andWhere(['status' => 1]);
|
|
||||||
}
|
|
||||||
|
|
||||||
public function afterSave($insert)
|
public function afterSave($insert)
|
||||||
{
|
{
|
||||||
ActiveRecordTest::$afterSaveInsert = $insert;
|
ActiveRecordTest::$afterSaveInsert = $insert;
|
||||||
ActiveRecordTest::$afterSaveNewRecord = $this->isNewRecord;
|
ActiveRecordTest::$afterSaveNewRecord = $this->isNewRecord;
|
||||||
parent::afterSave($insert);
|
parent::afterSave($insert);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function createQuery()
|
||||||
|
{
|
||||||
|
return new CustomerQuery(['modelClass' => get_called_class()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
16
tests/unit/data/ar/redis/CustomerQuery.php
Normal file
16
tests/unit/data/ar/redis/CustomerQuery.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
namespace yiiunit\data\ar\redis;
|
||||||
|
use yii\redis\ActiveQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* CustomerQuery
|
||||||
|
*/
|
||||||
|
class CustomerQuery extends ActiveQuery
|
||||||
|
{
|
||||||
|
public function active()
|
||||||
|
{
|
||||||
|
$this->andWhere(['status' => 1]);
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -1,8 +1,7 @@
|
|||||||
<?php
|
<?php
|
||||||
|
|
||||||
namespace yiiunit\data\ar\sphinx;
|
namespace yiiunit\data\ar\sphinx;
|
||||||
|
|
||||||
use yii\db\ActiveRelation;
|
use yii\sphinx\ActiveQuery;
|
||||||
|
|
||||||
class ArticleIndex extends ActiveRecord
|
class ArticleIndex extends ActiveRecord
|
||||||
{
|
{
|
||||||
@@ -13,11 +12,6 @@ class ArticleIndex extends ActiveRecord
|
|||||||
return 'yii2_test_article_index';
|
return 'yii2_test_article_index';
|
||||||
}
|
}
|
||||||
|
|
||||||
public static function favoriteAuthor($query)
|
|
||||||
{
|
|
||||||
$query->andWhere('author_id=1');
|
|
||||||
}
|
|
||||||
|
|
||||||
public function getSource()
|
public function getSource()
|
||||||
{
|
{
|
||||||
return $this->hasOne(ArticleDb::className(), ['id' => 'id']);
|
return $this->hasOne(ArticleDb::className(), ['id' => 'id']);
|
||||||
@@ -32,4 +26,9 @@ class ArticleIndex extends ActiveRecord
|
|||||||
{
|
{
|
||||||
return $this->source->content;
|
return $this->source->content;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static function createQuery()
|
||||||
|
{
|
||||||
|
return new ArticleIndexQuery(['modelClass' => get_called_class()]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
16
tests/unit/data/ar/sphinx/ArticleIndexQuery.php
Normal file
16
tests/unit/data/ar/sphinx/ArticleIndexQuery.php
Normal file
@@ -0,0 +1,16 @@
|
|||||||
|
<?php
|
||||||
|
namespace yiiunit\data\ar\sphinx;
|
||||||
|
use yii\sphinx\ActiveQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* ArticleIndexQuery
|
||||||
|
*/
|
||||||
|
class ArticleIndexQuery extends ActiveQuery
|
||||||
|
{
|
||||||
|
public function favoriteAuthor()
|
||||||
|
{
|
||||||
|
$this->andWhere('author_id=1');
|
||||||
|
return $this;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Reference in New Issue
Block a user