yii2: Сортировка и фильтр gridview по связанным и вычисляемым полям

yii2 gridview настройка фильтра и сортировкиСортировка и фильтр gridview по вычисляемым или связанным полям не является сложной задачей, но она требует понимание принципов устройства модели в Yii 2.0.

Для тех, кто любит пощупать рабочий код руками, есть приложение. Ставится как и приложение Yii 2 basic. Миграция создаст нужные таблицы.

Все самое интересное в models/Person.php и models/PersonSearch.php.

Итак, приступим…

Исходные данные

Допустим, что мы имеем следующие связанные таблицы в базе данных:

Подготовка

Воспользуемся замечательным gii для генерации моделей и crud. В итоге у нас должны получиться следующие классы:

  1. Person: Модель для таблицы tbl_person;
  2. PersonSearch: Класс поиска и фильтра для модели Person;
  3. Country: Модель для таблицы tbl_country;
  4. CountrySearch: Класс поиска и фильтра для модели Country.

План действий

Рассмотрим 3 варианта использования gridview в представлении index для класса Person.

Вариант первый: Сортировка и фильтр по вычисляемому полю

Настроим сортировку и фильтрацию по полю fullName класса Person, объединяющему first_name и second_name, разделенные пробелами.

Вариант второй: Сортировка и фильтр по вычисляемому полю из связанной таблицы

Добавим поле Страна в gridview класса Person из таблицы tbl_country, используя связь по ключу country_id и организуем сортировку и фильтр по этому полю.

Вариант третий: связанные записи из этой же таблицы

Для примера, добавим поле parentName, для отображения связанной записи этой же таблицы Person и настроим сортировку и фильтр.

Вариант первый

Шаг 1

Добавим геттер для поля fullName в модель Person:

Шаг 2

Добавим атрибут fullName в класс PersonSearch и настроим правила:

Шаг 3

Добавим новое поле в виджет gridview:

Готово! Теперь в gridview появилось синтетическое поле ФИО, по которому возможна сортировка и фильтрация.

Вариант 2

Шаг 1

Нужно убедиться, что в модели Person описана связь с моделью Country. Так же, желательно, описать геттер countryName.

Шаг 2

Добавим атрибут countryName в класс PersonSearch и настроим правила:

Шаг 3

Добавляем в gridview столбец для поля countryName:

Ура! Теперь у нас в gridview есть столбец с названием страны, которое берется из связанной таблицы.

Вариант третий

Шаг 1

Добавим модели Person связь к самой себе и геттер для нового атрибута ParentName:

Шаг 2

Добавим атрибут parentName в класс PersonSearch и настроим правила:

Шаг 3

Добавим в класс PersonSearch метод addCondition:

Шаг 4

Добавим столбец для отображения поля parentName в gridview:

Готово! Мы получили дополнительный столбец для поля, ссылающегося на связанную запись в этой же таблице с возможностью с работающим фильтром и сортировкой по этому полю.

