This commit is contained in:
2020-10-06 14:27:47 +07:00
commit 586be80cf6
16613 changed files with 3274099 additions and 0 deletions

View File

@@ -0,0 +1,203 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\data;
/**
* ActiveDataFilter allows composing a filtering condition in a format suitable for [[\yii\db\QueryInterface::where()]].
*
* @see DataFilter
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0.13
*/
class ActiveDataFilter extends DataFilter
{
/**
* @var array maps filtering condition keywords to build methods.
* These methods are used by [[buildCondition()]] to build the actual filtering conditions.
* Particular condition builder can be specified using a PHP callback. For example:
*
* ```php
* [
* 'XOR' => function (string $operator, mixed $condition) {
* //return array;
* },
* 'LIKE' => function (string $operator, mixed $condition, string $attribute) {
* //return array;
* },
* ]
* ```
*/
public $conditionBuilders = [
'AND' => 'buildConjunctionCondition',
'OR' => 'buildConjunctionCondition',
'NOT' => 'buildBlockCondition',
'<' => 'buildOperatorCondition',
'>' => 'buildOperatorCondition',
'<=' => 'buildOperatorCondition',
'>=' => 'buildOperatorCondition',
'=' => 'buildOperatorCondition',
'!=' => 'buildOperatorCondition',
'IN' => 'buildOperatorCondition',
'NOT IN' => 'buildOperatorCondition',
'LIKE' => 'buildOperatorCondition',
];
/**
* @var array map filtering operators to operators used in [[\yii\db\QueryInterface::where()]].
* The format is: `[filterOperator => queryOperator]`.
* If particular operator keyword does not appear in the map, it will be used as is.
*
* Usually the map can be left empty as filter operator names are consistent with the ones
* used in [[\yii\db\QueryInterface::where()]]. However, you may want to adjust it in some special cases.
* For example, when using PosgreSQL you may want to setup the following map:
*
* ```php
* [
* 'LIKE' => 'ILIKE'
* ]
* ```
*/
public $queryOperatorMap = [];
/**
* {@inheritdoc}
*/
protected function buildInternal()
{
$filter = $this->normalize(false);
if (empty($filter)) {
return [];
}
return $this->buildCondition($filter);
}
/**
* @param array $condition
* @return array built condition.
*/
protected function buildCondition($condition)
{
$parts = [];
foreach ($condition as $key => $value) {
if (isset($this->conditionBuilders[$key])) {
$method = $this->conditionBuilders[$key];
if (is_string($method)) {
$callback = [$this, $method];
} else {
$callback = $method;
}
} else {
$callback = [$this, 'buildAttributeCondition'];
}
$parts[] = $callback($key, $value);
}
if (!empty($parts)) {
if (count($parts) > 1) {
$parts = array_merge(['AND'], $parts);
} else {
$parts = array_shift($parts);
}
}
return $parts;
}
/**
* Builds conjunction condition, which consists of multiple independent ones.
* It covers such operators as `and` and `or`.
* @param string $operator operator keyword.
* @param mixed $condition raw condition.
* @return array actual condition.
*/
protected function buildConjunctionCondition($operator, $condition)
{
if (isset($this->queryOperatorMap[$operator])) {
$operator = $this->queryOperatorMap[$operator];
}
$result = [$operator];
foreach ($condition as $part) {
$result[] = $this->buildCondition($part);
}
return $result;
}
/**
* Builds block condition, which consists of a single condition.
* It covers such operators as `not`.
* @param string $operator operator keyword.
* @param mixed $condition raw condition.
* @return array actual condition.
*/
protected function buildBlockCondition($operator, $condition)
{
if (isset($this->queryOperatorMap[$operator])) {
$operator = $this->queryOperatorMap[$operator];
}
return [
$operator,
$this->buildCondition($condition),
];
}
/**
* Builds search condition for a particular attribute.
* @param string $attribute search attribute name.
* @param mixed $condition search condition.
* @return array actual condition.
*/
protected function buildAttributeCondition($attribute, $condition)
{
if (is_array($condition)) {
$parts = [];
foreach ($condition as $operator => $value) {
if (isset($this->operatorTypes[$operator])) {
if (isset($this->conditionBuilders[$operator])) {
$method = $this->conditionBuilders[$operator];
if (is_string($method)) {
$callback = [$this, $method];
} else {
$callback = $method;
}
$parts[] = $callback($operator, $value, $attribute);
} else {
$parts[] = $this->buildOperatorCondition($operator, $value, $attribute);
}
}
}
if (!empty($parts)) {
if (count($parts) > 1) {
return array_merge(['AND'], $parts);
}
return array_shift($parts);
}
}
return [$attribute => $this->filterAttributeValue($attribute, $condition)];
}
/**
* Builds an operator condition.
* @param string $operator operator keyword.
* @param mixed $condition attribute condition.
* @param string $attribute attribute name.
* @return array actual condition.
*/
protected function buildOperatorCondition($operator, $condition, $attribute)
{
if (isset($this->queryOperatorMap[$operator])) {
$operator = $this->queryOperatorMap[$operator];
}
return [$operator, $attribute, $this->filterAttributeValue($attribute, $condition)];
}
}

View File

@@ -0,0 +1,199 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\data;
use yii\base\InvalidConfigException;
use yii\base\Model;
use yii\db\ActiveQueryInterface;
use yii\db\Connection;
use yii\db\QueryInterface;
use yii\di\Instance;
/**
* ActiveDataProvider implements a data provider based on [[\yii\db\Query]] and [[\yii\db\ActiveQuery]].
*
* ActiveDataProvider provides data by performing DB queries using [[query]].
*
* The following is an example of using ActiveDataProvider to provide ActiveRecord instances:
*
* ```php
* $provider = new ActiveDataProvider([
* 'query' => Post::find(),
* 'pagination' => [
* 'pageSize' => 20,
* ],
* ]);
*
* // get the posts in the current page
* $posts = $provider->getModels();
* ```
*
* And the following example shows how to use ActiveDataProvider without ActiveRecord:
*
* ```php
* $query = new Query();
* $provider = new ActiveDataProvider([
* 'query' => $query->from('post'),
* 'pagination' => [
* 'pageSize' => 20,
* ],
* ]);
*
* // get the posts in the current page
* $posts = $provider->getModels();
* ```
*
* For more details and usage information on ActiveDataProvider, see the [guide article on data providers](guide:output-data-providers).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ActiveDataProvider extends BaseDataProvider
{
/**
* @var QueryInterface the query that is used to fetch data models and [[totalCount]]
* if it is not explicitly set.
*/
public $query;
/**
* @var string|callable the column that is used as the key of the data models.
* This can be either a column name, or a callable that returns the key value of a given data model.
*
* If this is not set, the following rules will be used to determine the keys of the data models:
*
* - If [[query]] is an [[\yii\db\ActiveQuery]] instance, the primary keys of [[\yii\db\ActiveQuery::modelClass]] will be used.
* - Otherwise, the keys of the [[models]] array will be used.
*
* @see getKeys()
*/
public $key;
/**
* @var Connection|array|string the DB connection object or the application component ID of the DB connection.
* If not set, the default DB connection will be used.
* Starting from version 2.0.2, this can also be a configuration array for creating the object.
*/
public $db;
/**
* Initializes the DB connection component.
* This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
* @throws InvalidConfigException if [[db]] is invalid.
*/
public function init()
{
parent::init();
if (is_string($this->db)) {
$this->db = Instance::ensure($this->db, Connection::className());
}
}
/**
* {@inheritdoc}
*/
protected function prepareModels()
{
if (!$this->query instanceof QueryInterface) {
throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.');
}
$query = clone $this->query;
if (($pagination = $this->getPagination()) !== false) {
$pagination->totalCount = $this->getTotalCount();
if ($pagination->totalCount === 0) {
return [];
}
$query->limit($pagination->getLimit())->offset($pagination->getOffset());
}
if (($sort = $this->getSort()) !== false) {
$query->addOrderBy($sort->getOrders());
}
return $query->all($this->db);
}
/**
* {@inheritdoc}
*/
protected function prepareKeys($models)
{
$keys = [];
if ($this->key !== null) {
foreach ($models as $model) {
if (is_string($this->key)) {
$keys[] = $model[$this->key];
} else {
$keys[] = call_user_func($this->key, $model);
}
}
return $keys;
} elseif ($this->query instanceof ActiveQueryInterface) {
/* @var $class \yii\db\ActiveRecordInterface */
$class = $this->query->modelClass;
$pks = $class::primaryKey();
if (count($pks) === 1) {
$pk = $pks[0];
foreach ($models as $model) {
$keys[] = $model[$pk];
}
} else {
foreach ($models as $model) {
$kk = [];
foreach ($pks as $pk) {
$kk[$pk] = $model[$pk];
}
$keys[] = $kk;
}
}
return $keys;
}
return array_keys($models);
}
/**
* {@inheritdoc}
*/
protected function prepareTotalCount()
{
if (!$this->query instanceof QueryInterface) {
throw new InvalidConfigException('The "query" property must be an instance of a class that implements the QueryInterface e.g. yii\db\Query or its subclasses.');
}
$query = clone $this->query;
return (int) $query->limit(-1)->offset(-1)->orderBy([])->count('*', $this->db);
}
/**
* {@inheritdoc}
*/
public function setSort($value)
{
parent::setSort($value);
if (($sort = $this->getSort()) !== false && $this->query instanceof ActiveQueryInterface) {
/* @var $modelClass Model */
$modelClass = $this->query->modelClass;
$model = $modelClass::instance();
if (empty($sort->attributes)) {
foreach ($model->attributes() as $attribute) {
$sort->attributes[$attribute] = [
'asc' => [$attribute => SORT_ASC],
'desc' => [$attribute => SORT_DESC],
'label' => $model->getAttributeLabel($attribute),
];
}
} else {
foreach ($sort->attributes as $attribute => $config) {
if (!isset($config['label'])) {
$sort->attributes[$attribute]['label'] = $model->getAttributeLabel($attribute);
}
}
}
}
}
}

