第二层内功 - 表单模型 - 多场景

  • 作者:KK

  • 发表日期:2017.3.2


表单模型就是所谓的“逻辑层”,但并非一个表单就只是一个业务逻辑,其实一个表单可以定义多个业务逻辑

所以在不同业务逻辑的情况下,就意味着有不同的场景,比如说一个文章表单,有添加场景,也有编辑场景


定义多场景的表单

下面是一个带有添加/编辑的文章表单示例:

namespace app\forms;

use Yii;
use app\models\Article;

class ArticleForm extends \yii\base\Model{
	const SCENE_ADD = 'add'; //添加场景
	
	const SCENE_EDIT = 'edit'; //编辑场景
	
	public $id = 0; //文章ID,编辑时需要
	
	public $title = ''; //文章标题
	
	public $categoryId = 0; //分类ID
	
	public $content = ''; //文章内容
	
	private $_article = null; //文章实例

	public function rules(){
		return [
			[['id', 'title', 'categoryId', 'content'], 'required'],
			['title', 'string', 'length' => [4, 20]],
			['content', 'string', 'length' => [10, 65535]],
			[['title', 'content'], 'safe'],
			[['id', 'categoryId'], 'integer'],
			['id', 'validateId'],
			['categoryId', 'exist', 'targetClass' => 'app\models\ArticleCategory', 'targetAttribute' => 'id', 'message' => '无效的文章分类'],
		];
	}
	
	//定义不同场景所需要校验的表单属性
	public function scenarios(){
		return [
			//添加,需要标题、分类、内容
			self::SCENE_ADD => ['title', 'categoryId', 'content'],
			
			//编辑,需要ID、标题、内容,没有分类,表示禁止修改分类
			self::SCENE_EDIT => ['id', 'title', 'content'],
		];
	}
	
	public function validateId(){
		$article = Article::findOne($this->id);
		if(!$article){
			$this->addError('id', '无效的文章ID');
			return;
		}
		$this->_article = $article;
	}
	
	public function add(){
		if(!$this->validate()){
			return false;
		}
		
		$article = new Article([
			'title' => $this->title,
			'category_id' => $this->categoryId,
			'content' => $this->content,
			'add_time' => time(),
		]);
		
		if(!$article->save()){
			throw new \yii\base\ErrorException('添加文章失败');
		}
		return $article;
	}
	
	public function edit(){
		if(!$this->validate()){
			return false;
		}
		
		Yii::configure($this->_article, [
			'title' => $this->title,
			'content' => $this->content,
		]);
		if(!$this->_article->save()){
			throw new \yii\base\ErrorException('编辑文章失败');
		}
		return $this->_article;
	}
}

以上表单中,add方法就是添加文章的业务逻辑,edit方法是编辑文章咯,可是两个业务要验证的用户端输入参数都包含了title和content,所以要通过scenarios方法来声明两个场景要校验的属性名称

当执行validate的时候,底层会自动在rules找到相关的规则进行校验,不会对无关的属性规则进行校验

控制器调用示例

public function actionAdd(){
	$form = new ArticleForm([
		'scenario' => ArticleForm::SCENE_ADD,
	]);
	
	//菜鸟注意:如果前端用ActiveForm就别像我这样传第2个参数空字符串
	if(!$form->load(Yii::$app->request->post(), '')){
		return '接收参数失败';
	}
	
	if($article = $form->add()){
		return '添加成功,文章ID是:' . $article->id;
	}else{
		return $form->firstError[0];
	}
}

public function actionEdit(){
	$form = new ArticleForm([
		'scenario' => ArticleForm::SCENE_EDIT,
	]);
	
	//菜鸟注意:如果前端用ActiveForm就别像我这样传第2个参数空字符串
	if(!$form->load(Yii::$app->request->post(), '')){
		return '接收参数失败';
	}
	
	if($form->edit()){
		return '保存完毕';
	}else{
		return $form->firstError[0];
	}
}

如果表单定义了多场景,执行add或edit这些业务处理方法前(其实是validate被执行之前),一定要先设定scenario属性(属性值就是场景的标识)

最终其实就是为了告诉validate要对哪些属性进行validate,而不是盲目地全部validate,毕竟不同场景下有不同的校验字段


深入应用

其实不能仅仅把场景理解为使用在validate控制上的,自己在form里面写的逻辑代码都可以if($this->scenario == self::SCENE_ADD)这样来判断确定是否要做某些逻辑(当该方法与其它场景共用时)

这种情况很少,但是会有,比如我在xoa项目中这个TaskForm表单用到场景判断