Invalid argument supplied for foreach() -- When trying to make a query on a SearchModel - Yii2

69 views Asked by At

I was trying to make a search model for a mini Geo location system. Whenever I tried to get the data sorted by directly calling the GeoData model it works unless we try to sort.

But when we try to use the CustomMade SearchModel it sends : Invalid argument supplied for foreach()

Here is the SearchModel:

<?php namespace app\models;
use Yii;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use app\models\GeoData;
/**
 * This is the ActiveQuery class for [[GeoData]].
 *
 * @see GeoData
 */
class GeoDataSearch extends GeoData
{
    const TODOS = 1;
    const FECHA = 2;
    const ENVIADO_POR = 3;

    public function rules()
    {
         return [
            [['latitude', 'longitude'], 'required'],
            [['latitude', 'longitude', 'accuracy', 'speed', 'betterlocation'], 'number'],
            [['device_id', 'active', 'sended', 'mobildate', 'created_at', 'updated_at', 'created_by'], 'integer'],
            [['created_by'], 'exist', 'skipOnError' => true, 'targetClass' => User::class, 'targetAttribute' => ['created_by' => 'id']],
        ];
    }
    /*public function active()
    {
        return $this->andWhere('[[status]]=1');
    }*/

    public function scenarios()
    {
        return Model::scenarios();
    }
    public function search($params)
    {
        $query = GeoData::find()->where(['created_by' => $params])->all();

            $dataProvider = new ActiveDataProvider([
                'query' => $query,
                'sort' => [
                    'defaultOrder' => [
                        'created_at' => SORT_DESC,
                    ]
                ]
                
            ]);
        
        $dataProvider->sort->attributes['created_at'] = [
            'asc' => ['created_at' => SORT_ASC],
            'desc' => ['created_at' => SORT_DESC]
        ];
        $dataProvider->sort->attributes['created_by'] = [
            'asc' => ['created_by' => SORT_ASC],
            'desc' => ['created_by' => SORT_DESC]
        ];
        $dataProvider->sort->attributes['geo_id'] = [
            'asc' => ['geo_id' => SORT_ASC],
            'desc' => ['geo_id' => SORT_DESC]
        ];

        $this->load($params);
   
        $query->andFilterWhere([
            'geo_id' => $this->geo_id,
            'latitude' => $this->latitude,
            'longitude' => $this->longitude,
            'accuracy' => $this->accuracy,
            'speed' => $this->speed,
            'device_id' => $this->device_id,
            'betterlocation' => $this->betterlocation,
            'active' => $this->active,
            'mobiledate' => $this->mobildate,
            'sended' => $this->sended,
            'updated_at' => $this->updated_at,
            'created_at' => $this->created_at,
            'created_by' => $this->created_by,
        ]);

        return $dataProvider;

    }

}

And the controller (The code that is commented works but it won't allow me to use the GridView nor filter or Sort:

public function actionView($id)
    {
        $title = "Ver Historial";
        $id = $_GET['id'];
        $searchModel = new GeoDataSearch;
        
        $dataProvider = $searchModel->search($id);
        return $this->render(
            'view',
            [
                'title' => $title,
                'searchModel' => $searchModel,
                'dataProvider' => $dataProvider
        ]);
/*
        if (Yii::$app->user->can(AuthItem::ROLE_ADMINISTRATOR))
        {
        $id = $_GET['id'];
        $title = "Ver Historial";
        $model = new GeoData;
        $dataProvider = $model::findAll(['created_by'=> $id]); 
        return $this->render(
            'view',
            [
                'title' => $title,
                'dataProvider' => $dataProvider,
            ]
            );
        } else {
            throw new ForbiddenHttpException('Access denied for user '.Yii::$app->user->identity->id);
        }
   */
    }

Any Suggestion will be really appreciated!

1

There are 1 answers

0
bpanatta On

First your GeoDataSearch search() function should look like this:

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

    $dataProvider = new ActiveDataProvider([
        'query' => $query,
        'sort' => [
            'defaultOrder' => [
                'created_at' => SORT_DESC,
            ]
        ]
        
    ]);

    $this->load($params);

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

    $query->andFilterWhere([
        'geo_id' => $this->geo_id,
        'latitude' => $this->latitude,
        'longitude' => $this->longitude,
        'accuracy' => $this->accuracy,
        'speed' => $this->speed,
        'device_id' => $this->device_id,
        'betterlocation' => $this->betterlocation,
        'active' => $this->active,
        'mobiledate' => $this->mobildate,
        'sended' => $this->sended,
        'updated_at' => $this->updated_at,
        'created_at' => $this->created_at,
        'created_by' => $this->created_by,
    ]);

    return $dataProvider;
}

Add a second function to GeoDataSearch model:

/**
 * @param int $creator_id ID of the creator
 * @param mixed $params
 * @return ActiveDataProvider
 */
public function searchByCreator($creator_id, $params)
{
    // This will verify if the $params['GeoDataSearch'] index exists
    if ( isset( $params['GeoDataSearch'] ) ) {
        $params['GeoDataSearch']['created_by'] = $creator_id;
    } else {
        // Create the index if it doesn't exist
        $params['GeoDataSearch'] = ['created_by' => $creator_id];
    }

    return $this->search($params);
}

Update your actionView:

public function actionView($id)
{
    $title = "Ver Historial";
    $searchModel = new GeoDataSearch;
    // The $id param is already receiving the $_GET['id']
    $dataProvider = $searchModel->searchByCreator($id, Yii::$app->request->queryParams);

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

Don't setup user validation in the actionView, use AccessControl instead.


Explaining

DataProvider are for easy data pagination and sorting (View docs).

Querys filters the data for the DataProvider manipulation.

The search method is a convention for attaching query filters to a DataProvider in a single place for better reuse. So you should be looking to have a generic search method that can be reused on multiple scenarios.

In this example, the GeoDataSearch->search() method is used for searching multiple GeoData models by passing any parameter you want, so you will pass an array to it, most likely with the data from front-end.

If you are using Yii2 ActiveForm inputs (with GET method set) in your front-end, on GeoDataSearch fields. For example:

<?= $form->field($searchModel, 'speed')->textInput(); ?>

This will allow to filter the results by the speed attribute. When a user submit this form, your back-end will get the following content from Yii::$app->request->queryParams:

[
    'GeoDataSearch' => [
        'speed' => '10.00'
    ]
]

You should use this on the search method, that will filter all GeoData by their speed.

Important to notice that the speed attribute will be a index on GeoDataSearch. This is the way Yii2 builds params with their core form methods and affects the models load() method, and the reason that the implementation on searchByCreator seems a little convoluted.

Back to your case,

You need to filter by the creator. Since this needs to always be attached to the search query params, I recommend you to create a new method (searchByCreator), which consumes the already existing search method, force the created_by attribute, all while keeping the other filters alive.