AR getModels() и select [Решено]

Общие вопросы по использованию второй версии фреймворка. Если не знаете как что-то сделать и это про Yii 2, вам сюда.
grig
Сообщения: 41
Зарегистрирован: 2014.12.21, 23:42

AR getModels() и select [Решено]

Сообщение grig »

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

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

 $dataProvider = new ActiveDataProvider([
        'query' => $query,
        ]); 
Перед этим идет выборка по различным условиям из модели пользователей. Пример:

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

 if (!empty($this->family_status))
            $query->andWhere(['marital_status' => $this->family_status]);

        if (!empty($this->have_children))
            $query->andWhere(['having_children' => $this->have_children]);

        if (!empty($this->smoker))
            $query->andWhere(['attitude_smoking' => $this->smoker]);

        if (!empty($this->drinking_habits))
            $query->andWhere(['drinking_habits' => $this->drinking_habits]);
Перед всем этим нужно работать с вычисляемым полем в БД вот так:

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

 if (!empty($this->age_from) || !empty($this->age_to))
        {
            $query->select(['*', new \yii\db\Expression('(YEAR(current_date())-YEAR(date_of_birth)) as age')]);
        }
        if (!empty($this->age_from))
            $query->andHaving('age >= :age_from', [':age_from' => $this->age_from]);

        if (!empty($this->age_to))
            $query->andHaving('age <= :age_to', [':age_to' => $this->age_to]);
