首页
Yii2 MVC

控制器

控制器 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 遵循如下规则衍生:

  1. 将每个单词的第一个字母转为大写;

  2. 去掉中横杠;

  3. 增加 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