View File

@@ -0,0 +1,144 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\data;
use yii\helpers\ArrayHelper;
/**
* ArrayDataProvider implements a data provider based on a data array.
*
* The [[allModels]] property contains all data models that may be sorted and/or paginated.
* ArrayDataProvider will provide the data after sorting and/or pagination.
* You may configure the [[sort]] and [[pagination]] properties to
* customize the sorting and pagination behaviors.
*
* Elements in the [[allModels]] array may be either objects (e.g. model objects)
* or associative arrays (e.g. query results of DAO).
* Make sure to set the [[key]] property to the name of the field that uniquely
* identifies a data record or false if you do not have such a field.
*
* Compared to [[ActiveDataProvider]], ArrayDataProvider could be less efficient
* because it needs to have [[allModels]] ready.
*
* ArrayDataProvider may be used in the following way:
*
* ```php
* $query = new Query;
* $provider = new ArrayDataProvider([
* 'allModels' => $query->from('post')->all(),
* 'sort' => [
* 'attributes' => ['id', 'username', 'email'],
* ],
* 'pagination' => [
* 'pageSize' => 10,
* ],
* ]);
* // get the posts in the current page
* $posts = $provider->getModels();
* ```
*
* Note: if you want to use the sorting feature, you must configure the [[sort]] property
* so that the provider knows which columns can be sorted.
*
* For more details and usage information on ArrayDataProvider, see the [guide article on data providers](guide:output-data-providers).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class ArrayDataProvider extends BaseDataProvider
{
/**
* @var string|callable the column that is used as the key of the data models.
* This can be either a column name, or a callable that returns the key value of a given data model.
* If this is not set, the index of the [[models]] array will be used.
* @see getKeys()
*/
public $key;
/**
* @var array the data that is not paginated or sorted. When pagination is enabled,
* this property usually contains more elements than [[models]].
* The array elements must use zero-based integer keys.
*/
public $allModels;
/**
* @var string the name of the [[\yii\base\Model|Model]] class that will be represented.
* This property is used to get columns' names.
* @since 2.0.9
*/
public $modelClass;
/**
* {@inheritdoc}
*/
protected function prepareModels()
{
if (($models = $this->allModels) === null) {
return [];
}
if (($sort = $this->getSort()) !== false) {
$models = $this->sortModels($models, $sort);
}
if (($pagination = $this->getPagination()) !== false) {
$pagination->totalCount = $this->getTotalCount();
if ($pagination->getPageSize() > 0) {
$models = array_slice($models, $pagination->getOffset(), $pagination->getLimit(), true);
}
}
return $models;
}
/**
* {@inheritdoc}
*/
protected function prepareKeys($models)
{
if ($this->key !== null) {
$keys = [];
foreach ($models as $model) {
if (is_string($this->key)) {
$keys[] = $model[$this->key];
} else {
$keys[] = call_user_func($this->key, $model);
}
}
return $keys;
}
return array_keys($models);
}
/**
* {@inheritdoc}
*/
protected function prepareTotalCount()
{
return count($this->allModels);
}
/**
* Sorts the data models according to the given sort definition.
* @param array $models the models to be sorted
* @param Sort $sort the sort definition
* @return array the sorted data models
*/
protected function sortModels($models, $sort)
{
$orders = $sort->getOrders();
if (!empty($orders)) {
ArrayHelper::multisort($models, array_keys($orders), array_values($orders));
}
return $models;
}
}

View File

@@ -0,0 +1,278 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\data;
use Yii;
use yii\base\Component;
use yii\base\InvalidArgumentException;
/**
* BaseDataProvider provides a base class that implements the [[DataProviderInterface]].
*
* For more details and usage information on BaseDataProvider, see the [guide article on data providers](guide:output-data-providers).
*
* @property int $count The number of data models in the current page. This property is read-only.
* @property array $keys The list of key values corresponding to [[models]]. Each data model in [[models]] is
* uniquely identified by the corresponding key value in this array.
* @property array $models The list of data models in the current page.
* @property Pagination|false $pagination The pagination object. If this is false, it means the pagination is
* disabled. Note that the type of this property differs in getter and setter. See [[getPagination()]] and
* [[setPagination()]] for details.
* @property Sort|bool $sort The sorting object. If this is false, it means the sorting is disabled. Note that
* the type of this property differs in getter and setter. See [[getSort()]] and [[setSort()]] for details.
* @property int $totalCount Total number of possible data models.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
abstract class BaseDataProvider extends Component implements DataProviderInterface
{
/**
* @var int Number of data providers on the current page. Used to generate unique IDs.
*/
private static $counter = 0;
/**
* @var string an ID that uniquely identifies the data provider among all data providers.
* Generated automatically the following way in case it is not set:
*
* - First data provider ID is empty.
* - Second and all subsequent data provider IDs are: "dp-1", "dp-2", etc.
*/
public $id;
private $_sort;
private $_pagination;
private $_keys;
private $_models;
private $_totalCount;
/**
* {@inheritdoc}
*/
public function init()
{
parent::init();
if ($this->id === null) {
if (self::$counter > 0) {
$this->id = 'dp-' . self::$counter;
}
self::$counter++;
}
}
/**
* Prepares the data models that will be made available in the current page.
* @return array the available data models
*/
abstract protected function prepareModels();
/**
* Prepares the keys associated with the currently available data models.
* @param array $models the available data models
* @return array the keys
*/
abstract protected function prepareKeys($models);
/**
* Returns a value indicating the total number of data models in this data provider.
* @return int total number of data models in this data provider.
*/
abstract protected function prepareTotalCount();
/**
* Prepares the data models and keys.
*
* This method will prepare the data models and keys that can be retrieved via
* [[getModels()]] and [[getKeys()]].
*
* This method will be implicitly called by [[getModels()]] and [[getKeys()]] if it has not been called before.
*
* @param bool $forcePrepare whether to force data preparation even if it has been done before.
*/
public function prepare($forcePrepare = false)
{
if ($forcePrepare || $this->_models === null) {
$this->_models = $this->prepareModels();
}
if ($forcePrepare || $this->_keys === null) {
$this->_keys = $this->prepareKeys($this->_models);
}
}
/**
* Returns the data models in the current page.
* @return array the list of data models in the current page.
*/
public function getModels()
{
$this->prepare();
return $this->_models;
}
/**
* Sets the data models in the current page.
* @param array $models the models in the current page
*/
public function setModels($models)
{
$this->_models = $models;
}
/**
* Returns the key values associated with the data models.
* @return array the list of key values corresponding to [[models]]. Each data model in [[models]]
* is uniquely identified by the corresponding key value in this array.
*/
public function getKeys()
{
$this->prepare();
return $this->_keys;
}
/**
* Sets the key values associated with the data models.
* @param array $keys the list of key values corresponding to [[models]].
*/
public function setKeys($keys)
{
$this->_keys = $keys;
}
/**
* Returns the number of data models in the current page.
* @return int the number of data models in the current page.
*/
public function getCount()
{
return count($this->getModels());
}
/**
* Returns the total number of data models.
* When [[pagination]] is false, this returns the same value as [[count]].
* Otherwise, it will call [[prepareTotalCount()]] to get the count.
* @return int total number of possible data models.
*/
public function getTotalCount()
{
if ($this->getPagination() === false) {
return $this->getCount();
} elseif ($this->_totalCount === null) {
$this->_totalCount = $this->prepareTotalCount();
}
return $this->_totalCount;
}
/**
* Sets the total number of data models.
* @param int $value the total number of data models.
*/
public function setTotalCount($value)
{
$this->_totalCount = $value;
}
/**
* Returns the pagination object used by this data provider.
* Note that you should call [[prepare()]] or [[getModels()]] first to get correct values
* of [[Pagination::totalCount]] and [[Pagination::pageCount]].
* @return Pagination|false the pagination object. If this is false, it means the pagination is disabled.
*/
public function getPagination()
{
if ($this->_pagination === null) {
$this->setPagination([]);
}
return $this->_pagination;
}
/**
* Sets the pagination for this data provider.
* @param array|Pagination|bool $value the pagination to be used by this data provider.
* This can be one of the following:
*
* - a configuration array for creating the pagination object. The "class" element defaults
* to 'yii\data\Pagination'
* - an instance of [[Pagination]] or its subclass
* - false, if pagination needs to be disabled.
*
* @throws InvalidArgumentException
*/
public function setPagination($value)
{
if (is_array($value)) {
$config = ['class' => Pagination::className()];
if ($this->id !== null) {
$config['pageParam'] = $this->id . '-page';
$config['pageSizeParam'] = $this->id . '-per-page';
}
$this->_pagination = Yii::createObject(array_merge($config, $value));
} elseif ($value instanceof Pagination || $value === false) {
$this->_pagination = $value;
} else {
throw new InvalidArgumentException('Only Pagination instance, configuration array or false is allowed.');
}
}
/**
* Returns the sorting object used by this data provider.
* @return Sort|bool the sorting object. If this is false, it means the sorting is disabled.
*/
public function getSort()
{
if ($this->_sort === null) {
$this->setSort([]);
}
return $this->_sort;
}
/**
* Sets the sort definition for this data provider.
* @param array|Sort|bool $value the sort definition to be used by this data provider.
* This can be one of the following:
*
* - a configuration array for creating the sort definition object. The "class" element defaults
* to 'yii\data\Sort'
* - an instance of [[Sort]] or its subclass
* - false, if sorting needs to be disabled.
*
* @throws InvalidArgumentException
*/
public function setSort($value)
{
if (is_array($value)) {
$config = ['class' => Sort::className()];
if ($this->id !== null) {
$config['sortParam'] = $this->id . '-sort';
}
$this->_sort = Yii::createObject(array_merge($config, $value));
} elseif ($value instanceof Sort || $value === false) {
$this->_sort = $value;
} else {
throw new InvalidArgumentException('Only Sort instance, configuration array or false is allowed.');
}
}
/**
* Refreshes the data provider.
* After calling this method, if [[getModels()]], [[getKeys()]] or [[getTotalCount()]] is called again,
* they will re-execute the query and return the latest data available.
*/
public function refresh()
{
$this->_totalCount = null;
$this->_models = null;
$this->_keys = null;
}
}

