Yii2 ActiveDataProvider Сортировка, фильтрация

  • 15 марта 2019
  • 18261
0%

Помогло?

Всем привет, в сегодняшнем уроке мы будем знакомиться с "провайдерами данных". Они нужны для упрощения выборки, потому что в их структуру изначально заложен следующий функционал:

  • Базовая SQL выборка (определение таблицы, с которой работаем, плюс какие-то базовые условия)
  • Пагинация (Вы можете гибко настроить количество данных для вывода, можно получить общее количество данных и так далее)
  • Сортировка данных (Используя разные параметры можно менять порядок вывода данных)

Конечно же вы можете реализовать весь этот функционал самостоятельно, используя средства ActiveRecord, Pagination, Sort. Но только подумайте, сколько кода вам придется написать, и что в итоге вы получите? :)  Случайно не ActiveDataProvider ? :)

Поэтому, давайте, сэкономим свое время и приступим к практике:

  1. Подготовка
  2. Yii2 ActiveDataProvider model
  3. Фильтрация данных
  4. Дополнительные возможности ActiveDataProvider

В данном уроке мы с вами познакомимся с тем, как настроить ActiveDataProvider на своем сайте. Сделаем код максимально расширяемым и понятным для повторного использования.

Теперь, давайте, создадим один из таких провайдеров. В качестве примера, можно рассмотреть стандартный yii2 advanced проект.

Подготовка.

Там, в проекте, по умолчанию есть миграция init. И, если вы выполните команду

php yii migrate

то увидите следующее:

ActiveDataProvider migrate

Когда вы ответите на вопрос: Yes, то увидите вновь создавшуюся таблицу users

ActiveDataProvider users

Теперь нам надо заполнить эту таблицу данными. Для вас я создал миграцию, которую вам нужно создать. Если вы не знаете как создавать миграции, почитайте в этой статье

<?php

use yii\db\Migration;
use common\models\User;

/**
 * Class m190310_082208_add_users
 */
class m190310_082208_add_users extends Migration
{
    /**
     * {@inheritdoc}
     */
    public function safeUp()
    {
        $model = new User();
        $model->generateAuthKey();
        $model->setPassword('123123');
        $model->generatePasswordResetToken();

        $this->insert('{{%user}}', [
            'id'                   => 1,
            'username'             => 'admin',
            'auth_key'             => $model->auth_key,
            'password_hash'        => $model->password_hash,
            'password_reset_token' => $model->password_reset_token,
            'email'                => 'admin@admin.ru',
            'status'               => 10,
            'created_at'           => time(),
            'updated_at'           => time(),
        ]);

        $model = new User();
        $model->generateAuthKey();
        $model->setPassword('123123');
        $model->generatePasswordResetToken();

        $this->insert('{{%user}}', [
            'id'                   => 2,
            'username'             => 'user1',
            'auth_key'             => $model->auth_key,
            'password_hash'        => $model->password_hash,
            'password_reset_token' => $model->password_reset_token,
            'email'                => 'user1@manager.ru',
            'status'               => 10,
            'created_at'           => time(),
            'updated_at'           => time(),
        ]);

        $model = new User();
        $model->generateAuthKey();
        $model->setPassword('123123');
        $model->generatePasswordResetToken();

        $this->insert('{{%user}}', [
            'id'                   => 3,
            'username'             => 'user2',
            'auth_key'             => $model->auth_key,
            'password_hash'        => $model->password_hash,
            'password_reset_token' => $model->password_reset_token,
            'email'                => 'user2@manager.ru',
            'status'               => 10,
            'created_at'           => time(),
            'updated_at'           => time(),
        ]);
    }

    /**
     * {@inheritdoc}
     */
    public function safeDown()
    {
        User::deleteAll(['id' => [1,2,3]]);
    }
}

После выполнения данной миграции:

php yii migrate

В вашей базе появятся 3 новых пользователя.

Yii2 users table

Yii2 ActiveDataProvider model

Так исторически сложилось, что вся логика для работы с ActiveDataProvider у меня лежит в папке /models/search. Пример того, как выглядит архитектура одного из моих проектов:

ActiveDataProvider arcitect

Давайте, создадим файл для работы с ActiveDataProvider

Выполним последовательно от корня:  

mkdir frontend/models/search
touch frontend/models/search/UserSearch.php

Давайте, сначала заполним эту модель:

frontend/models/search/UserSearch.php

<?php

namespace app\models\search;

use common\models\User;
use yii\data\ActiveDataProvider;

class UserSearch extends User
{
    public function search($params)
    {
        
    }
}

Достаточно просто будет объявить название,  задать namespace и функцию, о которой вы узнаете уже совсем скоро.

У вас вопрос: "откуда взялось app\models?" Предлагаю вам самостоятельно ознакомиться с классом yii\base\Application. Найдите там функцию: public function setBasePath($path) 

Теперь, давайте, настроим контроллер, чтобы к нему больше не возвращаться. Его задача - организовать вызов нашей модели, которая работает с ActiveDataProvider, и отправить данные во view. Решается (реально!) с помощью 2-х строчек кода. 

frontend/controllers/SiteController.php

