[英]Yii2 REST query
嗨。 我有一个扩展 yii\\rest\\ActiveController 的 ProductController。 问题是我如何通过 HTTP GET 请求进行查询。
喜欢: http://api.test.loc/v1/products/search?name=iphone
并且返回对象将包含名称为 iphone 的所有产品。
这是一种比我在上一次更新中介绍的方法更简单的方法。 它总是涉及到由gii生成的Search类。 我喜欢用它在一个地方定义和维护所有与搜索相关的逻辑,比如使用自定义场景、处理验证或在过滤过程中涉及相关模型(如本例中)。 所以我要回到我的第一个答案:
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 '';
}
这应该足以让您的请求看起来像:
这是相同的方法,但通过实施完整和更清洁的解决方案:
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 请求将如下所示:
或者甚至喜欢:
笔记:
如果不是/products?name=iphone
您正在寻找特定操作来处理搜索或过滤请求,例如:
然后,在上面的代码中,您需要删除actions 函数及其所有内容:
public function actions() { ... }
重命名: indexDataProvider()
到actionSearch()
& 最后将'extraPatterns' => ['GET search' => 'search']
到您的yii\\web\\UrlManager::rules 中,如@KedvesHunor 的回答中所述。
有一个简短的方法可以做到这一点,如果在使用Gii为您的模型生成 CRUD 时,您定义了一个Search Model Class ,那么您可以使用它来过滤结果,您所要做的就是将indexAction
的prepareDataProvider
函数覆盖为强制它返回模型搜索类的search
函数返回的ActiveDataProvider
实例,而不是创建一个自定义的新实例。
如果你的模型是Product.php 并且你生成了一个ProductSearch.php作为它的搜索类,那么要恢复它,然后在你的控制器中你只需要添加这个:
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);
}
然后过滤结果,您的网址可能如下所示:
api.test.loc/v1/products?ProductSearch[name]=iphone
甚至像这样:
api.test.loc/v1/products?ProductSearch[available]=1&ProductSearch[name]=iphone
好的,我想通了,只需将它放在您的控制器中并修改配置中的 URL 路由器。
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 规则(编辑)
'urlManager' => [
'enablePrettyUrl' => true,
'enableStrictParsing' => true,
'showScriptName' => false,
'rules' => [
['class' => 'yii\rest\UrlRule', 'controller' => ['v1/product'], 'extraPatterns' => ['GET search' => 'search']],
],
],
我不建议直接使用 Superglobals $_GET 。 相反,您可以使用Yii::$app->request->get()
。
以下是如何创建通用搜索操作并在控制器中使用它的示例。
在控制器端
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;
}
}
从 yii 2.0.13 开始, 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
是产品过滤器模型。
在 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 控制器中:- Moduels/v1/controllers/ **
basicinfo :- 是您的控制器名称,名称和年龄是您的字段名称。您可以添加表中存在的所有参数。
搜索 URL 喜欢:- 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');
}
}
如果您需要访问您的 api,例如: api/product/index?name=fashion
一种更短的过滤方式是:
- 取消设置动作,在我的例子中是index
动作。
public function actions()
{
$actions = parent::actions();
unset($actions['index']);
return $actions;
}
执行自定义查询,如下所示。
公共函数 actionIndex() {
$query = Product::find(); $dataProvider = new ActiveDataProvider([ 'query' => $query, 'pagination' => [ 'pageSize' => 1, ], ]); if (isset($_GET['name']) && !empty($_GET['name'])) { $searchWord = strtolower(trim($_GET['name'])); $query->andFilterWhere(['like', 'name', $searchWord]); } return $dataProvider;
}
如果您想按唯一字段进行搜索,这里有一个较短的版本,只获取一条记录:
public function actionSearch()
{
if (!empty($_GET)) {
$model = new $this->modelClass;
return $model::findOne($_GET);
} else {
throw new HttpException(400, 'There are no query string');
}
}
用法: http://example.com/api/search?slug=abs
: http://example.com/api/search?slug=abs
slug= http://example.com/api/search?slug=abs
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.