Так вот метод getModels() перебивает начало условия и вместо ("SELECT *, (YEAR(current_date())-YEAR(date_of_birth)) as age" Оставляет только SELECT *... и дальше все where. Соответственно при попытке выполнить вот это:

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

$query->andHaving('age >= :age_from', [':age_from' => $this->age_from]);
возникает ошибка, мол не знаю я такого поля, как age. В общем суть вопроса: можно ли как-нибудь обойти это, не убирая из кода вызовы getModels(), которые используются повсеместно.
Последний раз редактировалось grig 2015.03.04, 14:12, всего редактировалось 1 раз.

astronin
Сообщения: 606
Зарегистрирован: 2012.01.30, 17:46

Re: AR getModels() и select

Сообщение astronin »

разберитесь с методом select(), возможно вы его не правильно записываете, посмотрите логи
getModels() тут совершенно не причем

grig
Сообщения: 41
Зарегистрирован: 2014.12.21, 23:42

Re: AR getModels() и select

Сообщение grig »

astronin писал(а):разберитесь с методом select(), возможно вы его не правильно записываете, посмотрите логи
getModels() тут совершенно не причем
Почему не причем? Если возвращать как результат сам $query, а не $dataprovider и применять all(), то все работает отлично. Если возвращать dataprovider и применять getModels(), то все ломается. На сколько я понимаю ломается в том месте, где идет работа с пагинацией. Плюс ко всему в первом сообщении я привел код, в котором присутствует мой селект.

astronin
Сообщения: 606
Зарегистрирован: 2012.01.30, 17:46

Re: AR getModels() и select

Сообщение astronin »

потому что activedataprovider вызывает тот же all(), меняя только limit и order.
в логах какие запросы формируются?
попробуйте select сформировать без звездочки, с какими-то данными и с Expression

grig
Сообщения: 41
Зарегистрирован: 2014.12.21, 23:42

Re: AR getModels() и select

Сообщение grig »

Если использовать activeDataProvider и getModels() то запрос выглядит так:

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

SELECT COUNT(*) FROM `tbl_user` WHERE role!="admin" HAVING age >= '10' 
если использовать $query->all(), то этот же запрос выглядит так:

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

SELECT `*`, (YEAR(current_date())-YEAR(date_of_birth)) as age FROM `tbl_user` WHERE role!="admin" HAVING age >= '10'
Как видно из запроса, то вся первая часть заменяется COUNT(*). Вопрос: как можно в данном случае решить проблему с вычисляемым полем?

grig
Сообщения: 41
Зарегистрирован: 2014.12.21, 23:42

Re: AR getModels() и select

Сообщение grig »

В общем более-мение разобрался. Эта проблема возникает из-за пагинации и решается отключением оной ('pagination'=>false в описании activeDataProvider). На сколько я понял, разработчики фреймворка не учли, что при постраничной навигации кроме count(*) в селекте может быть еще что-то. Хотя может я не там смотрел... В любом случае лучшего решения, чем отключить постраничную навигацию на странице где используется поиск с вычисляемым полем я не нашел. Если кто-нибудь знает как это можно не очень сложно решить не отключая пагинацию - прошу отписаться.

astronin
Сообщения: 606
Зарегистрирован: 2012.01.30, 17:46

Re: AR getModels() и select

Сообщение astronin »

отправьте issue на гитхаб

zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: AR getModels() и select

Сообщение zelenin »

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

<?php

namespace Zelenin\yii\modules\Log\models\search;

use Yii;
use yii\data\ActiveDataProvider;
use Zelenin\yii\Kit\traits\SearchTrait;
use Zelenin\yii\modules\Log\models\Request;

class RequestSearch extends Request
{
    use SearchTrait;

    public $age;

    public function rules()
    {
        return $this->getDefaultRules();
    }

    public function search($params)
    {
        $query = static::find();
        $query->select(['*', new \yii\db\Expression('(YEAR(current_date())-YEAR(datetime)) as age')]);
        $dataProvider = new ActiveDataProvider(['query' => $query]);

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

        $this->getFilters($query, [
            ['like', 'route', $this->route],
            ['user_id' => $this->user_id],
            ['like', 'ip', $this->ip]
        ]);
        return $dataProvider;
    }
}

 
без проблем работает - age выводится в гриде на всех страницах

Demon_id
Сообщения: 421
Зарегистрирован: 2011.10.29, 00:13

Re: AR getModels() и select

Сообщение Demon_id »

grig писал(а): На сколько я понял, разработчики фреймворка не учли, что при постраничной навигации кроме count(*) в селекте может быть еще что-то.
У меня есть list view с пагинацией и запросом содержащим звёздочку и экспрешн, вроде вашего. Всё работает хорошо.

grig
Сообщения: 41
Зарегистрирован: 2014.12.21, 23:42

Re: AR getModels() и select

Сообщение grig »

Demon_id писал(а):
grig писал(а): На сколько я понял, разработчики фреймворка не учли, что при постраничной навигации кроме count(*) в селекте может быть еще что-то.
У меня есть list view с пагинацией и запросом содержащим звёздочку и экспрешн, вроде вашего. Всё работает хорошо.
Если Вас не затруднит, скиньте, пожалуйста код вашей модели и код listView вашей вьюхи, чтобы сравнить.

zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: AR getModels() и select

Сообщение zelenin »

grig писал(а):
Demon_id писал(а):
grig писал(а): На сколько я понял, разработчики фреймворка не учли, что при постраничной навигации кроме count(*) в селекте может быть еще что-то.
У меня есть list view с пагинацией и запросом содержащим звёздочку и экспрешн, вроде вашего. Всё работает хорошо.
Если Вас не затруднит, скиньте, пожалуйста код вашей модели и код listView вашей вьюхи, чтобы сравнить.
я вам уже модель сверху скинул именно с вашим вариантом доп. поля.

Аватара пользователя
Insolita
Сообщения: 788
Зарегистрирован: 2011.06.06, 01:39
Контактная информация:

Re: AR getModels() и select

Сообщение Insolita »

на пагинацию можно отдельный запрос для подсчёта viewtopic.php?f=19&t=21433 может поможет

grig
Сообщения: 41
Зарегистрирован: 2014.12.21, 23:42

Re: AR getModels() и select

Сообщение grig »

zelenin писал(а): я вам уже модель сверху скинул именно с вашим вариантом доп. поля.
Только что воспроизвел ситуацию на чистом basic проекте. Попробуйте у себя этот код. Если и у вас не выдаст ошибку... В общем скидываю все, что касается данной проблемы:
миграция для модели:

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

class m150303_171520_create_table_Users extends Migration
{
    public function up()
    {
        $this->createTable('user',['id' => Schema::TYPE_PK,
                                   'name VARCHAR(45) NULL',
                                   'date_of_birth DATE NULL']);
        $this->insert('user', ['id' =>null, 'name'=>'Alex', 'date_of_birth'=>'1980-01-01']);
        $this->insert('user', ['id' =>null, 'name'=>'Max', 'date_of_birth'=>'1990-01-01']);
        $this->insert('user', ['id' =>null, 'name'=>'Vera', 'date_of_birth'=>'1995-01-01']);
    }

    public function down()
    {
        echo "m150303_171520_create_table_Users cannot be reverted.\n";

        return false;
    }

} 
Модель:

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

namespace app\models;

use Yii;

/**
 * This is the model class for table "user".
 *
 * @property integer $id
 * @property string $name
 * @property string $date_of_birth
 */
class User extends \yii\db\ActiveRecord
{
    /**
     * @inheritdoc
     */
    public static function tableName()
    {
        return 'user';
    }

    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['date_of_birth'], 'safe'],
            [['name'], 'string', 'max' => 45]
        ];
    }

    /**
     * @inheritdoc
     */
    public function attributeLabels()
    {
        return [
            'id' => 'ID',
            'name' => 'Name',
            'date_of_birth' => 'Date Of Birth',
        ];
    }
} 
Модель поиска:

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

namespace app\models;

use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use app\models\User;

/**
 * UserSearch represents the model behind the search form about `app\models\User`.
 */
class UserSearch extends User
{
    public $age_from;
    /**
     * @inheritdoc
     */
    public function rules()
    {
        return [
            [['id'], 'integer'],
            [['name', 'date_of_birth'], 'safe'],
            ['age_from', 'number']
        ];
    }