834
vendor/yiisoft/yii2/data/DataFilter.php vendored Normal file
View File

@@ -0,0 +1,834 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\data;
use Yii;
use yii\base\InvalidConfigException;
use yii\base\Model;
use yii\helpers\ArrayHelper;
use yii\validators\BooleanValidator;
use yii\validators\EachValidator;
use yii\validators\NumberValidator;
use yii\validators\StringValidator;
use yii\validators\DateValidator;
use yii\validators\Validator;
/**
* DataFilter is a special [[Model]] for processing query filtering specification.
* It allows validating and building a filter condition passed via request.
*
* Filter example:
*
* ```json
* {
* "or": [
* {
* "and": [
* {
* "name": "some name",
* },
* {
* "price": "25",
* }
* ]
* },
* {
* "id": {"in": [2, 5, 9]},
* "price": {
* "gt": 10,
* "lt": 50
* }
* }
* ]
* }
* ```
*
* In the request the filter should be specified using a key name equal to [[filterAttributeName]]. Thus actual HTTP request body
* will look like following:
*
* ```json
* {
* "filter": {"or": {...}},
* "page": 2,
* ...
* }
* ```
*
* Raw filter value should be assigned to [[filter]] property of the model.
* You may populate it from request data via [[load()]] method:
*
* ```php
* use yii\data\DataFilter;
*
* $dataFilter = new DataFilter();
* $dataFilter->load(Yii::$app->request->getBodyParams());
* ```
*
* In order to function this class requires a search model specified via [[searchModel]]. This search model should declare
* all available search attributes and their validation rules. For example:
*
* ```php
* class SearchModel extends \yii\base\Model
* {
* public $id;
* public $name;
*
* public function rules()
* {
* return [
* [['id', 'name'], 'trim'],
* ['id', 'integer'],
* ['name', 'string'],
* ];
* }
* }
* ```
*
* In order to reduce amount of classes, you may use [[\yii\base\DynamicModel]] instance as a [[searchModel]].
* In this case you should specify [[searchModel]] using a PHP callable:
*
* ```php
* function () {
* return (new \yii\base\DynamicModel(['id' => null, 'name' => null]))
* ->addRule(['id', 'name'], 'trim')
* ->addRule('id', 'integer')
* ->addRule('name', 'string');
* }
* ```
*
* You can use [[validate()]] method to check if filter value is valid. If validation fails you can use
* [[getErrors()]] to get actual error messages.
*
* In order to acquire filter condition suitable for fetching data use [[build()]] method.
*
* > Note: This is a base class. Its implementation of [[build()]] simply returns normalized [[filter]] value.
* In order to convert filter to particular format you should use descendant of this class that implements
* [[buildInternal()]] method accordingly.
*
* @see ActiveDataFilter
*
* @property array $errorMessages Error messages in format `[errorKey => message]`. Note that the type of this
* property differs in getter and setter. See [[getErrorMessages()]] and [[setErrorMessages()]] for details.
* @property mixed $filter Raw filter value.
* @property array $searchAttributeTypes Search attribute type map. Note that the type of this property
* differs in getter and setter. See [[getSearchAttributeTypes()]] and [[setSearchAttributeTypes()]] for details.
* @property Model $searchModel Model instance. Note that the type of this property differs in getter and
* setter. See [[getSearchModel()]] and [[setSearchModel()]] for details.
*
* @author Paul Klimov <klimov.paul@gmail.com>
* @since 2.0.13
*/
class DataFilter extends Model
{
const TYPE_INTEGER = 'integer';
const TYPE_FLOAT = 'float';
const TYPE_BOOLEAN = 'boolean';
const TYPE_STRING = 'string';
const TYPE_ARRAY = 'array';
const TYPE_DATETIME = 'datetime';
const TYPE_DATE = 'date';
const TYPE_TIME = 'time';
/**
* @var string name of the attribute that handles filter value.
* The name is used to load data via [[load()]] method.
*/
public $filterAttributeName = 'filter';
/**
* @var string label for the filter attribute specified via [[filterAttributeName]].
* It will be used during error messages composition.
*/
public $filterAttributeLabel;
/**
* @var array keywords or expressions that could be used in a filter.
* Array keys are the expressions used in raw filter value obtained from user request.
* Array values are internal build keys used in this class methods.
*
* Any unspecified keyword will not be recognized as a filter control and will be treated as
* an attribute name. Thus you should avoid conflicts between control keywords and attribute names.
* For example: in case you have control keyword 'like' and an attribute named 'like', specifying condition
* for such attribute will be impossible.
*
* You may specify several keywords for the same filter build key, creating multiple aliases. For example:
*
* ```php
* [
* 'eq' => '=',
* '=' => '=',
* '==' => '=',
* '===' => '=',
* // ...
* ]
* ```
*
* > Note: while specifying filter controls take actual data exchange format, which your API uses, in mind.
* > Make sure each specified control keyword is valid for the format. For example, in XML tag name can start
* > only with a letter character, thus controls like `>`, '=' or `$gt` will break the XML schema.
*/
public $filterControls = [
'and' => 'AND',
'or' => 'OR',
'not' => 'NOT',
'lt' => '<',
'gt' => '>',
'lte' => '<=',
'gte' => '>=',
'eq' => '=',
'neq' => '!=',
'in' => 'IN',
'nin' => 'NOT IN',
'like' => 'LIKE',
];
/**
* @var array maps filter condition keywords to validation methods.
* These methods are used by [[validateCondition()]] to validate raw filter conditions.
*/
public $conditionValidators = [
'AND' => 'validateConjunctionCondition',
'OR' => 'validateConjunctionCondition',
'NOT' => 'validateBlockCondition',
'<' => 'validateOperatorCondition',
'>' => 'validateOperatorCondition',
'<=' => 'validateOperatorCondition',
'>=' => 'validateOperatorCondition',
'=' => 'validateOperatorCondition',
'!=' => 'validateOperatorCondition',
'IN' => 'validateOperatorCondition',
'NOT IN' => 'validateOperatorCondition',
'LIKE' => 'validateOperatorCondition',
];
/**
* @var array specifies the list of supported search attribute types per each operator.
* This field should be in format: 'operatorKeyword' => ['type1', 'type2' ...].
* Supported types list can be specified as `*`, which indicates that operator supports all types available.
* Any unspecified keyword will not be considered as a valid operator.
*/
public $operatorTypes = [
'<' => [self::TYPE_INTEGER, self::TYPE_FLOAT, self::TYPE_DATETIME, self::TYPE_DATE, self::TYPE_TIME],
'>' => [self::TYPE_INTEGER, self::TYPE_FLOAT, self::TYPE_DATETIME, self::TYPE_DATE, self::TYPE_TIME],
'<=' => [self::TYPE_INTEGER, self::TYPE_FLOAT, self::TYPE_DATETIME, self::TYPE_DATE, self::TYPE_TIME],
'>=' => [self::TYPE_INTEGER, self::TYPE_FLOAT, self::TYPE_DATETIME, self::TYPE_DATE, self::TYPE_TIME],
'=' => '*',
'!=' => '*',
'IN' => '*',
'NOT IN' => '*',
'LIKE' => [self::TYPE_STRING],
];
/**
* @var array list of operators keywords, which should accept multiple values.
*/
public $multiValueOperators = [
'IN',
'NOT IN',
];
/**
* @var array actual attribute names to be used in searched condition, in format: [filterAttribute => actualAttribute].
* For example, in case of using table joins in the search query, attribute map may look like the following:
*
* ```php
* [
* 'authorName' => '{{author}}.[[name]]'
* ]
* ```
*
* Attribute map will be applied to filter condition in [[normalize()]] method.
*/
public $attributeMap = [];
/**
* @var array|\Closure list of error messages responding to invalid filter structure, in format: `[errorKey => message]`.
*/
private $_errorMessages;
/**
* @var mixed raw filter specification.
*/
private $_filter;
/**
* @var Model|array|string|callable model to be used for filter attributes validation.
*/
private $_searchModel;
/**
* @var array list of search attribute types in format: attributeName => type
*/
private $_searchAttributeTypes;
/**
* @return mixed raw filter value.
*/
public function getFilter()
{
return $this->_filter;
}
/**
* @param mixed $filter raw filter value.
*/
public function setFilter($filter)
{
$this->_filter = $filter;
}
/**
* @return Model model instance.
* @throws InvalidConfigException on invalid configuration.
*/
public function getSearchModel()
{
if (!is_object($this->_searchModel) || $this->_searchModel instanceof \Closure) {
$model = Yii::createObject($this->_searchModel);
if (!$model instanceof Model) {
throw new InvalidConfigException('`' . get_class($this) . '::$searchModel` should be an instance of `' . Model::className() . '` or its DI compatible configuration.');
}
$this->_searchModel = $model;
}
return $this->_searchModel;
}
/**
* @param Model|array|string|callable $model model instance or its DI compatible configuration.
* @throws InvalidConfigException on invalid configuration.
*/
public function setSearchModel($model)
{
if (is_object($model) && !$model instanceof Model && !$model instanceof \Closure) {
throw new InvalidConfigException('`' . get_class($this) . '::$searchModel` should be an instance of `' . Model::className() . '` or its DI compatible configuration.');
}
$this->_searchModel = $model;
}
/**
* @return array search attribute type map.
*/
public function getSearchAttributeTypes()
{
if ($this->_searchAttributeTypes === null) {
$this->_searchAttributeTypes = $this->detectSearchAttributeTypes();
}
return $this->_searchAttributeTypes;
}
/**
* @param array|null $searchAttributeTypes search attribute type map.
*/
public function setSearchAttributeTypes($searchAttributeTypes)
{
$this->_searchAttributeTypes = $searchAttributeTypes;
}
/**
* Composes default value for [[searchAttributeTypes]] from the [[searchModel]] validation rules.
* @return array attribute type map.
*/
protected function detectSearchAttributeTypes()
{
$model = $this->getSearchModel();
$attributeTypes = [];
foreach ($model->activeAttributes() as $attribute) {
$attributeTypes[$attribute] = self::TYPE_STRING;
}
foreach ($model->getValidators() as $validator) {
$type = $this->detectSearchAttributeType($validator);
if ($type !== null) {
foreach ((array) $validator->attributes as $attribute) {
$attributeTypes[$attribute] = $type;
}
}
}
return $attributeTypes;
}
/**
* Detect attribute type from given validator.
*
* @param Validator validator from which to detect attribute type.
* @return string|null detected attribute type.
* @since 2.0.14
*/
protected function detectSearchAttributeType(Validator $validator)
{
if ($validator instanceof BooleanValidator) {
return self::TYPE_BOOLEAN;
}
if ($validator instanceof NumberValidator) {
return $validator->integerOnly ? self::TYPE_INTEGER : self::TYPE_FLOAT;
}
if ($validator instanceof StringValidator) {
return self::TYPE_STRING;
}
if ($validator instanceof EachValidator) {
return self::TYPE_ARRAY;
}
if ($validator instanceof DateValidator) {
if ($validator->type == DateValidator::TYPE_DATETIME) {
return self::TYPE_DATETIME;
}
if ($validator->type == DateValidator::TYPE_TIME) {
return self::TYPE_TIME;
}
return self::TYPE_DATE;
}
}
/**
* @return array error messages in format `[errorKey => message]`.
*/
public function getErrorMessages()
{
if (!is_array($this->_errorMessages)) {
if ($this->_errorMessages === null) {
$this->_errorMessages = $this->defaultErrorMessages();
} else {
$this->_errorMessages = array_merge(
$this->defaultErrorMessages(),
call_user_func($this->_errorMessages)
);
}
}
return $this->_errorMessages;
}
/**
* Sets the list of error messages responding to invalid filter structure, in format: `[errorKey => message]`.
* Message may contain placeholders that will be populated depending on the message context.
* For each message a `{filter}` placeholder is available referring to the label for [[filterAttributeName]] attribute.
* @param array|\Closure $errorMessages error messages in `[errorKey => message]` format, or a PHP callback returning them.
*/
public function setErrorMessages($errorMessages)
{
if (is_array($errorMessages)) {
$errorMessages = array_merge($this->defaultErrorMessages(), $errorMessages);
}
$this->_errorMessages = $errorMessages;
}
/**
* Returns default values for [[errorMessages]].
* @return array default error messages in `[errorKey => message]` format.
*/
protected function defaultErrorMessages()
{
return [
'invalidFilter' => Yii::t('yii', 'The format of {filter} is invalid.'),
'operatorRequireMultipleOperands' => Yii::t('yii', 'Operator "{operator}" requires multiple operands.'),
'unknownAttribute' => Yii::t('yii', 'Unknown filter attribute "{attribute}"'),
'invalidAttributeValueFormat' => Yii::t('yii', 'Condition for "{attribute}" should be either a value or valid operator specification.'),
'operatorRequireAttribute' => Yii::t('yii', 'Operator "{operator}" must be used with a search attribute.'),
'unsupportedOperatorType' => Yii::t('yii', '"{attribute}" does not support operator "{operator}".'),
];
}
/**
* Parses content of the message from [[errorMessages]], specified by message key.
* @param string $messageKey message key.
* @param array $params params to be parsed into the message.
* @return string composed message string.
*/
protected function parseErrorMessage($messageKey, $params = [])
{
$messages = $this->getErrorMessages();
if (isset($messages[$messageKey])) {
$message = $messages[$messageKey];
} else {
$message = Yii::t('yii', 'The format of {filter} is invalid.');
}
$params = array_merge(
[
'filter' => $this->getAttributeLabel($this->filterAttributeName),
],
$params
);
return Yii::$app->getI18n()->format($message, $params, Yii::$app->language);
}
// Model specific:
/**
* {@inheritdoc}
*/
public function attributes()
{
return [
$this->filterAttributeName,
];
}
/**
* {@inheritdoc}
*/
public function formName()
{
return '';
}
/**
* {@inheritdoc}
*/
public function rules()
{
return [
[$this->filterAttributeName, 'validateFilter', 'skipOnEmpty' => false],
];
}
/**
* {@inheritdoc}
*/
public function attributeLabels()
{
return [
$this->filterAttributeName => $this->filterAttributeLabel,
];
}
// Validation:
/**
* Validates filter attribute value to match filer condition specification.
*/
public function validateFilter()
{
$value = $this->getFilter();
if ($value !== null) {
$this->validateCondition($value);
}
}
/**
* Validates filter condition.
* @param mixed $condition raw filter condition.
*/
protected function validateCondition($condition)
{
if (!is_array($condition)) {
$this->addError($this->filterAttributeName, $this->parseErrorMessage('invalidFilter'));
return;
}
if (empty($condition)) {
return;
}
foreach ($condition as $key => $value) {
$method = 'validateAttributeCondition';
if (isset($this->filterControls[$key])) {
$controlKey = $this->filterControls[$key];
if (isset($this->conditionValidators[$controlKey])) {
$method = $this->conditionValidators[$controlKey];
}
}
$this->$method($key, $value);
}
}
/**
* Validates conjunction condition that consists of multiple independent ones.
* This covers such operators as `and` and `or`.
* @param string $operator raw operator control keyword.
* @param mixed $condition raw condition.
*/
protected function validateConjunctionCondition($operator, $condition)
{
if (!is_array($condition) || !ArrayHelper::isIndexed($condition)) {
$this->addError($this->filterAttributeName, $this->parseErrorMessage('operatorRequireMultipleOperands', ['operator' => $operator]));
return;
}
foreach ($condition as $part) {
$this->validateCondition($part);
}
}
/**
* Validates block condition that consists of a single condition.
* This covers such operators as `not`.
* @param string $operator raw operator control keyword.
* @param mixed $condition raw condition.
*/
protected function validateBlockCondition($operator, $condition)
{
$this->validateCondition($condition);
}
/**
* Validates search condition for a particular attribute.
* @param string $attribute search attribute name.
* @param mixed $condition search condition.
*/
protected function validateAttributeCondition($attribute, $condition)
{
$attributeTypes = $this->getSearchAttributeTypes();
if (!isset($attributeTypes[$attribute])) {
$this->addError($this->filterAttributeName, $this->parseErrorMessage('unknownAttribute', ['attribute' => $attribute]));
return;
}
if (is_array($condition)) {
$operatorCount = 0;
foreach ($condition as $rawOperator => $value) {
if (isset($this->filterControls[$rawOperator])) {
$operator = $this->filterControls[$rawOperator];
if (isset($this->operatorTypes[$operator])) {
$operatorCount++;
$this->validateOperatorCondition($rawOperator, $value, $attribute);
}
}
}
if ($operatorCount > 0) {
if ($operatorCount < count($condition)) {
$this->addError($this->filterAttributeName, $this->parseErrorMessage('invalidAttributeValueFormat', ['attribute' => $attribute]));
}
} else {
// attribute may allow array value:
$this->validateAttributeValue($attribute, $condition);
}
} else {
$this->validateAttributeValue($attribute, $condition);
}
}
/**
* Validates operator condition.
* @param string $operator raw operator control keyword.
* @param mixed $condition attribute condition.
* @param string $attribute attribute name.
*/
protected function validateOperatorCondition($operator, $condition, $attribute = null)
{
if ($attribute === null) {
// absence of an attribute indicates that operator has been placed in a wrong position
$this->addError($this->filterAttributeName, $this->parseErrorMessage('operatorRequireAttribute', ['operator' => $operator]));
return;
}
$internalOperator = $this->filterControls[$operator];
// check operator type :
$operatorTypes = $this->operatorTypes[$internalOperator];
if ($operatorTypes !== '*') {
$attributeTypes = $this->getSearchAttributeTypes();
$attributeType = $attributeTypes[$attribute];
if (!in_array($attributeType, $operatorTypes, true)) {
$this->addError($this->filterAttributeName, $this->parseErrorMessage('unsupportedOperatorType', ['attribute' => $attribute, 'operator' => $operator]));
return;
}
}
if (in_array($internalOperator, $this->multiValueOperators, true)) {
// multi-value operator:
if (!is_array($condition)) {
$this->addError($this->filterAttributeName, $this->parseErrorMessage('operatorRequireMultipleOperands', ['operator' => $operator]));
} else {
foreach ($condition as $v) {
$this->validateAttributeValue($attribute, $v);
}
}
} else {
// single-value operator :
$this->validateAttributeValue($attribute, $condition);
}
}
/**
* Validates attribute value in the scope of [[model]].
* @param string $attribute attribute name.
* @param mixed $value attribute value.
*/
protected function validateAttributeValue($attribute, $value)
{
$model = $this->getSearchModel();
if (!$model->isAttributeSafe($attribute)) {
$this->addError($this->filterAttributeName, $this->parseErrorMessage('unknownAttribute', ['attribute' => $attribute]));
return;
}
$model->{$attribute} = $value;
if (!$model->validate([$attribute])) {
$this->addError($this->filterAttributeName, $model->getFirstError($attribute));
return;
}
}
/**
* Validates attribute value in the scope of [[searchModel]], applying attribute value filters if any.
* @param string $attribute attribute name.
* @param mixed $value attribute value.
* @return mixed filtered attribute value.
*/
protected function filterAttributeValue($attribute, $value)
{
$model = $this->getSearchModel();
if (!$model->isAttributeSafe($attribute)) {
$this->addError($this->filterAttributeName, $this->parseErrorMessage('unknownAttribute', ['attribute' => $attribute]));
return $value;
}
$model->{$attribute} = $value;
if (!$model->validate([$attribute])) {
$this->addError($this->filterAttributeName, $model->getFirstError($attribute));
return $value;
}
return $model->{$attribute};
}
// Build:
/**
* Builds actual filter specification form [[filter]] value.
* @param bool $runValidation whether to perform validation (calling [[validate()]])
* before building the filter. Defaults to `true`. If the validation fails, no filter will
* be built and this method will return `false`.
* @return mixed|false built actual filter value, or `false` if validation fails.
*/
public function build($runValidation = true)
{
if ($runValidation && !$this->validate()) {
return false;
}
return $this->buildInternal();
}
/**
* Performs actual filter build.
* By default this method returns result of [[normalize()]].
* The child class may override this method providing more specific implementation.
* @return mixed built actual filter value.
*/
protected function buildInternal()
{
return $this->normalize(false);
}
/**
* Normalizes filter value, replacing raw keys according to [[filterControls]] and [[attributeMap]].
* @param bool $runValidation whether to perform validation (calling [[validate()]])
* before normalizing the filter. Defaults to `true`. If the validation fails, no filter will
* be processed and this method will return `false`.
* @return array|bool normalized filter value, or `false` if validation fails.
*/
public function normalize($runValidation = true)
{
if ($runValidation && !$this->validate()) {
return false;
}
$filter = $this->getFilter();
if (!is_array($filter) || empty($filter)) {
return [];
}
return $this->normalizeComplexFilter($filter);
}
/**
* Normalizes complex filter recursively.
* @param array $filter raw filter.
* @return array normalized filter.
*/
private function normalizeComplexFilter(array $filter)
{
$result = [];
foreach ($filter as $key => $value) {
if (isset($this->filterControls[$key])) {
$key = $this->filterControls[$key];
} elseif (isset($this->attributeMap[$key])) {
$key = $this->attributeMap[$key];
}
if (is_array($value)) {
$result[$key] = $this->normalizeComplexFilter($value);
} else {
$result[$key] = $value;
}
}
return $result;
}
// Property access:
/**
* {@inheritdoc}
*/
public function canGetProperty($name, $checkVars = true, $checkBehaviors = true)
{
if ($name === $this->filterAttributeName) {
return true;
}
return parent::canGetProperty($name, $checkVars, $checkBehaviors);
}
/**
* {@inheritdoc}
*/
public function canSetProperty($name, $checkVars = true, $checkBehaviors = true)
{
if ($name === $this->filterAttributeName) {
return true;
}
return parent::canSetProperty($name, $checkVars, $checkBehaviors);
}
/**
* {@inheritdoc}
*/
public function __get($name)
{
if ($name === $this->filterAttributeName) {
return $this->getFilter();
}
return parent::__get($name);
}
/**
* {@inheritdoc}
*/
public function __set($name, $value)
{
if ($name === $this->filterAttributeName) {
$this->setFilter($value);
} else {
parent::__set($name, $value);
}
}
/**
* {@inheritdoc}
*/
public function __isset($name)
{
if ($name === $this->filterAttributeName) {
return $this->getFilter() !== null;
}
return parent::__isset($name);
}
/**
* {@inheritdoc}
*/
public function __unset($name)
{
if ($name === $this->filterAttributeName) {
$this->setFilter(null);
} else {
parent::__unset($name);
}
}
}

