Фильтрация и сортировка по виртуальному полю модели - как?

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
Ответить
Matvik
Сообщения: 194
Зарегистрирован: 2013.06.21, 02:32

Фильтрация и сортировка по виртуальному полю модели - как?

Сообщение Matvik »

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

Код: Выделить всё

public function getStatus() {
        $today = date('Y-m-d');
        $begin = $this->date_begin;
        $end = $this->date_end;
        if ($end < $today) {
            return self::STATUS_ENDED;
        }
        if ($begin <= $today && $end >= $today) {
            return self::STATUS_IN_PROCESS;
        }
        if ($begin > $today) {
            return self::STATUS_FUTURE;
        }
    }
Есть еще метод, который возвращает названия:

Код: Выделить всё

public static function getStatuses() {
        return [
            self::STATUS_ENDED => Yii::t('app', 'Завершено'),
            self::STATUS_IN_PROCESS => Yii::t('app', 'Проходит сейчас!'),
            self::STATUS_FUTURE => Yii::t('app', 'Будущее'),
        ];
    }
Соответственно, в таблице вывод просто сделать:

Код: Выделить всё

$statuses = Event::getStatuses();
...
[
        'attribute' => 'status',
        'format' => 'text',
        'content' => function($data) use ($statuses) {
            return $statuses[$data->status];
        },
],
Как реализовать сортировку и фильтрацию в таблице по такому полю? В примерах и статьях встречаются более простые поля, например, ФИО, или простая ф-ция от поля. А тут уже себе голову сломал.
Аватара пользователя
Alexum
Сообщения: 683
Зарегистрирован: 2016.09.26, 10:00

Re: Фильтрация и сортировка по виртуальному полю модели - как?

Сообщение Alexum »

Как вариант - переделать searchModel, чтобы возвращала не active а arrayDataProvider. Т.е. в поисковой модели вы выполняете запрос к БД, затем вычисляете status. Результаты оформляете в массив, который скармливаете в 'allModels' в ArrayDataProvider.
Matvik
Сообщения: 194
Зарегистрирован: 2013.06.21, 02:32

Re: Фильтрация и сортировка по виртуальному полю модели - как?

Сообщение Matvik »

Alexum писал(а): 2017.05.01, 12:55 Как вариант - переделать searchModel, чтобы возвращала не active а arrayDataProvider. Т.е. в поисковой модели вы выполняете запрос к БД, затем вычисляете status. Результаты оформляете в массив, который скармливаете в 'allModels' в ArrayDataProvider.
Но тогда каждый раз будут тянутся все существующие записи, что не есть хорошо.
phpshko
Сообщения: 260
Зарегистрирован: 2015.03.21, 02:49

Re: Фильтрация и сортировка по виртуальному полю модели - как?

Сообщение phpshko »

Можно вычислять средствами sql чтобы не выгребать все данные.
пример, возможны опечатки

Код: Выделить всё

$today = date('Y-m-d');

$query->addSelect(['status' => new Expression('IF (date_end < '.$today.', '.self::STATUS_ENDED.', IF(date_begin <= '.$today.' AND date_end >= '.$today.', '.self::STATUS_ENDED.', IF(date_begin > '.$today.', '.self::STATUS_FUTURE.', '.self::STATUS_UNKNOWN.')))')]);
а дальше через order by сортировать, having фильтровать
phpshko
Сообщения: 260
Зарегистрирован: 2015.03.21, 02:49

Re: Фильтрация и сортировка по виртуальному полю модели - как?

Сообщение phpshko »

или добавить колонку status и каждый день пересчитывать его кроном (можно одним запросом с ifами), тогда сортировка/фильтрация с индексом будет быстрее чем вариант выше, но для новых записей это тоже нужно учитывать и проставлять статус при записи/изменении
Matvik
Сообщения: 194
Зарегистрирован: 2013.06.21, 02:32

Re: Фильтрация и сортировка по виртуальному полю модели - как?

Сообщение Matvik »