    /**
     * @inheritdoc
     */
    public function scenarios()
    {
        // bypass scenarios() implementation in the parent class
        return Model::scenarios();
    }

    /**
     * Creates data provider instance with search query applied
     *
     * @param array $params
     *
     * @return ActiveDataProvider
     */
    public function search($params)
    {
        $query = User::find();
        $query->select(['*', new \yii\db\Expression('(YEAR(current_date())-YEAR(date_of_birth)) as age')]);
        $dataProvider = new ActiveDataProvider([
            'query' => $query,
        ]);
        $this->load($params);
        if($this->age_from)
            $query->andHaving('age >= :age_from', [':age_from' => $this->age_from]);



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

        $query->andFilterWhere([
            'id' => $this->id,
            'date_of_birth' => $this->date_of_birth,
        ]);

        $query->andFilterWhere(['like', 'name', $this->name]);

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

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

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

        return $this->render('index', [
            'dataProvider' => $dataProvider,
            'model' => $searchModel
        ]);
    } 
Кусок вьюхи, в котором есть форма и выводится найденная информация (в данном случае имена)

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

 <?php
       $form = ActiveForm::begin([
      'method' => 'get',
      'action' => \yii\helpers\Url::to('/user/index'),
      'id' => 'user-search',
      'enableClientValidation' => false,
      'options' => ['class' => 'form-inline main-filter'],
      'fieldConfig' => [
          'template' => '{label}{input}{error}',
      ],
  ]);?>
    <?= $form->field($model, 'name'); ?>
    <br />
    <?= $form->field($model, 'age_from')->input('text',['onkeyup'=>'this.value=parseInt(this.value) | 0']) ?>
    <button type="submit" class="btn btn-primary">Поиск</button>
    <?php ActiveForm::end(); ?>
  <?php
      foreach($dataProvider as $value)
      {
        echo $value->name, ' ';
      }
  ?>

Demon_id
Сообщения: 421
Зарегистрирован: 2011.10.29, 00:13

Re: AR getModels() и select

Сообщение Demon_id »

действительно баг
viewtopic.php?f=27&t=22664

zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: AR getModels() и select

Сообщение zelenin »

а зачем вы в качестве датапровайдера передаете массив моделей? вы не видели никогда примеров использования?

zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: AR getModels() и select

Сообщение zelenin »

Demon_id писал(а):действительно баг
viewtopic.php?f=27&t=22664
не баг и не имеет отношения

Demon_id
Сообщения: 421
Зарегистрирован: 2011.10.29, 00:13

Re: AR getModels() и select

Сообщение Demon_id »

zelenin писал(а):а зачем вы в качестве датапровайдера передаете массив моделей? вы не видели никогда примеров использования?
проблема там не в этом. упадёт эксепш даже если сделать так:

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

$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
echo $dataProvider->totalItems; 

zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: AR getModels() и select

Сообщение zelenin »

Demon_id писал(а):
zelenin писал(а):а зачем вы в качестве датапровайдера передаете массив моделей? вы не видели никогда примеров использования?
проблема там не в этом. упадёт эксепш даже если сделать так:

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

$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
echo $dataProvider->totalItems;
в таком случае надо отдельно считать тотал. тем не менее getModels() все равно не верно употребляется.

grig
Сообщения: 41
Зарегистрирован: 2014.12.21, 23:42

Re: AR getModels() и select

Сообщение grig »

zelenin писал(а):
Demon_id писал(а):
zelenin писал(а):а зачем вы в качестве датапровайдера передаете массив моделей? вы не видели никогда примеров использования?
проблема там не в этом. упадёт эксепш даже если сделать так:

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

$dataProvider = $searchModel->search(Yii::$app->request->queryParams);
echo $dataProvider->totalItems;
в таком случае надо отдельно считать тотал. тем не менее getModels() все равно не верно употребляется.
Как это не верно? Согласно докам это и есть правильное употребление: http://www.yiiframework.com/doc-2.0/yii ... vider.html В самом верху сказано, что для получения записей для текущей страницы нужно использовать getModels(). На сколько я понимаю это используется, если по каким-то причинам не используется gridView или ListView.

zelenin
Сообщения: 10596
Зарегистрирован: 2013.04.20, 11:30

Re: AR getModels() и select

Сообщение zelenin »

grig писал(а): Как это не верно? Согласно докам это и есть правильное употребление: http://www.yiiframework.com/doc-2.0/yii ... vider.html В самом верху сказано, что для получения записей для текущей страницы нужно использовать getModels(). На сколько я понимаю это используется, если по каким-то причинам не используется gridView или ListView.
легко. getModels используется для получения моделей, а гриду требуется провайдер. Грид сам получит модели когда нужно, подставив необходимые значение (страница и лимиты).

Ответить