View File

@@ -0,0 +1,72 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\data;
/**
* DataProviderInterface is the interface that must be implemented by data provider classes.
*
* Data providers are components that sort and paginate data, and provide them to widgets
* such as [[\yii\grid\GridView]], [[\yii\widgets\ListView]].
*
* For more details and usage information on DataProviderInterface, see the [guide article on data providers](guide:output-data-providers).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
interface DataProviderInterface
{
/**
* Prepares the data models and keys.
*
* This method will prepare the data models and keys that can be retrieved via
* [[getModels()]] and [[getKeys()]].
*
* This method will be implicitly called by [[getModels()]] and [[getKeys()]] if it has not been called before.
*
* @param bool $forcePrepare whether to force data preparation even if it has been done before.
*/
public function prepare($forcePrepare = false);
/**
* Returns the number of data models in the current page.
* This is equivalent to `count($provider->getModels())`.
* When [[getPagination|pagination]] is false, this is the same as [[getTotalCount|totalCount]].
* @return int the number of data models in the current page.
*/
public function getCount();
/**
* Returns the total number of data models.
* When [[getPagination|pagination]] is false, this is the same as [[getCount|count]].
* @return int total number of possible data models.
*/
public function getTotalCount();
/**
* Returns the data models in the current page.
* @return array the list of data models in the current page.
*/
public function getModels();
/**
* Returns the key values associated with the data models.
* @return array the list of key values corresponding to [[getModels|models]]. Each data model in [[getModels|models]]
* is uniquely identified by the corresponding key value in this array.
*/
public function getKeys();
/**
* @return Sort the sorting object. If this is false, it means the sorting is disabled.
*/
public function getSort();
/**
* @return Pagination|false the pagination object. If this is false, it means the pagination is disabled.
*/
public function getPagination();
}

