Yii2系列教程七:Behaviors And Validations

JellyBool

JellyBool

这一篇文章的开头就无需多言了,紧接着上一篇的内容和计划,这一篇我们来说说Yii2的Behavior和Validations。

Behavior

首先我们来说说Behavior,在Yii2中Behavior主要是可以用于一些常用的Model字段当中,对其进行自动化操作,比如自动添加一些时间戳字段等,这样我们就不用在每一个需要保存记录的地方分别写生成时间戳的代码了,比如前面我们在Yii2系列教程五:简单的用户权限管理中的controllers/StatusController.phpactionCreate方法实现的

    $model->created_by = Yii::$app->user->getId();//add this line

    $model->created_at = time();

    $model->updated_at = time();

这些其实我们都是可以直接放在Status这个Model的behaviors()方法当中自动完成的。

OK,那既然这样,我们就来实现一下呗。

首先我们需要来喂我们的status表增加一个updated_by字段,用来记录是哪个用户对一条状态进行了更新:

./yii migrate/create extend_status_table_for_updated_by


执行完上面的命令之后,来到相对应的migration文件中,为其up()down()方法增加下面的代码:



public function up()

{

    $this->addColumn('{{%status}}','updated_by',Schema::TYPE_INTEGER.' NOT NULL');

    $this->addForeignKey('fk_status_updated_by', '{{%status}}', 'updated_by', '{{%user}}', 'id', 'CASCADE', 'CASCADE');

}


public function down()

{

    $this->dropForeignKey('fk_status_updated_by','{{%status}}');

    $this->dropColumn('{{%status}}','updated_by');

}

这个时候先不急着运行./yii migrate/up,因为这时候你运行./yii migrate/up其实是会报错的,因为对于已创建的status来说,它updated_by字段会默认为0,但是这个值在user这张表中并不存在。所以我们首先需要将status表中的几条数据删掉。鉴于这只是一个演示的小应用,为了保险起见,我将stutas这张表删除了,并且将migration这张表的于下面类似的两条记录删掉,以达到Yii2的migration重新创建stutas表的目的:

m150804_035107_create_status_table

m150806_034325_extend_status_table_for_created_by

然后再执行./yii migrate/up

./yii migrate/up


Yii Migration Tool (based on Yii v2.0.6)


Total 3 new migrations to be applied:

    m150804_035107_create_status_table

    m150806_034325_extend_status_table_for_created_by

    m150812_142736_extend_status_table_for_updated_by


Apply the above migrations? (yes|no) [no]:yes

*** applying m150804_035107_create_status_table

    > create table {{%status}} ... done (time: 0.025s)

*** applied m150804_035107_create_status_table (time: 0.033s)


*** applying m150806_034325_extend_status_table_for_created_by

    > add column created_by integer NOT NULL to table {{%status}} ... done (time: 0.036s)

    > add foreign key fk_status_created_by: {{%status}} (created_by) references {{%user}} (id) ... done (time: 0.014s)

*** applied m150806_034325_extend_status_table_for_created_by (time: 0.053s)


*** applying m150812_142736_extend_status_table_for_updated_by

    > add column updated_by integer NOT NULL to table {{%status}} ... done (time: 0.017s)

    > add foreign key fk_status_updated_by: {{%status}} (updated_by) references {{%user}} (id) ... done (time: 0.017s)

*** applied m150812_142736_extend_status_table_for_updated_by (time: 0.037s)

数据表创建完成之后,我们需要在Status这个Model文件中增加我们的behaviors方法:

public function behaviors()

    {

        return [

            [

                'class' => BlameableBehavior::className(),

                'createdByAttribute' => 'created_by',

                'updatedByAttribute' => 'updated_by',

            ],

            'timestamp' => [

                'class' => 'yii\behaviors\TimestampBehavior',

                'attributes' => [

                    ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],

                    ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],

                ],

            ],

        ];

    }

return的数组当中,第一个就是使用Yii2的BlameableBehavior自动完成created_byupdated_by这两个字段,至于timestamp这个数组,就是使用Yii2的TimestampBehavior来自动完成created_at和updated_at字段,并且这会根据数据的BEFORE_INSERTBEFORE_UPDATE两种情况来自动完成添加。