phpshko писал(а): 2017.05.01, 16:45 Можно вычислять средствами sql чтобы не выгребать все данные.
пример, возможны опечатки

Код: Выделить всё

$today = date('Y-m-d');

$query->addSelect(['status' => new Expression('IF (date_end < '.$today.', '.self::STATUS_ENDED.', IF(date_begin <= '.$today.' AND date_end >= '.$today.', '.self::STATUS_ENDED.', IF(date_begin > '.$today.', '.self::STATUS_FUTURE.', '.self::STATUS_UNKNOWN.')))')]);
а дальше через order by сортировать, having фильтровать
Оу, мне нравится, я собственно что-нибуть такое и предполагал не не знал, можно ли так. Пропробую, спасибо!
Matvik
Сообщения: 194
Зарегистрирован: 2013.06.21, 02:32

Re: Фильтрация и сортировка по виртуальному полю модели - как?

Сообщение Matvik »

Кстати, а как можна в самом gridview получить выбранное дополнительно поле? Без использования виртуального поля модели. Оно уже есть в результате запроса, следовательно, нету никакого смысла в дополнительних расчетах в геттере модели.
Пробую убирать геттер и прописать

Код: Выделить всё

 'content' => function($data) use ($statuses) {
      return $statuses[$data['status']];
 },
Но матерится. То есть, как можно в контексте колонки gridview получить выбранное дополнительно поле (в даном случае, status)?
Matvik
Сообщения: 194
Зарегистрирован: 2013.06.21, 02:32

Re: Фильтрация и сортировка по виртуальному полю модели - как?

Сообщение Matvik »

А вот при фильтрации возникает проблемма:
если использовать

Код: Выделить всё

$query->andFilterHaving([
     'status' => $this->status,
]);
то при попытке фильтрации выдает ошибку
SQLSTATE[42S21]: Column already exists: 1060 Duplicate column name 'id',
а если

Код: Выделить всё

$query->andFilterWhere([
     'status' => $this->status,
]);
то
SQLSTATE[42S22]: Column not found: 1054 Unknown column 'status' in 'where clause',
потому что в таком случае при подсчете количества строк в результате почему-то дополнительное поле не входит в запрос.

Сам запрос формируется так:

Код: Выделить всё