30 thoughts on “yii2: Сортировка и фильтр gridview по связанным и вычисляемым полям”

  1. В первом варианте используется addCondition(), который описывается только в варианте 3. В оригинальном английском тексте такой же косяк.

  2. // Фильтр по стране
    $query->joinWith([‘country’ => function ($q) {
    $q->where(‘tbl_country.country_name LIKE «%’ . $this->countryName . ‘%»‘);
    }]);

    А конкретнее строку:
    $q->where(‘tbl_country.country_name LIKE «%’ . $this->countryName . ‘%»‘);

    лучше заменить на:
    $q->andFilterWhere([‘like’, ‘tbl_country.country_name’, $this->countryName]);

    Попробуйте в фильтре этого поля, вбить значение, выполнить поиск, потом значение стереть, и выполнить поиск.

  3. А как сделать чтоб вместо
    $query->joinWith([‘country’ => function ($q) {
    $q->where(‘tbl_country.country_name LIKE «%’ . $this->countryName . ‘%»‘);
    }]);

    Сделать left join так как в моем случае там может быть null?

  4. Что за классы PersonSearch, CountrySearch. По имени понятно, что для поиска. Где их размещать? И как вызывать?

  5. Подскажите пожалуйста, нигде найти не могу ответ. У меня таблица также берет из другой данные ФИО и точно также её обрабатываю. Проблема в том, что мне нужно фильтровать данные по отделу. А он находится через ещё одну таблицу:
    основная таблица -> сотрудники -> отделы.
    Поэтому я смог сделать только сортировку по вашему образцу и то она сортирует не как строки, а как id, т.к. в таблице с сотрудниками id’шники из таблицы отделов, в которой находятся названия отделов.

  6. У меня во втором варианте на 3 шаге не выводилось поле поиска, пока ‘country_name’ не заменил на
    [‘attribute’ => ‘countryName’,
    ‘value’ => ‘country.name’],

      1. Подскажите пожалуйста. Если сделать как у Вас в статье
        return $this->country->country_name;
        то я получаю ошибку:
        PHP Notice – yii\base\ErrorException
        Trying to get property of non-object
        Я сделал так и все заработало:
        return $this->country[‘country_name’];
        Подскажите, с чем это связано?

        1. Выложите код модели целиком куда-нибудь.

          Добавил в шапке обновление. Может быть поможет код приложения целиком?

  7. Фильтрация по полному имени в данном примере у меня не работает.
    Чтобы фильтрация нормально работала переписал
    // Фильтр по полному имени
    $query->andWhere(‘first_name LIKE «%’ . $this->fullName . ‘%» ‘ .
    ‘OR last_name LIKE «%’ . $this->fullName . ‘%»‘
    );

    на вот это
    $query->andFilterWhere([‘like’, ‘concat_ws(» «,name,surname,patronymic)’, $this->fullName]);

    1. Да, вариант который приведен в статье, работает только если искать либо first_name, либо last_name, но не оба вместе. Ваш вариант лучше. Только там что-то с кавычками в разделителе concat_ws. Нужно заменить на обычные » «

  8. Кто сможет подсказать как сделать фильтр по действительно вычисляемому полю, к примеру есть записи в 2-х таблицах связь один ко многим. Так вот множество записей обрабатываются на пыхе, (обработка сложная поэтому на sql реализовать можно но получится страницы 2 формата а4) так вот вся эта обработка засунута в геттер модели, и возвращает число, вопрос как фильтровать по этому полю?
    Не прибегая к ArrayDataProvider или к фильтрации на фронте через дататаблю…

    1. Третий вариант — добавить поле для хранения нужного значения и обновлять его при изменении зависимости.

  9. Возможно не сюда, но все же….

    как через GridView выводить динамическое количество колонок, а именно количество колонок будет будет зависеть от фиксированные данные к примеру 5 колонок + по колонке на каждый день периода (2016.07.25 — 2016.08.31 = 6 колонок).

    Данные хранятся в свойстве модели в массиве.
    Собственно вопрос можно ли как нибудь развернуть это свойство в стандартном GridView, если да то как?

    1. Варианты которые я нашел, отрисовать табличку руками.
    2. Написать свой GridView на основе существующего (со своим пасьянсом и куртизанками).
    3. Отказаться от GridView и забирать выхлоп в dataTable (все ровно она потом обрабатывает табличку, вариант плох в виду плохого знания JS)
    4. Найти другой GridView…

    4 сегодня вечерком займусь есть 2 наметки…

  10. // ‘id_category2’,
    [
    ‘attribute’=>’category2’,
    ‘value’ =>’category2.name’,
    ‘filter’ => Html::activeDropDownList($searchModel, ‘id_category2’, ArrayHelper::map(Category2::find()->all(), ‘id’, ‘name’), [ ‘class’ =>’form-control’,’prompt’ => ‘<<>>’]),
    ],
    если этот код в ставить в GridView, то можно получить фильтр с выпадающим списком

  11. Мне кажется, или подобный код уязвим для SQL-инъекций ?

    $query->andWhere(‘first_name LIKE «%’ . $this->fullName . ‘%» ‘ .
    ‘OR last_name LIKE «%’ . $this->fullName . ‘%»‘
    );

  12. Отличная статья!
    Подскажите, пожалуйста, как сделать фильтр, который будет проверять наличие или отсутствие данных в определенном поле таблицы?
    Т.е. именно фильтровать записи по условиям:
    – поле field пустое (null, », etc)
    – поле field НЕ пустое
    Как такое можно реализовать в методе search?
    Какие условия добавить в запрос?

  13. Во втором варианте в моделе Person нужно было добавить еще переменную:

    public $countryName

    и в виджете GridView атрибут и значение:

    ‘contryName’ => [
    ‘attribute’ => ‘countryName’,
    ‘value’ => function($data) {
    return $data->country->name;
    },

  14. Ребят я только одного не понимаю, как вы передаете $query вначале, потом его правите и отдаете ActiveDataProvider… Может все таки нужно передавать ссылкой?
    $dataProvider = new ActiveDataProvider([
    ‘query’ => $query,
    ]);

    ‘query’ => &$query,

Добавить комментарий

Ваш e-mail не будет опубликован. Обязательные поля помечены *