…
public function actionIndex()
    {
        $searchModel = new UserSearch();
        $dataProvider = $searchModel->search(Yii::$app->request->queryParams);

        return $this->render('index', [
            'searchModel'  => $searchModel,
            'dataProvider' => $dataProvider,
        ]);
    }
…

Переменной $dataProvider присваивается результат функции search, которая принимает все get параметры. Также, обратите внимание, на переменную $searchModel, с помощью которой можно получить атрибуты модели UserSearch. Например, если мы будем использовать gridView, то переменная $searchModel участвует в фильтрации данных.

Теперь, давайте, перейдем в views/site/index и настроим вьюху. Сначала удалим оттуда все лишнее. Оставим только:

frontend/views/site/index.php

<?php

/* @var $this yii\web\View */
/* @var $dataProvider \yii\data\ActiveDataProvider */


$this->title = 'My Yii Application';

\yii\helpers\VarDumper::dump($dataProvider->getModels(), 3, true);

VarDumper - это внутренний хелпер, который помогает распечатывать массивы и данные в удобном для чтения виде.

  • Первый параметр - массив данных, 
  • второй - глубина
  • Третий - подсветка синтаксиса

Если мы сейчас запустим наш проект, он естественно работать не будет:

ActiveDataProvider

Объясняется это достаточно легко: мы не заполнили модель. Поэтому отправляемся туда и доделаем дело.

frontend/models/search/UserSearch.php

<?php

namespace app\models\search;

use common\models\User;
use yii\data\ActiveDataProvider;

class UserSearch extends User
{
    public function search($params)
    {
        $query = static::find();

        $dataProvider = new ActiveDataProvider([
            'query' => $query,
        ]);

        $this->load($params);

        if (!$this->validate()) {
            return $dataProvider;
        }

        return $dataProvider;
    }
}

Смотрите, что мы сделали.

Строка 12: Используем возможности ActiveRecord (формируем instance выборки данных).

НИ В КОЕМ случае не используйте здесь методы ->all(), ->one() и тд.! То есть, запись вида:  
$query = static::find()->all(); НЕДОПУСТИМА! 

Строка 14: Далее мы формируем саму переменную $dataProvider, которой присваиваем сам ActiveDataProvider. В конструктор мы можем передать параметры (о них вы будете узнавать по ходу статьи).  Самый главный и обязательный параметр - это query. Сюда мы передаем этот instance sql запроса, построенного через ActiveRecord. Также допустимо построить запрос и через queryBuilder - new Query. Но об этом как-нибудь в другой раз, либо можете погуглить самостоятельно.

Строка 18: Используя метод load, мы пытаемся провалидировать и установить атрибутам переданные значения. (Это тема фильтрации, об этом немного позже).

Строка 20: Если данные не проходят валидацию, то мы возвращаем ActiveDataProvider в том виде, в каком он сейчас (на основе запроса) static::find() (на языке SQL это будет звучать примерно так: SELECT * FROM `user`)

Строка 24: Сюда мы попадем, если данные прошли валидацию. Тогда мы изменяем sql запрос и уже отдаем измененный ActiveDataProvider.

Вот, собственно, классическая схема описания провайдера данных. Если вы все сделали также, то увидите вот такую картину:

Теперь, давайте, отсортируем этот массив по id пользователя. Делается это очень просто. Нужно передать параметр ?sort=-id (минус значит от большего к меньшему, если минус убрать, то будет наоборот). Вместо id вы можете передать другой атрибут, например username.

Фильтрация данных

Теперь, давайте, настроим фильтр. Для этого перейдем в модель и добавим туда следующий код:

frontend/models/search/UserSearch.php

<?php

namespace app\models\search;

use common\models\User;
use yii\data\ActiveDataProvider;
use yii\helpers\ArrayHelper;

class UserSearch extends User
{

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return ArrayHelper::merge(parent::rules(), [
            ['id','integer'],
            ['username','string'],
        ]);
    }

    public function search($params)
    {
        $query = static::find();

        $dataProvider = new ActiveDataProvider([
            'query' => $query,
        ]);

        $this->load($params);

        if (!$this->validate()) {
            return $dataProvider;
        }

        $query->filterWhere(['id' => $this->id]);
        $query->andFilterWhere(['LIKE', 'username', $this->username]);

        return $dataProvider;
    }
}

Строка 15: Здесь мы добавили правило валидации, то есть все id у нас числа, а username это строка.

Строки 37,38: Здесь очень интересное решение и достаточно функциональное. Когда наш алгоритм пройдет правило валидации, он дойдет до функции filterWhere. Она работает таким образом, что сделает выборку из бд, если данный атрибут установлен, а если нет, то не будет ничего делать.

Чтобы организовать фильтрацию, и при этом сделать так, чтобы наша модель “съела”  все параметры, и они успешно прошли валидацию и установились в атрибутах модели, надо передать их в том виде, в каком требуется для функции load(). А это массив вида:

То есть так называемый scope (это массив модели, которая будет принимать атрибуты). В нашем случае - это UserSearch, и внутри нее нужный нам параметр, который будет установлен в качестве значения атрибута. Такой подход будет отлично работать, если у вас на странице несколько форм, но все они обрабатываются одним контроллером.