$query = Event::find()->joinWith(['tour', 'tour.
        $today = date('Y-m-d');
        $query->addSelect([
            '*',
            'status' => new \yii\db\Expression(
                    'IF (date_end < "'.$today.'", "'.self::STATUS_ENDED.'", IF(date_begin <= "'.$today.'" AND date_end >= "'.$today.'", "'.self::STATUS_IN_PROCESS.'", IF(date_begin > "'.$today.'", "'.self::STATUS_FUTURE.'", NULL)))'
                    )
            ]);
и никакого дублирования id нет
Matvik
Сообщения: 194
Зарегистрирован: 2013.06.21, 02:32

Re: Фильтрация и сортировка по виртуальному полю модели - как?

Сообщение Matvik »

UPDATE:
Паралельно выяснилось, что почему-то в таблицу попадает id связанной модели, а не главной. Проставил алиасы в joinWith - все равно. Пришлось добавить такое

Код: Выделить всё

'id' => self::tableName() . '.id',
Весь код метода поиска теперь такой:

Код: Выделить всё

public function search($params)
    {
        $query = Event::find()->joinWith(['tour t', 'tour.type tt']);
        
        $today = date('Y-m-d');
        $query->addSelect([
            '*',
            'id' => self::tableName() . '.id',
            'status' => new \yii\db\Expression(
                    'IF (date_end < "'.$today.'", "'.self::STATUS_ENDED.'", IF(date_begin <= "'.$today.'" AND date_end >= "'.$today.'", "'.self::STATUS_IN_PROCESS.'", IF(date_begin > "'.$today.'", "'.self::STATUS_FUTURE.'", NULL)))'
                    )
            ]);
        // add conditions that should always apply here

        $dataProvider = new ActiveDataProvider([
            'query' => $query,
        ]);
        
        $dataProvider->setSort([
            'attributes' => [
                'id' => [
                    'asc' => [self::tableName() . '.id' => SORT_ASC],
                    'desc' => [self::tableName() . '.id' => SORT_DESC],
                ],
                'date_begin',
                'date_end',
                'tour_id' => [
                    'asc' => ['t.name' => SORT_ASC],
                    'desc' => ['t.name' => SORT_DESC],
                ],
                'tourType' => [
                    'asc' => ['tt.name' => SORT_ASC],
                    'desc' => ['tt.name' => SORT_DESC],
                ],
                'status' => [
                    'asc' => ['status' => SORT_ASC],
                    'desc' => ['status' => SORT_DESC],
                ]
            ],
            'defaultOrder' => [
                'id' => SORT_DESC, 
            ]
        ]);

        $this->load($params);

        if (!$this->validate()) {
            // uncomment the following line if you do not want to return any records when validation fails
            // $query->where('0=1');
            return $dataProvider;
        }

        // grid filtering conditions
        $query->andFilterWhere([
            'id' => $this->id,
        ]);
        
        if ($this->tourType) {
            $query->andFilterWhere([
                'tt.id' => $this->tourType,
            ]);
        }
        
        $query->andFilterWhere(['>=', 'date_end', $this->date_begin]);
        $query->andFilterWhere(['<=', 'date_begin', $this->date_end]);
        
        $query->andFilterWhere(['like', 't.name', $this->tour_id]);
        
        $query->andFilterHaving([
            'status' => $this->status,
        ]);

        return $dataProvider;
    }
И все равно, при попытке поиска вылезает ошибка:

Код: Выделить всё

SQLSTATE[42S21]: Column already exists: 1060 Duplicate column name 'id'
The SQL being executed was: SELECT COUNT(*) FROM (SELECT *, `chr_events_events`.`id` AS `id`, IF (date_end < "2017-05-02", "ended", IF(date_begin <= "2017-05-02" AND date_end >= "2017-05-02", "in_process", IF(date_begin > "2017-05-02", "future", NULL))) AS `status` FROM `chr_events_events` LEFT JOIN `chr_events_tours` `t` ON `chr_events_events`.`tour_id` = `t`.`id` LEFT JOIN `chr_events_tours_types` `tt` ON `t`.`type_id` = `tt`.`id` HAVING `status`='ended') `c`
И где тут блин дублирование id, если указаны алиасы на приджойненные таблицы?
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Фильтрация и сортировка по виртуальному полю модели - как?

Сообщение ElisDN »

Matvik писал(а): 2017.05.02, 03:17 И где тут блин дублирование id, если указаны алиасы на приджойненные таблицы?
Дублирование в SELECT *, `chr_events_events`.`id` AS `id`.
Matvik
Сообщения: 194
Зарегистрирован: 2013.06.21, 02:32

Re: Фильтрация и сортировка по виртуальному полю модели - как?

Сообщение Matvik »

ElisDN писал(а): 2017.05.02, 07:42
Matvik писал(а): 2017.05.02, 03:17 И где тут блин дублирование id, если указаны алиасы на приджойненные таблицы?
Дублирование в SELECT *, `chr_events_events`.`id` AS `id`.
А как тогда сделать, чтоб id основной таблицы выбиралось нормально? Если это дело убрать, то в таблице неправильная айдишка в колонка id, и кнопка апдейта и удаления тоже неправильный id принимает в url
Аватара пользователя
ElisDN
Сообщения: 5845
Зарегистрирован: 2012.10.07, 10:24
Контактная информация:

Re: Фильтрация и сортировка по виртуальному полю модели - как?

Сообщение ElisDN »

Matvik писал(а): 2017.05.02, 22:03 А как тогда сделать, чтоб id основной таблицы выбиралось нормально?
Вместо * все поля вручную перечислить.
Ответить