Запрос YII2 REST
Hy.
У меня есть ProductController, который расширяет yii\rest\ActiveController.
Вопрос в том, как я могу делать запросы через HTTP-запрос GET.
Нравится: http://api.test.loc/v1/products/search?name=iphone
И возвращаемый объект будет содержать все продукты с именем iphone.
Ответы
Ответ 1
Хорошо, я понял, просто поставьте это в свой контроллер и измените URL-адрес маршрутизатора в config.
public function actionSearch()
{
if (!empty($_GET)) {
$model = new $this->modelClass;
foreach ($_GET as $key => $value) {
if (!$model->hasAttribute($key)) {
throw new \yii\web\HttpException(404, 'Invalid attribute:' . $key);
}
}
try {
$provider = new ActiveDataProvider([
'query' => $model->find()->where($_GET),
'pagination' => false
]);
} catch (Exception $ex) {
throw new \yii\web\HttpException(500, 'Internal server error');
}
if ($provider->getCount() <= 0) {
throw new \yii\web\HttpException(404, 'No entries found with this query string');
} else {
return $provider;
}
} else {
throw new \yii\web\HttpException(400, 'There are no query string');
}
}
И правило URL (edit)
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
['class' => 'yii\rest\UrlRule', 'controller' => ['v1/product'], 'extraPatterns' => ['GET search' => 'search']],
],
],
Ответ 2
ОБНОВЛЕНИЕ: 29 апреля 2016 г.
Это еще один подход, более простой, чем тот, который я представил в предыдущем обновлении. Это всегда связано с классом Поиск, сгенерированным gii. Мне нравится использовать его для определения и поддержки всей логики поиска в одном месте, например, используя пользовательские сценарии, обрабатывать проверки или включать связанные модели в процесс фильтрации (например, в пример). Поэтому я возвращаюсь к своему первому ответу:
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);
}
Затем убедитесь, что ваш класс поиска использует load($params,'')
вместо load($params)
или, наоборот, добавляет его в класс модели:
class Product extends \yii\db\ActiveRecord
{
public function formName()
{
return '';
}
Этого должно быть достаточно, чтобы ваши запросы выглядели следующим образом:
/имя продуктов = iphone &? Статус = доступен & род = имя, -Цена
ОБНОВЛЕНИЕ: сентябрь 23 2015
Это тот же подход, но путем реализации полного и чистого решения:
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);
}
}
Теперь ваш запрос GET будет выглядеть так:
/продукты? Имя = iphone
Или даже как:
/имя продуктов = iphone &? Статус = доступен & род = имя, -Цена
-
Примечание:
Если вместо /products?name=iphone
вы ищете конкретную
действие для обработки запросов поиска или фильтрации, например:
/Продукты/Поиск? Имя = iphone
Затем в приведенном выше коде вам нужно удалить функцию действий со всем ее содержимым:
public function actions() { ... }
переименовать: indexDataProvider()
до actionSearch()
& наконец добавить 'extraPatterns' => ['GET search' => 'search']
в свои yii\web\UrlManager:: правила, как описано
в ответ @KedvesHunor.
Оригинальный ответ: 31 мая 2015 г.
Есть короткий способ сделать это, если при использовании Gii для генерации CRUD для вашей модели вы определили класс модели поиска, тогда вы можете использовать его для фильтрации результатов, все, что вам нужно сделать, это переопределить prepareDataProvider
функции indexAction
, чтобы принудительно вернуть экземпляр ActiveDataProvider
, возвращенный функцией search
вашего класса поиска моделей, вместо создания пользовательской новой.
Чтобы возобновить работу, если ваша модель Product.php, и вы создали для нее класс ProductSearch.php <класs → , то в вашем контроллере вам просто нужно добавить это:
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);
}
Затем для фильтрации результатов ваш URL-адрес может выглядеть так:
api.test.loc/v1/products?ProductSearch[name]=iphone
или даже так:
api.test.loc/v1/products?ProductSearch[available]=1&ProductSearch[name]=iphone
Ответ 3
Я бы не рекомендовал использовать Superglobals $ _GET напрямую. Вместо этого вы можете использовать Yii::$app->request->get()
.
Ниже приведен пример того, как вы можете создать общее действие поиска и использовать его в контроллере.
На контроллере End
public function actions() {
$actions = [
'search' => [
'class' => 'app\[YOUR NAMESPACE]\SearchAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
'params' => \Yii::$app->request->get()
],
];
return array_merge(parent::actions(), $actions);
}
public function verbs() {
$verbs = [
'search' => ['GET']
];
return array_merge(parent::verbs(), $verbs);
}
Пользовательское действие поиска
<?php
namespace app\[YOUR NAMESPACE];
use Yii;
use yii\data\ActiveDataProvider;
use yii\rest\Action;
class SearchAction extends Action {
/**
* @var callable a PHP callable that will be called to prepare a data provider that
* should return a collection of the models. If not set, [[prepareDataProvider()]] will be used instead.
* The signature of the callable should be:
*
* '''php
* function ($action) {
* // $action is the action object currently running
* }
* '''
*
* The callable should return an instance of [[ActiveDataProvider]].
*/
public $prepareDataProvider;
public $params;
/**
* @return ActiveDataProvider
*/
public function run() {
if ($this->checkAccess) {
call_user_func($this->checkAccess, $this->id);
}
return $this->prepareDataProvider();
}
/**
* Prepares the data provider that should return the requested collection of the models.
* @return ActiveDataProvider
*/
protected function prepareDataProvider() {
if ($this->prepareDataProvider !== null) {
return call_user_func($this->prepareDataProvider, $this);
}
/**
* @var \yii\db\BaseActiveRecord $modelClass
*/
$modelClass = $this->modelClass;
$model = new $this->modelClass([
]);
$safeAttributes = $model->safeAttributes();
$params = array();
foreach($this->params as $key => $value){
if(in_array($key, $safeAttributes)){
$params[$key] = $value;
}
}
$query = $modelClass::find();
$dataProvider = new ActiveDataProvider([
'query' => $query,
]);
if (empty($params)) {
return $dataProvider;
}
foreach ($params as $param => $value) {
$query->andFilterWhere([
$param => $value,
]);
}
return $dataProvider;
}
}
Ответ 4
В Config/web.php → Добавить 'extraPatterns' = > ['GET search' = > 'search']
'urlManager' => [
'enablePrettyUrl' => true,
'showScriptName' => false,
'rules' => [['class' => 'yii\rest\UrlRule', 'controller' => 'v1/basicinfo', 'pluralize'=>false,'extraPatterns' => ['GET search' => 'search']]]]
** В Rest Api Controller: - Moduels/v1/controllers/**
basicinfo: - Является ли ваш контроллер именем, именем и возрастом вашим именем поля. Вы можете добавить все параметры в таблицу.
URL-адрес поиска LIKE: - basicinfo/search? name = yogi & age = 12-23
Включить использование yii\data\ActiveDataProvider;
public function actionSearch()
{
if (!empty($_GET)) {
$model = new $this->modelClass;
foreach ($_GET as $key => $value) {
if (!$model->hasAttribute($key)) {
throw new \yii\web\HttpException(404, 'Invalid attribute:' . $key);
}
}
try {
$query = $model->find();
foreach ($_GET as $key => $value) {
if ($key != 'age') {
$query->andWhere(['like', $key, $value]);
}
if ($key == 'age') {
$agevalue = explode('-',$value);
$query->andWhere(['between', $key,$agevalue[0],$agevalue[1]]);
}
}
$provider = new ActiveDataProvider([
'query' => $query,
'sort' => [
'defaultOrder' => [
'updated_by'=> SORT_DESC
]
],
'pagination' => [
'defaultPageSize' => 20,
],
]);
} catch (Exception $ex) {
throw new \yii\web\HttpException(500, 'Internal server error');
}
if ($provider->getCount() <= 0) {
throw new \yii\web\HttpException(404, 'No entries found with this query string');
} else {
return $provider;
}
} else {
throw new \yii\web\HttpException(400, 'There are no query string');
}
}
Ответ 5
Начиная с yii 2.0.13, у yii yii\rest\IndexAction
есть новое свойство - dataFilter
, которое упрощает процесс фильтрации. По умолчанию ActiveController использует yii\rest\IndexAction
для действия index
:
class ActiveController extends Controller {
public function actions()
{
return [
'index' => [
'class' => 'yii\rest\IndexAction',
'modelClass' => $this->modelClass,
'checkAccess' => [$this, 'checkAccess'],
]
}
}
Выполните следующие действия в контроллере ProductController
:
class ProductController extends ActiveController
{
public function actions()
{
$actions = parent::actions();
$actions['index']['dataFilter'] = [
'class' => 'yii\data\ActiveDataFilter',
'searchModel' => 'app\models\ProductSearch'
];
return $actions;
}
}
Предположим, app\models\ProductSearch
является моделью фильтра продукта.