353
vendor/yiisoft/yii2/data/Pagination.php vendored Normal file
View File

@@ -0,0 +1,353 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\data;
use Yii;
use yii\base\BaseObject;
use yii\web\Link;
use yii\web\Linkable;
use yii\web\Request;
/**
* Pagination represents information relevant to pagination of data items.
*
* When data needs to be rendered in multiple pages, Pagination can be used to
* represent information such as [[totalCount|total item count]], [[pageSize|page size]],
* [[page|current page]], etc. These information can be passed to [[\yii\widgets\LinkPager|pagers]]
* to render pagination buttons or links.
*
* The following example shows how to create a pagination object and feed it
* to a pager.
*
* Controller action:
*
* ```php
* public function actionIndex()
* {
* $query = Article::find()->where(['status' => 1]);
* $countQuery = clone $query;
* $pages = new Pagination(['totalCount' => $countQuery->count()]);
* $models = $query->offset($pages->offset)
* ->limit($pages->limit)
* ->all();
*
* return $this->render('index', [
* 'models' => $models,
* 'pages' => $pages,
* ]);
* }
* ```
*
* View:
*
* ```php
* foreach ($models as $model) {
* // display $model here
* }
*
* // display pagination
* echo LinkPager::widget([
* 'pagination' => $pages,
* ]);
* ```
*
* For more details and usage information on Pagination, see the [guide article on pagination](guide:output-pagination).
*
* @property int $limit The limit of the data. This may be used to set the LIMIT value for a SQL statement for
* fetching the current page of data. Note that if the page size is infinite, a value -1 will be returned. This
* property is read-only.
* @property array $links The links for navigational purpose. The array keys specify the purpose of the links
* (e.g. [[LINK_FIRST]]), and the array values are the corresponding URLs. This property is read-only.
* @property int $offset The offset of the data. This may be used to set the OFFSET value for a SQL statement
* for fetching the current page of data. This property is read-only.
* @property int $page The zero-based current page number.
* @property int $pageCount Number of pages. This property is read-only.
* @property int $pageSize The number of items per page. If it is less than 1, it means the page size is
* infinite, and thus a single page contains all items.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Pagination extends BaseObject implements Linkable
{
const LINK_NEXT = 'next';
const LINK_PREV = 'prev';
const LINK_FIRST = 'first';
const LINK_LAST = 'last';
/**
* @var string name of the parameter storing the current page index.
* @see params
*/
public $pageParam = 'page';
/**
* @var string name of the parameter storing the page size.
* @see params
*/
public $pageSizeParam = 'per-page';
/**
* @var bool whether to always have the page parameter in the URL created by [[createUrl()]].
* If false and [[page]] is 0, the page parameter will not be put in the URL.
*/
public $forcePageParam = true;
/**
* @var string the route of the controller action for displaying the paged contents.
* If not set, it means using the currently requested route.
*/
public $route;
/**
* @var array parameters (name => value) that should be used to obtain the current page number
* and to create new pagination URLs. If not set, all parameters from $_GET will be used instead.
*
* In order to add hash to all links use `array_merge($_GET, ['#' => 'my-hash'])`.
*
* The array element indexed by [[pageParam]] is considered to be the current page number (defaults to 0);
* while the element indexed by [[pageSizeParam]] is treated as the page size (defaults to [[defaultPageSize]]).
*/
public $params;
/**
* @var \yii\web\UrlManager the URL manager used for creating pagination URLs. If not set,
* the "urlManager" application component will be used.
*/
public $urlManager;
/**
* @var bool whether to check if [[page]] is within valid range.
* When this property is true, the value of [[page]] will always be between 0 and ([[pageCount]]-1).
* Because [[pageCount]] relies on the correct value of [[totalCount]] which may not be available
* in some cases (e.g. MongoDB), you may want to set this property to be false to disable the page
* number validation. By doing so, [[page]] will return the value indexed by [[pageParam]] in [[params]].
*/
public $validatePage = true;
/**
* @var int total number of items.
*/
public $totalCount = 0;
/**
* @var int the default page size. This property will be returned by [[pageSize]] when page size
* cannot be determined by [[pageSizeParam]] from [[params]].
*/
public $defaultPageSize = 20;
/**
* @var array|false the page size limits. The first array element stands for the minimal page size, and the second
* the maximal page size. If this is false, it means [[pageSize]] should always return the value of [[defaultPageSize]].
*/
public $pageSizeLimit = [1, 50];
/**
* @var int number of items on each page.
* If it is less than 1, it means the page size is infinite, and thus a single page contains all items.
*/
private $_pageSize;
/**
* @return int number of pages
*/
public function getPageCount()
{
$pageSize = $this->getPageSize();
if ($pageSize < 1) {
return $this->totalCount > 0 ? 1 : 0;
}
$totalCount = $this->totalCount < 0 ? 0 : (int) $this->totalCount;
return (int) (($totalCount + $pageSize - 1) / $pageSize);
}
private $_page;
/**
* Returns the zero-based current page number.
* @param bool $recalculate whether to recalculate the current page based on the page size and item count.
* @return int the zero-based current page number.
*/
public function getPage($recalculate = false)
{
if ($this->_page === null || $recalculate) {
$page = (int) $this->getQueryParam($this->pageParam, 1) - 1;
$this->setPage($page, true);
}
return $this->_page;
}
/**
* Sets the current page number.
* @param int $value the zero-based index of the current page.
* @param bool $validatePage whether to validate the page number. Note that in order
* to validate the page number, both [[validatePage]] and this parameter must be true.
*/
public function setPage($value, $validatePage = false)
{
if ($value === null) {
$this->_page = null;
} else {
$value = (int) $value;
if ($validatePage && $this->validatePage) {
$pageCount = $this->getPageCount();
if ($value >= $pageCount) {
$value = $pageCount - 1;
}
}
if ($value < 0) {
$value = 0;
}
$this->_page = $value;
}
}
/**
* Returns the number of items per page.
* By default, this method will try to determine the page size by [[pageSizeParam]] in [[params]].
* If the page size cannot be determined this way, [[defaultPageSize]] will be returned.
* @return int the number of items per page. If it is less than 1, it means the page size is infinite,
* and thus a single page contains all items.
* @see pageSizeLimit
*/
public function getPageSize()
{
if ($this->_pageSize === null) {
if (empty($this->pageSizeLimit)) {
$pageSize = $this->defaultPageSize;
$this->setPageSize($pageSize);
} else {
$pageSize = (int) $this->getQueryParam($this->pageSizeParam, $this->defaultPageSize);
$this->setPageSize($pageSize, true);
}
}
return $this->_pageSize;
}
/**
* @param int $value the number of items per page.
* @param bool $validatePageSize whether to validate page size.
*/
public function setPageSize($value, $validatePageSize = false)
{
if ($value === null) {
$this->_pageSize = null;
} else {
$value = (int) $value;
if ($validatePageSize && isset($this->pageSizeLimit[0], $this->pageSizeLimit[1]) && count($this->pageSizeLimit) === 2) {
if ($value < $this->pageSizeLimit[0]) {
$value = $this->pageSizeLimit[0];
} elseif ($value > $this->pageSizeLimit[1]) {
$value = $this->pageSizeLimit[1];
}
}
$this->_pageSize = $value;
}
}
/**
* Creates the URL suitable for pagination with the specified page number.
* This method is mainly called by pagers when creating URLs used to perform pagination.
* @param int $page the zero-based page number that the URL should point to.
* @param int $pageSize the number of items on each page. If not set, the value of [[pageSize]] will be used.
* @param bool $absolute whether to create an absolute URL. Defaults to `false`.
* @return string the created URL
* @see params
* @see forcePageParam
*/
public function createUrl($page, $pageSize = null, $absolute = false)
{
$page = (int) $page;
$pageSize = (int) $pageSize;
if (($params = $this->params) === null) {
$request = Yii::$app->getRequest();
$params = $request instanceof Request ? $request->getQueryParams() : [];
}
if ($page > 0 || $page == 0 && $this->forcePageParam) {
$params[$this->pageParam] = $page + 1;
} else {
unset($params[$this->pageParam]);
}
if ($pageSize <= 0) {
$pageSize = $this->getPageSize();
}
if ($pageSize != $this->defaultPageSize) {
$params[$this->pageSizeParam] = $pageSize;
} else {
unset($params[$this->pageSizeParam]);
}
$params[0] = $this->route === null ? Yii::$app->controller->getRoute() : $this->route;
$urlManager = $this->urlManager === null ? Yii::$app->getUrlManager() : $this->urlManager;
if ($absolute) {
return $urlManager->createAbsoluteUrl($params);
}
return $urlManager->createUrl($params);
}
/**
* @return int the offset of the data. This may be used to set the
* OFFSET value for a SQL statement for fetching the current page of data.
*/
public function getOffset()
{
$pageSize = $this->getPageSize();
return $pageSize < 1 ? 0 : $this->getPage() * $pageSize;
}
/**
* @return int the limit of the data. This may be used to set the
* LIMIT value for a SQL statement for fetching the current page of data.
* Note that if the page size is infinite, a value -1 will be returned.
*/
public function getLimit()
{
$pageSize = $this->getPageSize();
return $pageSize < 1 ? -1 : $pageSize;
}
/**
* Returns a whole set of links for navigating to the first, last, next and previous pages.
* @param bool $absolute whether the generated URLs should be absolute.
* @return array the links for navigational purpose. The array keys specify the purpose of the links (e.g. [[LINK_FIRST]]),
* and the array values are the corresponding URLs.
*/
public function getLinks($absolute = false)
{
$currentPage = $this->getPage();
$pageCount = $this->getPageCount();
$links = [
Link::REL_SELF => $this->createUrl($currentPage, null, $absolute),
];
if ($currentPage > 0) {
$links[self::LINK_FIRST] = $this->createUrl(0, null, $absolute);
$links[self::LINK_PREV] = $this->createUrl($currentPage - 1, null, $absolute);
}
if ($currentPage < $pageCount - 1) {
$links[self::LINK_NEXT] = $this->createUrl($currentPage + 1, null, $absolute);
$links[self::LINK_LAST] = $this->createUrl($pageCount - 1, null, $absolute);
}
return $links;
}
/**
* Returns the value of the specified query parameter.
* This method returns the named parameter value from [[params]]. Null is returned if the value does not exist.
* @param string $name the parameter name
* @param string $defaultValue the value to be returned when the specified parameter does not exist in [[params]].
* @return string the parameter value
*/
protected function getQueryParam($name, $defaultValue = null)
{
if (($params = $this->params) === null) {
$request = Yii::$app->getRequest();
$params = $request instanceof Request ? $request->getQueryParams() : [];
}
return isset($params[$name]) && is_scalar($params[$name]) ? $params[$name] : $defaultValue;
}
}

