在 Yii1 时代用 Gii 生成生成 Model 的时候,Model 里面就有一个 search 方法。
在 Yii2 时代用 Gii 生成生成 Model 的时候,Model 里面是没有 search 方法的,也就是说默认没给你生成搜索方法。 要想实现搜索怎么办?
Gii 点击 CRUD Generator 生成 CRUD 的时候填写 Search Model Class
input 框就会生成一个针对某个 Model 的 search 文件,这个值是可选的,如果
不填写,也就不会生成这个文件。
个人觉得每个 Model 再生成一个单独的 ModelSearch 文件就感觉文件有点太多了。一直想找一个通用省事的解决方案。
这个方案再我读 DevGroup-ru/dotplant2 项目源码的时候找到答案了。 非常感谢他们的开源代码,这个项目可以借鉴的不只有这一点。
使用这个方法主要解决了:当我们想实现表搜索的时候不再需要逐个生成 ModelSearch 文件了一个 SearchModel 文件搞定所有搜索,下面我们来谈谈如何使用。
先添加这个类文件 或者使用我优化过的 SearchModel 文件:
<?php
/**
* author : forecho <caizhenghai@gmail.com>
* createTime : 2015/12/29 15:33
* description:
*/
namespace common\components;
use yii\base\InvalidParamException;
use yii\base\Model;
use yii\data\ActiveDataProvider;
use yii\data\Pagination;
use yii\db\ActiveQuery;
/*
*
* Class SearchModel
* @package common\components
*/
class SearchModel extends Model
{
private $attributes;
private $attributeLabels;
private $internalRelations;
private $model;
private $modelClassName;
private $relationAttributes = [];
private $rules;
private $scenarios;
public $defaultOrder;
public $groupBy;
public $pageSize = 20;
public $partialMatchAttributes = []; // 模糊查询
public $relations = [];
/**
* @param ActiveQuery $query
* @param string $attribute
* @param bool $partialMath
*/
private function addCondition($query, $attribute, $partialMath = false)
{
if (isset($this->relationAttributes[$attribute])) {
$attributeName = $this->relationAttributes[$attribute];
} else {
$attributeName = call_user_func([$this->modelClassName, 'tableName']) . '.' . $attribute;
}
$value = $this->$attribute;
if ($value === '') {
return;
}
if ($partialMath) {
$query->andWhere(['like', $attributeName, trim($value)]);
} else {
$query->andWhere($this->conditionTrans($attributeName, $value));
}
}
/**
* 可以查询大于小于和IN
* @param $attributeName
* @param $value
* @return array
*/
private function conditionTrans($attributeName, $value)
{
switch (true) {
case is_array($value):
return [$attributeName => $value];
break;
case stripos($value, '>=') !== false:
return ['>=', $attributeName, substr($value, 2)];
break;
case stripos($value, '<=') !== false:
return ['<=', $attributeName, substr($value, 2)];
break;
case stripos($value, '<') !== false:
return ['<', $attributeName, substr($value, 1)];
break;
case stripos($value, '>') !== false:
return ['>', $attributeName, substr($value, 1)];
break;
case stripos($value, ',') !== false:
return [$attributeName => explode(',', $value)];
break;
default:
return [$attributeName => $value];
break;
}
}
/**
* @param array $params
* @throws \yii\base\InvalidParamException
*/
public function __construct($params)
{
$this->scenario = 'search';
parent::__construct($params);
if ($this->model === null) {
throw new InvalidParamException('Param "model" cannot be empty');
}
$this->rules = $this->model->rules();
$this->scenarios = $this->model->scenarios();
$this->attributeLabels = $this->model->attributeLabels();
foreach ($this->safeAttributes() as $attribute) {
$this->attributes[$attribute] = '';
}
}
/**
* @param string $name
* @return mixed
*/
public function __get($name)
{
if (isset($this->attributes[$name])) {
return $this->attributes[$name];
}
return parent::__get($name);
}
/**
* @param string $name
* @param mixed $value
*/
public function __set($name, $value)
{
if (isset($this->attributes[$name])) {
$this->attributes[$name] = $value;
} else {
parent::__set($name, $value);
}
}
/**
* @return Model
*/
public function getModel()
{
return $this->model;
}
/**
* @param mixed $value
*/
public function setModel($value)
{
if ($value instanceof Model) {
$this->model = $value;
$this->scenario = $this->model->scenario;
$this->modelClassName = get_class($value);
} else {
$this->model = new $value;
$this->modelClassName = $value;
}
}
/**
* @return array
*/
public function rules()
{
return $this->rules;
}
/**
* @return array
*/
public function attributeLabels()
{
return $this->attributeLabels;
}
/**
* @return array
*/
public function scenarios()
{
return $this->scenarios;
}
/**
* @param array $params
* @return ActiveDataProvider
*/
public function search($params)
{
$query = call_user_func([$this->modelClassName, 'find']);
$dataProvider = new ActiveDataProvider(
[
'query' => $query,
'pagination' => new Pagination(
[
'forcePageParam' => false,
'pageSize' => $this->pageSize,
]
),
]
);
if (is_array($this->relations)) {
foreach ($this->relations as $relation => $attributes) {
$pieces = explode('.', $relation);
$path = '';
$parentPath = '';
foreach ($pieces as $i => $piece) {
if ($i == 0) {
$path = $piece;
} else {
$parentPath = $path;
$path .= '.' . $piece;
}
if (!isset($this->internalRelations[$path])) {
if ($i == 0) {
$relationClass = call_user_func([$this->model, 'get' . $piece]);
} else {
$className = $this->internalRelations[$parentPath]['className'];
$relationClass = call_user_func([new $className, 'get' . $piece]);
}
$this->internalRelations[$path] = [
'className' => $relationClass->modelClass,
'tableName' => call_user_func([$relationClass->modelClass, 'tableName']),
];
}
}
foreach ((array)$attributes as $attribute) {
$attributeName = str_replace('.', '_', $relation) . '_' . $attribute;
$tableAttribute = $this->internalRelations[$relation]['tableName'] . '.' . $attribute;
$this->rules[] = [$attributeName, 'safe'];
$this->scenarios[$this->scenario][] = $attributeName;
$this->attributes[$attributeName] = '';
$this->relationAttributes[$attributeName] = $tableAttribute;
$dataProvider->sort->attributes[$attributeName] = [
'asc' => [$tableAttribute => SORT_ASC],
'desc' => [$tableAttribute => SORT_DESC],
];
}
}
$query->joinWith(array_keys($this->relations));
}
if (is_array($this->defaultOrder)) {
$dataProvider->sort->defaultOrder = $this->defaultOrder;
}
if (is_array($this->groupBy)) {
$query->addGroupBy($this->groupBy);
}
$this->load($params);
foreach ($this->attributes as $name => $value) {
$this->addCondition($query, $name, in_array($name, $this->partialMatchAttributes));
}
return $dataProvider;
}
}
到你的代码库中,我喜欢放在 namespace common\components;
命名空间下。
最基本的使用
public function actionSearch()
{
$searchModel = new SearchModel(
[
'model' => Topic::className(),
'scenario' => 'default',
]
);
$dataProvider = $searchModel->search(['SearchModel' => Yii::$app->request->queryParams]);
return $this->render('index', [
'dataProvider' => $dataProvider,
]);
}
Yii::$app->request->queryParams
必须是一维数组,对应的数据库中字段。
高级的用法
public function actionSearch()
{
$searchModel = new SearchModel(
[
'defaultOrder' => ['id' => SORT_DESC],
'model' => Topic::className(),
'scenario' => 'default',
'relations' => ['comment' => []], // 关联表(可以是Model里面的关联)
'partialMatchAttributes' => ['title'], // 模糊查询
'pageSize' => 15
]
);
$dataProvider = $searchModel->search(['SearchModel' => Yii::$app->request->queryParams]);
$dataProvider->query->andWhere([Topic::tableName() . '.user_id' => 23, Comment::tableName() . '.status' => 1 ]);
return $this->render('index', [
'dataProvider' => $dataProvider,
]);
}
本文由 forecho 创作,采用 知识共享署名 3.0 中国大陆许可协议 进行许可。 可自由转载、引用,但需署名作者且注明文章出处。
如果这篇文章对您有帮助,不妨微信小额赞助我一下,让我有动力继续写出高质量的教程。
@forecho 想请教下,如果我的数据集是通过存储过程查询得来的,是否也可以实现searchmodal
@qq2508481 #2楼 看示例,Topic
是指你要搜索的 Model,示例是要搜索话题 Model,对应的是 topic
表。
如果你要搜索 user
表,对应的应该是 'model' => User::className()
。
我这样讲,你能理解吗?
@vipluosong #6楼 用 Yii2 的 HasOne
和 HasMany
具体可以查询文档
SearchModel
就配置相应的 'relations' => ['comment' => []],