Теперь мы настроили модель и можем передать параметры, чтобы отфильтровать данные:

?UserSearch[id]=2

В результате мы получим данные, отфильтрованные по id == 2:

ActiveDataProvider

Но, если мы добавим еще один параметр:

?UserSearch[id]=2&UserSearch[username]=admin

(что на самом деле должно вернуть нам пустой результат), получим в результате пустой массив.

Что еще нужно знать про ActiveDataProvider? Вы можете получить общую сумму элементов на странице. Это функция getTotalCount() в ActiveDataProvider. Давайте перейдем во view и реализуем ее:

frontend/views/site/index.php

<?php

/* @var $this yii\web\View */
/* @var $dataProvider \yii\data\ActiveDataProvider */

$this->title = 'My Yii Application';


if ($dataProvider->getTotalCount() > 0) {

    \yii\helpers\VarDumper::dump($dataProvider->getModels(), 3, true);

}else {

    echo \yii\bootstrap\Alert::widget([
        'body' => 'Ничего не найдено',
        'options' => [
            'class' => 'alert alert-danger'
        ]
    ]);

}

Строка 9: Мы проверяем, если итоговая сумма более 0, то распечатать массив

Строка 15: Если элементов нет, то вывести виджет Bootstrap3, в теле которого будет написано: ‘Ничего не найдено’.

Дополнительные возможности ActiveDataProvider

Также вы можете настроить пагинацию прямо в конструкторе ActiveDataProvider. Давайте, сделаем это. Переходим в нашу модель:

frontend/models/search/UserSearch.php

<?php

namespace app\models\search;

use common\models\User;
use yii\data\ActiveDataProvider;
use yii\helpers\ArrayHelper;

class UserSearch extends User
{

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return ArrayHelper::merge(parent::rules(), [
            ['id','integer'],
            ['username','string'],
        ]);
    }

    public function search($params)
    {
        $query = static::find();

        $dataProvider = new ActiveDataProvider([
            'query' => $query,
            'Pagination' => [
                'pageSize' => 2
            ],
        ]);

        $this->load($params);

        if (!$this->validate()) {
            return $dataProvider;
        }

        $query->filterWhere(['id' => $this->id]);
        $query->andFilterWhere(['LIKE', 'username', $this->username]);

        return $dataProvider;
    }
}

Строка 29: Задаем настройки пагинации. Чтобы лучше разбираться в данном вопросе, вы можете почитать про класс: yii\data\Pagination. Там очень много настроек

Теперь, давайте, удалим все параметры из адресной строки и откроем страницу. Вы увидите только 2 записи (пользователь с id = 1 и id = 2). Где 3-ий? Нам нужно передать параметр 2-ой страницы: ?page=2. А вот он и он:

ActiveDataProvider yii2

Еще вы можете настроить класс сортировки. Давайте, сделаем сортировку по умолчанию. Для этого опять открываем нашу модель:

frontend/models/search/UserSearch.php

<?php

namespace app\models\search;

use common\models\User;
use yii\data\ActiveDataProvider;
use yii\helpers\ArrayHelper;

class UserSearch extends User
{

    /**
     * {@inheritdoc}
     */
    public function rules()
    {
        return ArrayHelper::merge(parent::rules(), [
            ['id','integer'],
            ['username','string'],
        ]);
    }

    public function search($params)
    {
        $query = static::find();

        $dataProvider = new ActiveDataProvider([
            'query' => $query,
            'Pagination' => [
                'pageSize' => 2
            ],
            'sort' => [
                'defaultOrder' => [
                    'id' => SORT_DESC
                ]
            ]
        ]);

        $this->load($params);

        if (!$this->validate()) {
            return $dataProvider;
        }

        $query->filterWhere(['id' => $this->id]);
        $query->andFilterWhere(['LIKE', 'username', $this->username]);

        return $dataProvider;
    }
}

Строка 32: Мы задаем сортировку по умолчанию. По сути, это атрибуты класса: yii\data\Sort

Ну вот, в целом, и все. Как видите, благодаря “провайдеру” данных вы можете очень легко и гибко настроить практически все необходимые функции для работы. Если есть какие-нибудь вопросы, пишите мне на почту, буду рад помочь. Желаю успехов в обучении. Посылаю лучи добра!

Содержание

Облако тегов

Следующая статья

Yii3 Framework. Что нам ждать от него?

Что можно сказать о Yii3? Ведь он еще не вышел. Каким хотят видеть его разработчики? Вопросов больше, чем ответом. Даже не известно когда его ждать. Но некоторые предположения можно все-таки сделать.

Сообщения: (2)

?
Публикуя сообщение, вы соглашаетесь с пользовательским соглашением
ППервый3 года назад
проклятье, не пойму почему всё время order by id база PostgresSQL не работает
Ответить
?
Публикуя сообщение, вы соглашаетесь с пользовательским соглашением
Скрыть ответы: (1)
ААдминистратор3 года назад
А что в логах написано? Сортировка просто не отрабатывает или отрабатывает с какой-то ошибкой?
Ответить

Читайте также