464
vendor/yiisoft/yii2/data/Sort.php vendored Normal file
View File

@@ -0,0 +1,464 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\data;
use Yii;
use yii\base\BaseObject;
use yii\base\InvalidConfigException;
use yii\helpers\Html;
use yii\helpers\Inflector;
use yii\web\Request;
/**
* Sort represents information relevant to sorting.
*
* When data needs to be sorted according to one or several attributes,
* we can use Sort to represent the sorting information and generate
* appropriate hyperlinks that can lead to sort actions.
*
* A typical usage example is as follows,
*
* ```php
* public function actionIndex()
* {
* $sort = new Sort([
* 'attributes' => [
* 'age',
* 'name' => [
* 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC],
* 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC],
* 'default' => SORT_DESC,
* 'label' => 'Name',
* ],
* ],
* ]);
*
* $models = Article::find()
* ->where(['status' => 1])
* ->orderBy($sort->orders)
* ->all();
*
* return $this->render('index', [
* 'models' => $models,
* 'sort' => $sort,
* ]);
* }
* ```
*
* View:
*
* ```php
* // display links leading to sort actions
* echo $sort->link('name') . ' | ' . $sort->link('age');
*
* foreach ($models as $model) {
* // display $model here
* }
* ```
*
* In the above, we declare two [[attributes]] that support sorting: `name` and `age`.
* We pass the sort information to the Article query so that the query results are
* sorted by the orders specified by the Sort object. In the view, we show two hyperlinks
* that can lead to pages with the data sorted by the corresponding attributes.
*
* For more details and usage information on Sort, see the [guide article on sorting](guide:output-sorting).
*
* @property array $attributeOrders Sort directions indexed by attribute names. Sort direction can be either
* `SORT_ASC` for ascending order or `SORT_DESC` for descending order. Note that the type of this property
* differs in getter and setter. See [[getAttributeOrders()]] and [[setAttributeOrders()]] for details.
* @property array $orders The columns (keys) and their corresponding sort directions (values). This can be
* passed to [[\yii\db\Query::orderBy()]] to construct a DB query. This property is read-only.
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class Sort extends BaseObject
{
/**
* @var bool whether the sorting can be applied to multiple attributes simultaneously.
* Defaults to `false`, which means each time the data can only be sorted by one attribute.
*/
public $enableMultiSort = false;
/**
* @var array list of attributes that are allowed to be sorted. Its syntax can be
* described using the following example:
*
* ```php
* [
* 'age',
* 'name' => [
* 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC],
* 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC],
* 'default' => SORT_DESC,
* 'label' => 'Name',
* ],
* ]
* ```
*
* In the above, two attributes are declared: `age` and `name`. The `age` attribute is
* a simple attribute which is equivalent to the following:
*
* ```php
* 'age' => [
* 'asc' => ['age' => SORT_ASC],
* 'desc' => ['age' => SORT_DESC],
* 'default' => SORT_ASC,
* 'label' => Inflector::camel2words('age'),
* ]
* ```
*
* Since 2.0.12 particular sort direction can be also specified as direct sort expression, like following:
*
* ```php
* 'name' => [
* 'asc' => '[[last_name]] ASC NULLS FIRST', // PostgreSQL specific feature
* 'desc' => '[[last_name]] DESC NULLS LAST',
* ]
* ```
*
* The `name` attribute is a composite attribute:
*
* - The `name` key represents the attribute name which will appear in the URLs leading
* to sort actions.
* - The `asc` and `desc` elements specify how to sort by the attribute in ascending
* and descending orders, respectively. Their values represent the actual columns and
* the directions by which the data should be sorted by.
* - The `default` element specifies by which direction the attribute should be sorted
* if it is not currently sorted (the default value is ascending order).
* - The `label` element specifies what label should be used when calling [[link()]] to create
* a sort link. If not set, [[Inflector::camel2words()]] will be called to get a label.
* Note that it will not be HTML-encoded.
*
* Note that if the Sort object is already created, you can only use the full format
* to configure every attribute. Each attribute must include these elements: `asc` and `desc`.
*/
public $attributes = [];
/**
* @var string the name of the parameter that specifies which attributes to be sorted
* in which direction. Defaults to `sort`.
* @see params
*/
public $sortParam = 'sort';
/**
* @var array the order that should be used when the current request does not specify any order.
* The array keys are attribute names and the array values are the corresponding sort directions. For example,
*
* ```php
* [
* 'name' => SORT_ASC,
* 'created_at' => SORT_DESC,
* ]
* ```
*
* @see attributeOrders
*/
public $defaultOrder;
/**
* @var string the route of the controller action for displaying the sorted contents.
* If not set, it means using the currently requested route.
*/
public $route;
/**
* @var string the character used to separate different attributes that need to be sorted by.
*/
public $separator = ',';
/**
* @var array parameters (name => value) that should be used to obtain the current sort directions
* and to create new sort URLs. If not set, `$_GET` will be used instead.
*
* In order to add hash to all links use `array_merge($_GET, ['#' => 'my-hash'])`.
*
* The array element indexed by [[sortParam]] is considered to be the current sort directions.
* If the element does not exist, the [[defaultOrder|default order]] will be used.
*
* @see sortParam
* @see defaultOrder
*/
public $params;
/**
* @var \yii\web\UrlManager the URL manager used for creating sort URLs. If not set,
* the `urlManager` application component will be used.
*/
public $urlManager;
/**
* Normalizes the [[attributes]] property.
*/
public function init()
{
$attributes = [];
foreach ($this->attributes as $name => $attribute) {
if (!is_array($attribute)) {
$attributes[$attribute] = [
'asc' => [$attribute => SORT_ASC],
'desc' => [$attribute => SORT_DESC],
];
} elseif (!isset($attribute['asc'], $attribute['desc'])) {
$attributes[$name] = array_merge([
'asc' => [$name => SORT_ASC],
'desc' => [$name => SORT_DESC],
], $attribute);
} else {
$attributes[$name] = $attribute;
}
}
$this->attributes = $attributes;
}
/**
* Returns the columns and their corresponding sort directions.
* @param bool $recalculate whether to recalculate the sort directions
* @return array the columns (keys) and their corresponding sort directions (values).
* This can be passed to [[\yii\db\Query::orderBy()]] to construct a DB query.
*/
public function getOrders($recalculate = false)
{
$attributeOrders = $this->getAttributeOrders($recalculate);
$orders = [];
foreach ($attributeOrders as $attribute => $direction) {
$definition = $this->attributes[$attribute];
$columns = $definition[$direction === SORT_ASC ? 'asc' : 'desc'];
if (is_array($columns) || $columns instanceof \Traversable) {
foreach ($columns as $name => $dir) {
$orders[$name] = $dir;
}
} else {
$orders[] = $columns;
}
}
return $orders;
}
/**
* @var array the currently requested sort order as computed by [[getAttributeOrders]].
*/
private $_attributeOrders;
/**
* Returns the currently requested sort information.
* @param bool $recalculate whether to recalculate the sort directions
* @return array sort directions indexed by attribute names.
* Sort direction can be either `SORT_ASC` for ascending order or
* `SORT_DESC` for descending order.
*/
public function getAttributeOrders($recalculate = false)
{
if ($this->_attributeOrders === null || $recalculate) {
$this->_attributeOrders = [];
if (($params = $this->params) === null) {
$request = Yii::$app->getRequest();
$params = $request instanceof Request ? $request->getQueryParams() : [];
}
if (isset($params[$this->sortParam])) {
foreach ($this->parseSortParam($params[$this->sortParam]) as $attribute) {
$descending = false;
if (strncmp($attribute, '-', 1) === 0) {
$descending = true;
$attribute = substr($attribute, 1);
}
if (isset($this->attributes[$attribute])) {
$this->_attributeOrders[$attribute] = $descending ? SORT_DESC : SORT_ASC;
if (!$this->enableMultiSort) {
return $this->_attributeOrders;
}
}
}
}
if (empty($this->_attributeOrders) && is_array($this->defaultOrder)) {
$this->_attributeOrders = $this->defaultOrder;
}
}
return $this->_attributeOrders;
}
/**
* Parses the value of [[sortParam]] into an array of sort attributes.
*
* The format must be the attribute name only for ascending
* or the attribute name prefixed with `-` for descending.
*
* For example the following return value will result in ascending sort by
* `category` and descending sort by `created_at`:
*
* ```php
* [
* 'category',
* '-created_at'
* ]
* ```
*
* @param string $param the value of the [[sortParam]].
* @return array the valid sort attributes.
* @since 2.0.12
* @see $separator for the attribute name separator.
* @see $sortParam
*/
protected function parseSortParam($param)
{
return is_scalar($param) ? explode($this->separator, $param) : [];
}
/**
* Sets up the currently sort information.
* @param array|null $attributeOrders sort directions indexed by attribute names.
* Sort direction can be either `SORT_ASC` for ascending order or
* `SORT_DESC` for descending order.
* @param bool $validate whether to validate given attribute orders against [[attributes]] and [[enableMultiSort]].
* If validation is enabled incorrect entries will be removed.
* @since 2.0.10
*/
public function setAttributeOrders($attributeOrders, $validate = true)
{
if ($attributeOrders === null || !$validate) {
$this->_attributeOrders = $attributeOrders;
} else {
$this->_attributeOrders = [];
foreach ($attributeOrders as $attribute => $order) {
if (isset($this->attributes[$attribute])) {
$this->_attributeOrders[$attribute] = $order;
if (!$this->enableMultiSort) {
break;
}
}
}
}
}
/**
* Returns the sort direction of the specified attribute in the current request.
* @param string $attribute the attribute name
* @return bool|null Sort direction of the attribute. Can be either `SORT_ASC`
* for ascending order or `SORT_DESC` for descending order. Null is returned
* if the attribute is invalid or does not need to be sorted.
*/
public function getAttributeOrder($attribute)
{
$orders = $this->getAttributeOrders();
return isset($orders[$attribute]) ? $orders[$attribute] : null;
}
/**
* Generates a hyperlink that links to the sort action to sort by the specified attribute.
* Based on the sort direction, the CSS class of the generated hyperlink will be appended
* with "asc" or "desc".
* @param string $attribute the attribute name by which the data should be sorted by.
* @param array $options additional HTML attributes for the hyperlink tag.
* There is one special attribute `label` which will be used as the label of the hyperlink.
* If this is not set, the label defined in [[attributes]] will be used.
* If no label is defined, [[\yii\helpers\Inflector::camel2words()]] will be called to get a label.
* Note that it will not be HTML-encoded.
* @return string the generated hyperlink
* @throws InvalidConfigException if the attribute is unknown
*/
public function link($attribute, $options = [])
{
if (($direction = $this->getAttributeOrder($attribute)) !== null) {
$class = $direction === SORT_DESC ? 'desc' : 'asc';
if (isset($options['class'])) {
$options['class'] .= ' ' . $class;
} else {
$options['class'] = $class;
}
}
$url = $this->createUrl($attribute);
$options['data-sort'] = $this->createSortParam($attribute);
if (isset($options['label'])) {
$label = $options['label'];
unset($options['label']);
} else {
if (isset($this->attributes[$attribute]['label'])) {
$label = $this->attributes[$attribute]['label'];
} else {
$label = Inflector::camel2words($attribute);
}
}
return Html::a($label, $url, $options);
}
/**
* Creates a URL for sorting the data by the specified attribute.
* This method will consider the current sorting status given by [[attributeOrders]].
* For example, if the current page already sorts the data by the specified attribute in ascending order,
* then the URL created will lead to a page that sorts the data by the specified attribute in descending order.
* @param string $attribute the attribute name
* @param bool $absolute whether to create an absolute URL. Defaults to `false`.
* @return string the URL for sorting. False if the attribute is invalid.
* @throws InvalidConfigException if the attribute is unknown
* @see attributeOrders
* @see params
*/
public function createUrl($attribute, $absolute = false)
{
if (($params = $this->params) === null) {
$request = Yii::$app->getRequest();
$params = $request instanceof Request ? $request->getQueryParams() : [];
}
$params[$this->sortParam] = $this->createSortParam($attribute);
$params[0] = $this->route === null ? Yii::$app->controller->getRoute() : $this->route;
$urlManager = $this->urlManager === null ? Yii::$app->getUrlManager() : $this->urlManager;
if ($absolute) {
return $urlManager->createAbsoluteUrl($params);
}
return $urlManager->createUrl($params);
}
/**
* Creates the sort variable for the specified attribute.
* The newly created sort variable can be used to create a URL that will lead to
* sorting by the specified attribute.
* @param string $attribute the attribute name
* @return string the value of the sort variable
* @throws InvalidConfigException if the specified attribute is not defined in [[attributes]]
*/
public function createSortParam($attribute)
{
if (!isset($this->attributes[$attribute])) {
throw new InvalidConfigException("Unknown attribute: $attribute");
}
$definition = $this->attributes[$attribute];
$directions = $this->getAttributeOrders();
if (isset($directions[$attribute])) {
$direction = $directions[$attribute] === SORT_DESC ? SORT_ASC : SORT_DESC;
unset($directions[$attribute]);
} else {
$direction = isset($definition['default']) ? $definition['default'] : SORT_ASC;
}
if ($this->enableMultiSort) {
$directions = array_merge([$attribute => $direction], $directions);
} else {
$directions = [$attribute => $direction];
}
$sorts = [];
foreach ($directions as $attribute => $direction) {
$sorts[] = $direction === SORT_DESC ? '-' . $attribute : $attribute;
}
return implode($this->separator, $sorts);
}
/**
* Returns a value indicating whether the sort definition supports sorting by the named attribute.
* @param string $name the attribute name
* @return bool whether the sort definition supports sorting by the named attribute.
*/
public function hasAttribute($name)
{
return isset($this->attributes[$name]);
}
}

