控制器
控制器 ID:指的是控制器的名字,比如article
对应ArticleController
下面为一些示例,假设 controller namespace 控制器命名空间为 app\controllers:
article 对应 app\controllers\ArticleController;
post-comment 对应 app\controllers\PostCommentController;
admin/post-comment 对应 app\controllers\admin\PostCommentController;
adminPanels/post-comment 对应 app\controllers\adminPanels\PostCommentController.
默认控制器
每个应用有一个由yii\base\Application::$defaultRoute
属性指定的默认控制器; 当请求没有指定 路由,该属性值作为路由使用。
-
对于 Web applications 网页应用,它的值为 'site',
-
对于 console applications 控制台应用,它的值为 help,
所以 URL 为 http://hostname/index.php 表示由 site 控制器来处理。
可以在 应用配置 中修改默认控制器,如下所示:
frontend/config/main.php
[ 'defaultRoute' => 'welcome' ]
动作
动作通常是用来执行资源的特定操作,因此,动作 ID 通常为动词,如 view, update 等。
操作 ID 应仅包含英文小写字母、数字、下划线和中横杠,操作 ID 中的中横杠用来分隔单词。 例如 view, update2, comment-post 是真实的操作 ID, view?, Update 不是操作 ID.
可通过两种方式创建操作 ID,内联操作和独立操作.
-
An inline action is 内联操作在控制器类中定义为方法;
-
独立操作是继承
yii\base\Action
或它的子类的类。
内联操作容易创建,在无需重用的情况下优先使用; 独立操作相反,主要用于多个控制器重用, 或重构为扩展。
动作的格式
动作方法的名字是根据操作 ID 遵循如下规则衍生:
-
将每个单词的第一个字母转为大写;
-
去掉中横杠;
-
增加 action 前缀.
例如 index 转成 actionIndex, hello-world 转成 actionHelloWorld。
例如 index 转成 actionIndex, hello-world 转成 actionHelloWorld。
注意: 操作方法的名字大小写敏感,如果方法名称为 ActionIndex 不会认为是操作方法, 所以请求 index 操作会返回一个异常, 也要注意操作方法必须是公有的, 私有或者受保护的方法不能定义成内联操作。
最佳实践
在设计良好的应用中,控制器很精练,包含的操作代码简短; 如果你的控制器很复杂,通常意味着需要重构, 转移一些代码到其他类中。
归纳起来,控制器
-
可访问 请求 数据;
-
可根据请求数据调用 模型 的方法和其他服务组件;
-
可使用 视图 构造响应;
-
不应处理应被模型处理的请求数据;
-
应避免嵌入 HTML 或其他展示代码,这些代码最好在 视图中处理.
模型
模型是 MVC 模式中的一部分, 是代表业务数据、规则和逻辑的对象。
可通过继承 yii\base\Model
或它的子类定义模型类。Model 类也是更多高级模型如Active Record
活动记录的基类。
模型并不强制一定要继承
yii\base\Model
,但是由于很多组件支持yii\base\Model
, 最好使用它做为模型基类。
属性标签
可以通过attributeLabels
方法设置属性标签,然后可以调用模型的getAttributeLabel
方法获取
class EntryForm extends Model
{
public $name;
public $email;
public function rules() {
return [
[['name' , 'email'] , 'required'] ,
['email' , 'email'] ,
];
}
public function attributeLabels() {
return [
'name' => '名称' ,
'email' => '邮箱'
];
}
}
public function actionEntry() {
$model = new EntryForm();
echo $model->getAttributeLabel("name");
}
表单验证
需要注意,
yii2 model load not working
的问题:需要加第二个参数:''
public function actionTest() {
$model = new EntryForm();
$post = Yii::$app->request->post();
if ($model->load($post , '') && $model->validate()) {
print_r($model);
} else {
print_r($model->getErrors());
}
}
另外需要注意的是,使用块赋值,Form 里的 验证最终会体现在字段的输出上。
$model->attributes = \Yii::$app->request->post('ContactForm');
// 或者
$dto->load(Yii::$app->request->post() , '')
如果没有出现在验证规则里,
$model
里面就没有对应的字段,需要注意一下。对于不需要验证,又想出现在$model
中的,可以设置一个 safe 属性
public function rules() {
return [
[['name' , 'email' , 'subject' , 'body'] , 'required'],
[['city'], 'safe']
];
}
对于非 ActiveRecord 模型,需要显示地定义模型的字段。如下:必须使用类属性的方式,注解是没用的
class User extends Model {
public $phone_number;
public $nick_name;
public $avatar;
public function rules(): array {
return [
// safe 属性,不需要进行验证
[['phone_number' , 'nick_name' , 'avatar'] , 'safe']
];
}
}
验证规则参考:
https://www.yiiframework.com/doc/guide/2.0/zh-cn/input-validation
https://www.yiiframework.com/doc/guide/2.0/zh-cn/tutorial-core-validators
如果一个字段的值是:
"", [], {}, null
,如果没有设置为 required,会略过检查
唯一性检查:
表单中使用unique可以进行唯一性检查。需要注意的是有时候表单字段跟 ActiveRecord 的字段不一定是对应的,可以按照如下的方式设置:
// 检查 ent_name 是否是唯一的,对应的是 Ent ActiveRecord 的 name 字段,并且加了过滤条件
[
'ent_name' ,
'unique' ,
'targetAttribute' => 'name' ,
'targetClass' => Ent::class ,
'filter' => ['status' => STATUS_OK] ,
'message' => '该企业已存在'
] ,
存在性检查:
[
'email',
'exist',
'targetClass' => '\common\models\User',
'filter' => ['status' => User::STATUS_ACTIVE],
'message' => 'There is no user with this email address.'
],
表单默认值
对于不需要进行验证的字段,需要设置该字段的默认值,不然会报错。如下代码,可以在变量声明的时候设置默认值,或者使用 yii 的 数据预处理
<?php
class EntDto extends Model {
public string $ent_name = '';
public string $contact_name = '';
public string $contact_phone = '';
public string $contact_email = '';
public function rules(): array {
return [
[['ent_name' , 'contact_name' , 'contact_phone' , 'contact_email'] , 'safe']
];
}
}
场景
https://www.yiiframework.com/doc/guide/2.0/zh-cn/structure-models#scenarios
我们可以使用场景来对 Dto 做一个收敛,防止 Dto 的数量过多。
但场景只能决定验证规则和块赋值,不能决定 model 的哪些字段不显示。这对于 Entity 转换为 Dto 时不太方便。我们也可以换个方式实现。
// 场景:用户加载
const SCENARIO_VIEW = 'view';
public function scenarios(): array {
$scenarios = parent::scenarios();
//这里将 SCENARIO_VIEW 设置为默认的场景,只是为了得到一个场景的标记。
$scenarios[ self::SCENARIO_VIEW ] = $scenarios[ self::SCENARIO_DEFAULT ];
return $scenarios;
}
/**
* 这里利用场景来控制字段的显示与否
* @return array
*/
public function fields(): array {
$fields = parent::fields();
switch ($this->scenario) {
case self::SCENARIO_VIEW:
unset($fields['email']);
break;
}
return $fields;
}
调用:
$userDto = new UserDto(['scenario' => UserDto::SCENARIO_VIEW]);
最佳实践
https://www.yiiframework.com/doc/guide/2.0/zh-cn/structure-models#best-practices