需要注意的是,我们这时候需要在Status中引入下面两个类:

use yii\behaviors\BlameableBehavior;

use yii\db\ActiveRecord;

这些工作都完成之后,我们就可以将我们原来在controllers/StatusController.phpactionCreate方法的相关那几行注释掉了:

public function actionCreate()

    {

        $model = new Status();


        if ($model->load(Yii::$app->request->post())) {


           /* $model->created_by = Yii::$app->user->getId();

            $model->created_at = time();

            $model->updated_at = time();*/


            if ($model->save()) {

                return $this->redirect(['view', 'id' => $model->id]);

            }

        }

        return $this->render('create', [

            'model' => $model,

        ]);

    }

注释掉之后,我们可以访问http://localhost:8999/status/create 来创建一条新的status来看看:

替代文字

点击创建按钮,你依然可以看到下面这个可爱的页面:

替代文字

到这里,我们的Behavior在Yii2中就已经实现完毕了。下面来看看Validations

Validations

Yii2的Validation提供多种多样的验证机制,比如常见的email,图片,数字等,更多的验证机制和条件,你可以到这里查看文档:

http://www.yiiframework.com/doc-2.0/yii-validators-validator.html

这里我想说明的一点是,在Yii2之中,Validation通常都是通过validate()来实现的,那么这个validate()` 方法的背后其实是怎么样的呢?可以看看下面这个顺序:

1. 首先检测Model当中有没有`scenarios()`方法,如果该方法存在,则首先执行该方法里面对应的场景之下的验证规则


2. 再检测Model当中的`rules()`方法,然后执行验证规则

为了演示方便,我们来创建一个新的Migrations:

./yii migrate/create create_sample_table

然后还是想之前一样,来到相对应的migration文件当中,实现up()down()方法:

public function up()

    {

        $tableOptions = null;

        if ($this->db->driverName === 'mysql') {

            $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB';

        }


        $this->createTable('{{%sample}}', [

            'id' => Schema::TYPE_PK,

            'thought' => Schema::TYPE_STRING.' NOT NULL DEFAULT ""',

            'goodness' => Schema::TYPE_SMALLINT . ' NOT NULL DEFAULT 0',

            'rank' => Schema::TYPE_INTEGER . ' NOT NULL',

            'censorship' => Schema::TYPE_STRING . ' NOT NULL',

            'occurred' => Schema::TYPE_DATE . ' NOT NULL',

        ], $tableOptions);

    }



    public function down()

    {

        $this->dropTable('{{%sample}}');

    }


然后执行之:

./yii migrate/up


Yii Migration Tool (based on Yii v2.0.6)


Total 1 new migration to be applied:

    m150812_153139_create_sample_table


Apply the above migration? (yes|no) [no]:yes

*** applying m150812_153139_create_sample_table

    > create table {{%sample}} ... done (time: 0.026s)

*** applied m150812_153139_create_sample_table (time: 0.037s)

使用Gii生成Model文件:

替代文字

再生成CRUD文件:

替代文字

替代文字

然后打开Sample.php,你就可以看到Yii2为我们生成的rules()了:

 public function rules()

    {

        return [

            [['goodness', 'rank'], 'integer'],

            [['rank', 'censorship', 'occurred'], 'required'],

            [['occurred'], 'safe'],

            [['thought', 'censorship'], 'string', 'max' => 255]

        ];

    }

Default Value验证

我门修改一下rules()方法里面的occurred字段的验证,给它设置一个默认值:

return [

            [['goodness', 'rank'], 'integer'],

            [['rank', 'censorship'], 'required'],

            // Gii generate this

            //[['occurred'], 'safe'],

            ['occurred', 'default', 'value' => date('Y-m-d')],

            [['thought', 'censorship'], 'string', 'max' => 255]

        ];

然后当我们创建一个新的Sample的时候,如果我们将occurred字段保留为空,那么这个字段就会默认取值为当天的日期。

替代文字

Rank填入2,然后创建后是这样的:

替代文字

至于另外的验证规则,你可以自己试着更改一下rules()方法里面的规则,然后进行相应的验证。

下一节

再详细讲讲Validations吧,这一节写下来貌似Validations要说的实在有点多。

Github源码:https://github.com/JellyBool/helloYii

Happy Hacking

本文由 JellyBool 创作, 转载和引用遵循 署名-非商业性使用 2.5 中国大陆 进行许可。

共有 27 条评论

Anonymous

说的很nice…已经用Yii2开发大半年了,还是学到了不少东西.

Anonymous

这么666,都大半年了。。。

Anonymous

公司网站项目都是yii框架…YII框架的确强大.只是一直没时间去了解laravel

Anonymous

我发现我两个都接触之后,我还是比较喜欢Laravel,不过做某些东西还是会考虑Yii2,实在是很棒。

Anonymous
'timestamp' => [
                'class' => 'yii\behaviors\TimestampBehavior',
                'attributes' => [
                    ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
                    ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at'],
                ],
            ],

这里好像有问题,$model->errors 打印出来,提示

created_at & updated_at can not be null.

Anonymous

我好像都没有遇到这些问题。。你前面是一样的正确设置么?

Anonymous

然后打开 SampleController.php,你就可以看到Yii2为我们生成的rules()了:

 public function rules()
    {
        return [
            [['goodness', 'rank'], 'integer'],
            [['rank', 'censorship', 'occurred'], 'required'],
            [['occurred'], 'safe'],
            [['thought', 'censorship'], 'string', 'max' => 255]
        ];
    }

这里应该有笔误,上面这段代码应该是 models/sample.php 这个文件生成的

Anonymous

对,我修改一下。。

Anonymous

我这里也是报错了 保存不了 ,请问下是怎么解决的?

Anonymous

我这里也是报错了 保存不了

Anonymous

代码贴出来我看看?

Anonymous
public function behaviors() {
        return [
            [
                'class' => BlameableBehavior::className(),
                'createdByAttribute' => 'created_by',
                'updatedByAttribute' => 'updated_by',
            ],
            'timestamp' => [
                'class' => 'yii\behaviors\TimestampBehavior',
                'attributes' => [
                    ActiveRecord::EVENT_BEFORE_INSERT => ['created_at', 'updated_at'],
                    ActiveRecord::EVENT_BEFORE_UPDATE => ['updated'],
                ]
            ]
        ];
    }


 public function actionCreate() {
        $model = new Status();
        if ($model->load(Yii::$app->request->post())) {
//            $model->created_by = Yii::$app->user->getId();
//            $model->created_at = time();
//            $model->updated_at = time();
            $save = $model->save();
            if ($save) {
                return $this->redirect(['view', 'id' => $model->id]);
            }
        }
        return $this->render('create', [
                    'model' => $model,
        ]);
    }
Anonymous

报错

Array ( [created_at] => Array ( [0] => Created At不能为空。 ) [updated_at] => Array ( [0] => Updated At不能为空。 ) )
Anonymous

把你的behaviors()的代码贴出来看看?

Anonymous
ActiveRecord::EVENT_BEFORE_UPDATE => ['updated_at']

这一行改一下试试?

Anonymous

这个是我改错了 之前是updated_at不行的

Anonymous

还有可能是哪里的问题呢

Anonymous

整个Status.php的代码我看看吧

Anonymous

感谢博主的分享, 话说你难道就没想过做一个分类么… 想看一种类型的文章好类啊[囧]

Anonymous

点tag就好了

Anonymous

我好想没看见有tag啊, 难道是我眼瞎吗?..

Anonymous

首页哪里的列表就有

Anonymous

噢, 好的, 但是你为什么不在站点上做个菜单呢, 怎么感觉好异类~
难道是故意的么.

Anonymous

故意的,其实也没啥导航的需要,就是记得东西而已

Anonymous

你现在的博客流量也不小啦, 弄个菜单呗…
哈哈

Anonymous

额。。。这点博客还叫流量不小。。。。

Anonymous

我是点击create后还是跳到create页面,数据库里没保存下来 StatusController中的

public function actionCreate()
{
    $model = new Status();

    if ($model->load(Yii::$app->request->post())) {

         //$model->created_by = Yii::$app->user->getId();
         //$model->created_at = time();
         //$model->updated_at = time();

        if ($model->save()) {
            return $this->redirect(['view', 'id' => $model->id]);
        }
    }
    return $this->render('create', [
        'model' => $model,
    ]);
}

save()就没有执行