View File

@@ -0,0 +1,171 @@
<?php
/**
* @link http://www.yiiframework.com/
* @copyright Copyright (c) 2008 Yii Software LLC
* @license http://www.yiiframework.com/license/
*/
namespace yii\data;
use Yii;
use yii\base\InvalidConfigException;
use yii\db\Connection;
use yii\db\Expression;
use yii\db\Query;
use yii\di\Instance;
/**
* SqlDataProvider implements a data provider based on a plain SQL statement.
*
* SqlDataProvider provides data in terms of arrays, each representing a row of query result.
*
* Like other data providers, SqlDataProvider also supports sorting and pagination.
* It does so by modifying the given [[sql]] statement with "ORDER BY" and "LIMIT"
* clauses. You may configure the [[sort]] and [[pagination]] properties to
* customize sorting and pagination behaviors.
*
* SqlDataProvider may be used in the following way:
*
* ```php
* $count = Yii::$app->db->createCommand('
* SELECT COUNT(*) FROM user WHERE status=:status
* ', [':status' => 1])->queryScalar();
*
* $dataProvider = new SqlDataProvider([
* 'sql' => 'SELECT * FROM user WHERE status=:status',
* 'params' => [':status' => 1],
* 'totalCount' => $count,
* 'sort' => [
* 'attributes' => [
* 'age',
* 'name' => [
* 'asc' => ['first_name' => SORT_ASC, 'last_name' => SORT_ASC],
* 'desc' => ['first_name' => SORT_DESC, 'last_name' => SORT_DESC],
* 'default' => SORT_DESC,
* 'label' => 'Name',
* ],
* ],
* ],
* 'pagination' => [
* 'pageSize' => 20,
* ],
* ]);
*
* // get the user records in the current page
* $models = $dataProvider->getModels();
* ```
*
* Note: if you want to use the pagination feature, you must configure the [[totalCount]] property
* to be the total number of rows (without pagination). And if you want to use the sorting feature,
* you must configure the [[sort]] property so that the provider knows which columns can be sorted.
*
* For more details and usage information on SqlDataProvider, see the [guide article on data providers](guide:output-data-providers).
*
* @author Qiang Xue <qiang.xue@gmail.com>
* @since 2.0
*/
class SqlDataProvider extends BaseDataProvider
{
/**
* @var Connection|array|string the DB connection object or the application component ID of the DB connection.
* Starting from version 2.0.2, this can also be a configuration array for creating the object.
*/
public $db = 'db';
/**
* @var string the SQL statement to be used for fetching data rows.
*/
public $sql;
/**
* @var array parameters (name=>value) to be bound to the SQL statement.
*/
public $params = [];
/**
* @var string|callable the column that is used as the key of the data models.
* This can be either a column name, or a callable that returns the key value of a given data model.
*
* If this is not set, the keys of the [[models]] array will be used.
*/
public $key;
/**
* Initializes the DB connection component.
* This method will initialize the [[db]] property to make sure it refers to a valid DB connection.
* @throws InvalidConfigException if [[db]] is invalid.
*/
public function init()
{
parent::init();
$this->db = Instance::ensure($this->db, Connection::className());
if ($this->sql === null) {
throw new InvalidConfigException('The "sql" property must be set.');
}
}
/**
* {@inheritdoc}
*/
protected function prepareModels()
{
$sort = $this->getSort();
$pagination = $this->getPagination();
if ($pagination === false && $sort === false) {
return $this->db->createCommand($this->sql, $this->params)->queryAll();
}
$sql = $this->sql;
$orders = [];
$limit = $offset = null;
if ($sort !== false) {
$orders = $sort->getOrders();
$pattern = '/\s+order\s+by\s+([\w\s,\.]+)$/i';
if (preg_match($pattern, $sql, $matches)) {
array_unshift($orders, new Expression($matches[1]));
$sql = preg_replace($pattern, '', $sql);
}
}
if ($pagination !== false) {
$pagination->totalCount = $this->getTotalCount();
$limit = $pagination->getLimit();
$offset = $pagination->getOffset();
}
$sql = $this->db->getQueryBuilder()->buildOrderByAndLimit($sql, $orders, $limit, $offset);
return $this->db->createCommand($sql, $this->params)->queryAll();
}
/**
* {@inheritdoc}
*/
protected function prepareKeys($models)
{
$keys = [];
if ($this->key !== null) {
foreach ($models as $model) {
if (is_string($this->key)) {
$keys[] = $model[$this->key];
} else {
$keys[] = call_user_func($this->key, $model);
}
}
return $keys;
}
return array_keys($models);
}
/**
* {@inheritdoc}
*/
protected function prepareTotalCount()
{
return (new Query([
'from' => ['sub' => "({$this->sql})"],
'params' => $this->params,
]))->count('*', $this->db);
}
}