Yii2 REST query

Kedves Hunor picture Kedves Hunor · Aug 27, 2014 · Viewed 24.1k times · Source

Hy. I have a ProductController which extends the yii\rest\ActiveController. Question is that how can i make querys via HTTP GET request.

Like: http://api.test.loc/v1/products/search?name=iphone

And the return object will contains all products with name iphone.

Answer

Salem Ouerdani picture Salem Ouerdani · May 31, 2015

UPDATE: Apr 29 2016

This is one more approach simpler than the one I introduced in the previous update. It is always about involving the Search class generated by gii. I like using it to define and maintain all the search related logic in a single place like using custom scenarios, handle validations, or involve related models on the filtering process (like in this example). So I'm going back to my first answer :

public function actions() 
{ 
    $actions = parent::actions();
    $actions['index']['prepareDataProvider'] = [$this, 'prepareDataProvider'];
    return $actions;
}

public function prepareDataProvider() 
{
    $searchModel = new \app\models\ProductSearch();    
    return $searchModel->search(\Yii::$app->request->queryParams);
}

Then be sure that your search class is using load($params,'') instead of load($params) or alternatively add this to the model class:

class Product extends \yii\db\ActiveRecord
{
    public function formName()
    {
        return '';
    }

That should be enough to have your requests looking like:

/products?name=iphone&status=available&sort=name,-price


UPDATE: Sep 23 2015

This is the same approach but by implementing a complete & cleaner solution :

namespace app\api\modules\v1\controllers;

use yii\rest\ActiveController;
use yii\helpers\ArrayHelper;
use yii\web\BadRequestHttpException;

class ProductController extends ActiveController
{
    public $modelClass = 'app\models\Product';
    // Some reserved attributes like maybe 'q' for searching all fields at once 
    // or 'sort' which is already supported by Yii RESTful API
    public $reservedParams = ['sort','q'];

    public function actions() {
        $actions = parent::actions();
        // 'prepareDataProvider' is the only function that need to be overridden here
        $actions['index']['prepareDataProvider'] = [$this, 'indexDataProvider'];
        return $actions;
    }

    public function indexDataProvider() {
        $params = \Yii::$app->request->queryParams;

        $model = new $this->modelClass;
        // I'm using yii\base\Model::getAttributes() here
        // In a real app I'd rather properly assign 
        // $model->scenario then use $model->safeAttributes() instead
        $modelAttr = $model->attributes;

        // this will hold filtering attrs pairs ( 'name' => 'value' )
        $search = [];

        if (!empty($params)) {
            foreach ($params as $key => $value) {
                // In case if you don't want to allow wired requests
                // holding 'objects', 'arrays' or 'resources'
                if(!is_scalar($key) or !is_scalar($value)) {
                    throw new BadRequestHttpException('Bad Request');
                }
                // if the attr name is not a reserved Keyword like 'q' or 'sort' and 
                // is matching one of models attributes then we need it to filter results
                if (!in_array(strtolower($key), $this->reservedParams) 
                    && ArrayHelper::keyExists($key, $modelAttr, false)) {
                    $search[$key] = $value;
                }
            }
        }

        // you may implement and return your 'ActiveDataProvider' instance here.
        // in my case I prefer using the built in Search Class generated by Gii which is already 
        // performing validation and using 'like' whenever the attr is expecting a 'string' value.
        $searchByAttr['ProductSearch'] = $search;
        $searchModel = new \app\models\ProductSearch();    
        return $searchModel->search($searchByAttr);     
    }
}

Now your GET request will look like :

/products?name=iphone

Or even like :

/products?name=iphone&status=available&sort=name,-price

  • Note:

    If instead of /products?name=iphone you are looking for a specific action to handle searching or filtering requests like :

    /products/search?name=iphone

    Then, in the code above you'll need to remove the actions function with all its content :

    public function actions() { ... }