要使Yii RESTful API正确响应请求,需确保User模型继承ActiveRecord、实现tableName()与rules()、屏蔽敏感字段、预加载关联数据;否则将出现查询错误、安全漏洞或N+1问题。

要让 Yii 框架 RESTful API 正确响应 GET /users、GET /users/1 等请求并返回结构化数据,必须确保模型层能被控制器准确调用、字段验证逻辑生效、关联关系可序列化,且数据库查询不暴露敏感字段或触发 N+1 问题。
模型类必须继承 ActiveRecord 并定义 tableName()
控制器通过 $modelClass 反射实例化模型,若模型未继承 yii\db\ActiveRecord 或未声明表名,ActiveController 在执行 find() 或 findOne() 时会直接抛出错误或返回空结果。
在 api\models\User.php 中写入:
class User extends \yii\db\ActiveRecord
{
public static function tableName()
{
return 'user';
}
}
【tableName() 返回值必须与数据库真实表名完全一致,大小写敏感】
必须实现 rules() 方法并启用验证
Yii RESTful 接口默认不强制校验输入,但 POST/PUT 请求若携带非法字段(如 id、status=999),而模型 rules() 返回空数组,数据将直接写入数据库,导致安全漏洞或状态错乱。
第一步:在 User 模型中补全 rules():
public function rules()
{
return [
[['name', 'email'], 'required'],
['email', 'email'],
[['status'], 'in', 'range' => [0, 1]],
];
}
第二步:在 UserController 中显式启用验证行为(否则即使 rules 存在也不生效):
public function behaviors()
{
$behaviors = parent::behaviors();
$behaviors['verbs'] = [
'class' => \yii\rest\VerbFilter::className(),
'actions' => [
'create' => ['POST'],
'update' => ['PUT', 'PATCH'],
],
];
$behaviors['contentNegotiator']['formats']['text/html'] = \yii\web\Response::FORMAT_JSON;
return $behaviors;
}
这一步不做,POST 请求即使字段缺失也不会报 422,而是静默插入 NULL 或默认值。
敏感字段需主动屏蔽
默认情况下,ActiveController 会把模型所有 public 属性转为 JSON 输出,password_hash、api_token、deleted_at 等字段一旦泄露,系统即失守。
方法一:重写 fields() 方法(推荐)
public function fields()
{
$fields = parent::fields();
unset($fields['password_hash'], $fields['api_token']);
return $fields;
}
方法二:使用 scenarios() + safe 标记(适用于不同接口版本差异化输出)
public function scenarios()
{
$scenarios = parent::scenarios();
$scenarios['api-list'] = ['id', 'name', 'email', 'created_at'];
return $scenarios;
}
然后在控制器中设置:
public $serializer = [
'class' => \yii\rest\Serializer::className(),
'collectionEnvelope' => 'items',
];
并在 actionIndex() 中手动指定场景:
$models = User::find()->asArray()->all();
return new \yii\data\ArrayDataProvider(['allModels' => $models]);
——这种写法绕过模型序列化,需自行控制字段。
关联数据必须用 with() 预加载
若 User 模型有 getProfile() 关联方法,而控制器未预加载,访问 /users/1 时 ActiveController 默认只查主表,后续序列化中调用 $model->profile 会触发额外 SQL 查询,一个请求可能发起 50+ 次 DB 查询。
在 UserController 的 findModel() 方法中覆盖默认逻辑:
protected function findModel($id)
{
$model = User::find()
->with('profile', 'roles')
->where(['id' => $id])
->one();
if ($model === null) {
throw new \yii\web\HttpException(404, 'The requested Item does not exist.');
}
return $model;
}
注意:with() 中的关联名必须与模型里定义的 getter 方法名完全一致(如 getProfile() → 'profile'),拼错则静默忽略,仍会 N+1。


















