Пример вывода массива с поиском, сортировкой и пагинацией с помощью виджета GridView
Добавлено: 2021.07.11, 07:46
Введение
При работе с различными источниками данных часто приходится выводить массивы. Удобнее для вывода массивов использовать виджет GridView, но т. к. в поиске не нашел обобщенной информации по организации фильтрации, сортировки и пагинации выводимых массивов, решил составить небольшой пример, с кратким описанием.
Сам пример представляет собой минимально необходимый код, для вывода синтетического массива с поддержкой фильтра (поиска), сортировкой и пагинации. В дальнейшем такой пример можно использовать при решении аналогичных задач в качестве шаблона или руководства.
Пример состоит из трёх файлов:
Невнятное описание решения
Вся суть в модели. Создаём модель, которая будет:
Исходный код файлов
Файл protected/models/samples/ArraySample.php
Файл с описанием модели:
Файл protected/views/test/index.php
Файл представления нашего массива:
Файл protected/controllers/TestController.php
Контроллер:
При работе с различными источниками данных часто приходится выводить массивы. Удобнее для вывода массивов использовать виджет GridView, но т. к. в поиске не нашел обобщенной информации по организации фильтрации, сортировки и пагинации выводимых массивов, решил составить небольшой пример, с кратким описанием.
Сам пример представляет собой минимально необходимый код, для вывода синтетического массива с поддержкой фильтра (поиска), сортировкой и пагинации. В дальнейшем такой пример можно использовать при решении аналогичных задач в качестве шаблона или руководства.
Пример состоит из трёх файлов:
- protected/models/samples/ArraySample.php — модель работы с массивом. Именно в ней описано, где получать данные, структура данных, как их фильтровать, и в ней же будет формироваться провайдер для виджета GridView.
- protected/views/test/index.php — представление массива с использованием виджета GridView.
- protected/controllers/TestController.php — контроллер, связывающий воедино модель и представление.
Невнятное описание решения
Вся суть в модели. Создаём модель, которая будет:
- Получать данные в виде массива, как — не важно, откуда — тоже не важно, зависит от вашей задачи.
- Фильтровать полученные данные, если источник позволяет в запросе отсылать ему фильтр получаемых данных, то формируем запрос к источнику данных с учетом фильтров, если это возможно.
Но не во всех случаях получается сформировать запрос к источнику с учетом фильтра. Так, к примеру, дата в источнике может храниться в формате timestamp, тогда фильтровать уже придется полученные из источника данные и возвращать пользователю уже отфильтрованные данные. - Формировать провайдер для виджета. В качестве провайдера выбран класс \yii\data\ArrayDataProvider, именно он обеспечит нам как связь массива с виджетом, так и сортировку массива.
Исходный код файлов
Файл protected/models/samples/ArraySample.php
Файл с описанием модели:
Код: Выделить всё
<?
/**
* @file protected/models/samples/ArraySample.php
* @brief Модель @ref app::models::samples::ArraySample "app\\models\\samples\\ArraySample"
* @author ladserg
* @date 2021/07/11
* @version $Id: $
**/
namespace app\models\samples;
use yii\helpers\ArrayHelper;
/**
* @class ArraySample
* @ingroup Models
* @brief Пример модели работы с массивами данных.
*
* Данный пример содержит минимально необходимый код организации вывода
* массива при помощи
* [GridView](https://www.yiiframework.com/doc/api/2.0/yii-grid-gridview),
* с поддержкой сортировки, фильтрации строк и пагинации.
**/
class ArraySample extends \yii\base\Model
{
// Для фильтрации необходимо для каждой колонки массива определить атрибут.
/**
* @var string $name
* Название праздника.
*/
public $name;
/**
* @var timestamp $date
* Дата праздника.
*/
public $date;
/**
* @var integer $holiday
* Признак выходного дня
* - 0 - рабочий день
* - 1 - выходной день
*/
public $holiday;
/**
* @var integer $pageSize
* Начальное количество строк на странице для пагинации.
*
* Т.к. массив у нас маленький, то для демонстрации пагинации укажем
* значение поменьше.
*/
public static $pageSize=5;
/**
* @var const DATE_FMT
* Формат даты.
* Что бы не путаться, переопределим в качестве константы формат даты, для
* дальнейшего его использования при фильтрации и выводе значений.
*/
public const DATE_FMT='Y-m-d';
/**
* @brief Получение меток атрибутов массива.
* @details Этот метод позволяет виджету GridView получить человеко-читаемые
* названия столбцов. Без этого метода в качестве заголовков столбцов, при
* выводе таблицы, будут выступать индексы значений.
* @return array - Ассоциированный массив атрибутов с их метками.
*/
public function attributeLabels()
{
return [
'name'=>'Праздник',
'date'=>'Дата',
'holiday'=>'Выходной',
];
}
/**
* @brief Получение правил валидации значений атрибутов сущности.
* @details По сути нам валидировать нечего, но если не перечислить поля
* в правилах, то GridView просто не позволит по неуказанному полю
* фильтровать.
*
* Достаточно указать тип полей. Можно все поля указать как строки, можно
* организовать валидацию и посложнее, к примеру проверять, что бы в
* качестве даты не вводили всякую похабщину, или ограничить количество
* символов в строках.
* @return array - Массив правил.
*/
public function rules()
{
return [
[['name', 'date',],'string'],
[['holiday', ],'integer'],
];
}
/**
* @brief Получение строк массива.
* @details В реальности, массив может быть получен из любого источника,
* это может быть и результат хитрого запроса к БД, может быть результат
* запроса к API веб-сайта возвращенного в любом формате (CSV, JSON, XML,
* т.д.). Для нашего примера оный метод будет возвращать статический
* массив данных.
*
* Для примера возьмём список праздников из виртуального личного
* календаря за 2021, в который добавим поле указывающее выходной день
* это или нет:
* * значение 1 - будет соответствовать выходному дню,
* * значение 0 - будет соответствовать рабочему дню,
*
* Наш список будет выглядеть так:
* name | date | holiday
* -----------------------------|--------------:|:-------:
* Новый год | 2021.01.01 | 1
* Рождество Христово | 2021.01.07 | 1
* Д.р. тёщи | 2021.02.09 | 0
* День защитника Отечества | 2021.02.23 | 1
* Международный женский день | 2021.03.08 | 1
* Днюха Ильюхи | 2021.04.15 | 0
* Днюха Таньки | 2021.04.21 | 0
* Праздник весны и труда | 2021.05.01 | 1
* День Победы | 2021.05.09 | 1
* День России | 2021.06.12 | 1
* Д.р. жены | 2021.07.29 | 0
* День свадьбы | 2021.09.01 | 0
* День народного единства | 2021.11.04 | 1
* Корпоратив | 2021.12.30 | 0
*
* Что за праздники и что за личности в таблице фигурируют нам не важно, оную
* таблицу мы просто возьмём за пример для нашего массива.
*
* При этом дату будем хранить в виде метки Unix (timestamp), для последующих
* примеров фильтрации.
* @return array - Массив данных
*/
public static function getAll()
{
return [
[
'name'=>'Новый год',
'date'=>mktime(0, 0, 0, 1, 1, 2021),
'holiday'=>1,
],
[
'name'=>'Рождество Христово',
'date'=>mktime(0, 0, 0, 1, 7, 2021),
'holiday'=>1,
],
[
'name'=>'Д.р. тёщи',
'date'=>mktime(0, 0, 0, 2, 9, 2021),
'holiday'=>0,
],
[
'name'=>'День защитника Отечества',
'date'=>mktime(0, 0, 0, 2, 23, 2021),
'holiday'=>1,
],
[
'name'=>'Международный женский день',
'date'=>mktime(0, 0, 0, 3, 8, 2021),
'holiday'=>1,
],
[
'name'=>'Днюха Ильюхи',
'date'=>mktime(0, 0, 0, 4, 15, 2021),
'holiday'=>0,
],
[
'name'=>'Днюха Таньки',
'date'=>mktime(0, 0, 0, 4, 21, 2021),
'holiday'=>0,
],
[
'name'=>'Праздник весны и труда',
'date'=>mktime(0, 0, 0, 5, 1, 2021),
'holiday'=>1,
],
[
'name'=>'День Победы',
'date'=>mktime(0, 0, 0, 5, 9, 2021),
'holiday'=>1,
],
[
'name'=>'День России',
'date'=>mktime(0, 0, 0, 6, 12, 2021),
'holiday'=>1,
],
[
'name'=>'Д.р. жены',
'date'=>mktime(0, 0, 0, 7, 29, 2021),
'holiday'=>0,
],
[
'name'=>'День свадьбы',
'date'=>mktime(0, 0, 0, 9, 1, 2021),
'holiday'=>0,
],
[
'name'=>'День народного единства',
'date'=>mktime(0, 0, 0, 11, 4, 2021),
'holiday'=>1,
],
[
'name'=>'Корпоратив',
'date'=>mktime(0, 0, 0, 12, 30, 2021),
'holiday'=>0,
],
];
}
/**
* @brief Получение отфильтрованных строк массива.
* @details Это как раз и есть основной метод получения данных. И именно
* в этом методе мы и опишем как фильтровать наши строки.
*
* В реальности источники данных могут поддерживать фильтрацию, к примеру
* LDAP в запросах позволяют указывать фильтры, различные API так же
* позволяют в своих запросах указывать параметры фильтрации. Именно в этом
* методе и нужно формировать запросы с учётом фильтров и получать данные
* из нужного источника с использованием сформированного запроса.
*
* В нашем случае мы имеем обычный массив, поэтому мы без всяких затей в
* цикле пройдёмся по нему, то что не подходит под наш фильтр мы отбросим,
* а то что подходит добавим в результирующий массив, который вернём в
* качестве результата.
* @param array $params - параметры фильтрации, обычно их генерирует GridView.
* Параметры фильтрации содержатся в подмассиве $params[Имя_модели]
* в виде значений 'поле'=>'фильтр'.
* @return array - Массив данных.
*/
public function find($params=NULL)
{
/**
* Загрузим параметры фильтрации в модель. Если не сделать этого, то при
* применении фильтра поля фильтров останутся пустыми.
*
* Так же это позволит нам использовать значения атрибутов объекта,
* не прибегая к массиву параметров, для выяснения значений полей
* фильтрации.
*/
$this->load($params);
// Получим наш массив
$request=static::getAll();
// Если параметры фильтрации пусты, то просто вернём весь массив
if(empty($params)) return $request;
// Определим пустой массив результатов, в него мы будем добавлять
// отфильтрованные строки.
$res=[];
// Распарсим наш массив и без всяких затей проверим каждую строку на
// соответствие условиям фильтра
foreach($request as $row) {
// Отбросим все строки, по попадающие под фильтр имени
if($this->name != "") {
/*
* Тут отражена проблема кириллицы. Для регистронезависимого
* поиска нужно оба значения (фильтр и значение поля) привести
* к одному регистру, функция strtolower не умеет делать этого
* для кириллических букв, поэтому пришлось к нижнему регистру
* привести текст при помощи функции mb_strtolower.
*
* Если вам не нужен регистронезависимый поиск.. то преобразование
* регистра можно убрать.
*
* Сам по себе фильтр по полю name применяется простым поиском
* вхождение строки, если строка не найдена.. то пропускаем
* итерацию цикла, и данная строка не попадёт в результирующий
* массив.
*/
if(strpos(mb_strtolower($row["name"]),mb_strtolower($this->name))===false)
continue;
};
// Отбросим все строки, не попадающие под фильтр выходного дня
if($this->holiday != "") {
if($row["holiday"]!=$this->holiday) continue;
};
// Фильтрация даты... мы без всяких экивоков преобразуем дату в строку и
// поищем фильтруемое значение в уже полученной строке.
if($this->date!="") {
if(strpos(date(static::DATE_FMT, $row["date"]),$this->date)===false)
continue;
};
// Если все проверки прошли, то добавим строку к результирующему
// массиву.
$res[]=$row;
}
// Вернём то что осталось от фильтрации.
return $res;
}
/**
* @brief Подготовка фильтрации строк, возвращает готовый к употреблению
* провайдер.
* @details Именно этот метод будет возвращать сформированный провайдер для
* значения dataProvider виджета GridView.
* @param array $params - параметры фильтрации, обычно их генерирует
* GridView.
* @return mixed - Провайдер данных.
*/
public function search($params)
{
// Сформируем провайдер
$dataProvider = new \yii\data\ArrayDataProvider([
// В качестве модели укажем метод, который вернёт нам нужный массив,
// с уже отфильтрованными строками.
'allModels' => $this->find($params),
// Включим пагинацию.
'pagination' => [
'pageSize' => static::$pageSize,
],
// При желании пагинацию можно отключить совсем
// 'pagination' => false,
// Если пагинация отключена, то можно отключить и подсчёт количества
// строк
// 'totalCount' => 0,
/*
* Опишем, как сортировать наш массив. Саму сортировку на себя возьмёт
* провайдер, нам же нужно сказать ему по каким правилам, какие поля
* сортировать. Без этого описания GridView не предоставит нам
* возможности сортировать наши данные.
*/
'sort' => [
'defaultOrder'=>[ 'name'=> SORT_ASC, ],
'attributes' => [
'name' => [
'asc' => ['name' => SORT_ASC],
'desc' => ['name' => SORT_DESC],
'default' => SORT_DESC,
],
'date' => [
'asc' => ['date' => SORT_ASC],
'desc' => ['date' => SORT_DESC],
'default' => SORT_DESC,
],
'holiday' => [
'asc' => ['holiday' => SORT_ASC, 'name' => SORT_ASC],
'desc' => ['holiday' => SORT_DESC, 'name' => SORT_ASC],
'default' => SORT_DESC,
],
],
],
]);
/*
* Это проверка значений полей фильтров, если что-то введено неверно, то
* GridView выведет ошибку в соответствующем поле. Это нужно если вы
* проверяете, что пользователь вводит в качеств фильтра. К примеру в
* нашем случае в поле holiday требуется вводить число, и если будет
* введено что-то иное, то пользователь получит сообщение об ошибочном
* значении.
*
* Если вам не требуется проверка вводимых значений, то ниже приведенное
* условие можно закомментировать.
*/
if (!$this->validate()) return $dataProvider;
return $dataProvider;
}
};
Файл представления нашего массива:
Код: Выделить всё
<?
/**
* @file protected/views/test/index.php
* @ingroup Views
* @brief Представление
* @author ladserg
* @date 2021/07/11
* @version $Id: $
*/
use app\models\samples\ArraySample;?>
<?=\yii\grid\GridView::widget([
/**
* Модель фильтрации. Если её не указать, то поля с фильтрами не отобразятся,
* При этом параметры фильтрации будут передаваться модели.
*
* Если не указать модель фильтрации, то виджет не сможет получить метки
* атрибутов, и в заголовках колонок будет использоваться имена полей массива,
* а не человекочитаемые названия.
*/
'filterModel' => $searchModel,
'dataProvider' => $provider,
// Показывать грид, когда нет результатов?
// П.С. если колонки не определить, то при отсутствии результатов
// не отображаются названия колонок и поля фильтров.
'showOnEmpty' => true,
// Отображать заголовок таблицы?
'showHeader' => true,
// Отображать футер таблицы?
'showFooter' => false,
/*
* Строка-шаблон вывода элементов грида. Например:
* 'layout' => "{pager}\n{summary}\n{items}\n{pager}",
*
* При отключенной пагинации, и подсчёте количества строк, можно убрать
* лишнее раскомментировав строку ниже.
*/
// 'layout' => '{items}',
// Или переместить информацию о показанных записях вниз, для личного удобства:
'layout' => "{items}\n{summary}\n{pager}",
'caption' => '<h2>Пример вывода и фильтрации строк массива</h2>',
'columns' => [
[
'class' => \yii\grid\SerialColumn::className(),
'header' => '№',
'contentOptions' => [
'style' => 'min-width:30px;width:30px;',
'align' => 'right'
],
],
['attribute' => 'name',],
['attribute' => 'date',
'contentOptions' => [
'align' => 'right',
'style' => 'width: 100px; white-space: normal;',
],
'value' => function($data) {
return date(ArraySample::DATE_FMT, $data['date']);
},
],
['attribute' => 'holiday',
// Что бы не путать пользователя ноликами и единичками, дадим ему в
// качестве фильтра выпадающее меню с человекочитаемыми значениями.
'filter' => [0 => 'Нет', 1 => 'Да',],
'contentOptions' => [
'align' => 'center',
'style' => 'width: 20px; white-space: normal;',
],
'value' => function($data) {
return $data['holiday']==1?'Да':'Нет';
},
],
],
]);?>
Контроллер:
Код: Выделить всё
<?
/**
* @file protected/controllers/TestController.php
* @brief Класс @ref app::controllers::TestController "TestController"
* @author ladserg
* @date 2021/07/11
* @version $Id: $
*/
namespace app\controllers;
use yii\web\Controller;
use app\models\samples\ArraySample;
use Yii;
/**
* @class TestController
* @ingroup Controllers
* @brief Контроллер @ref app::controllers::TestController "/test"
*/
class TestController extends Controller
{
/**
* @brief Событие по умолчанию.
*/
public function actionIndex()
{
$data['queryParams']=Yii::$app->request->queryParams;
// В качестве модели поиска будет выступать объект созданной нами модели
// работы с массивами.
$data['searchModel']=new ArraySample();
// Провайдер для GridView сформирует метод search исходя из параметров
// запроса
$data['provider'] = $data['searchModel']->search($data['queryParams']);
return $this->render('index', $data);
}
}