YII2 技术剖析

YII基础

YII是一个给予组件的框架,YII几乎所有核心类都派生于(继承自)yii\base\Component
在YII1中的组件是CComponent,YII2将YII1中的CCompnent拆分为两个类yii\base\Obectyii\base\Component。其中Object轻量级些,通过getter和setter定义类的属性(property)。Component派生自Object,并支持事件(event)和行为(behavior),因此Component具有三个重要特性:

  • 属性(property)
  • 事件(event)
  • 行为(behavoir)
    这三个特性丰富和拓展类功能,改变类行为的重要切入点。因此Component在YII中地址极高。

Component由于增加event和behavior特性,在方便开发的同也牺牲了一定的效率。若开发中无需event和behavior特性,例如表示数据的类,可不从Component继承而从Object继承。典型的应用场景是若表示用户输入的一组数据优先使用Object。若需对对象的行为和响应处理的事件进行处理,毫无疑问应当采用Component,从效率上讲Object更接近原生php类。因此,在可能的情况下,应当优先使用Object。

Object

vendor/yiisoft/yii2/base/Object.php

class Object implementss Configurable
{
  public static function className()
  {
    return get_called_class();
  }

  public function __construct($config=[])
  {
    if(!empty($config)){
      Yii::configure($this,$config);
    }
    $this->init();
  }

  public function init()
  {
  }

  public function __get($name)
  {
    $getter = 'get'.$name;
    if(method_exists($this,$getter)){
      return $this->getter();
    }elseif(method_exists($this,'set'.$name)){
      throw new InvalidCallException('Getting write-only property: '.get_class($this).'::'.$name);
    }else{
      throw new UnkownPropertyException('Getting unknown property:'.get_class($this).'::'.$name);
    }
  }    
  
  public function __set($name,$value)
  {
    $setter = 'set'.$name;
    if(method_exists($this,$setter)){
      $this->$setter($value);
    }elseif(method_exists($this,'get'.$name)){
      throw new InvalidCallException('Setting read-only property:'.get_class($this).'::'.$name);
    }else{
      throw new UnknownPropertyException('Setting unknown property: '.get_class($this).'::'.$name);
    }
  }

  public function __isset($name)
  {
    $getter = 'get'.$name;
   if(method_exists($this,$getter)){
      return $this->getter()!=null;
    }else{
      return false;
    }
  }

  public function __unset($name)
  {
    $setter = 'set'.$name;
    if(method_exists($this,$setter)){
      $this->$setter(null);
    }elseif(method_exists($this,'get'.$name)){
      thrown new InvalidCallException('Unsetting read-only property:'.get_class($this).'::'.$name);
    }
  }

  public function __call($name,$params)
  {
    throw new UnkownMethodException('Calling unknown method:'.get_class($this).'::'.$name.'()');
  }

  public function hasProperty($name,$checkVars=true)
  {
    return $this->canGetProperty($name,$checkVars) || $this->canSetProperty($name,true);
  }

  public function canGetProperty($name,$checkVars=true){
    return method_exists($this,'get'.$name) || $checkVars && property_exists($this,$name);
  } 

  public function canSetProperty($name,$checkVars=true)
  {
     return method_exists($this,'set'.$name) || $checkVars && property_exists($this,$name);
  }

  public function hasMethod($name)
  {
    return method_exists($this,$name);
  }
}

Object的配置方法

Yii提供了一个统一的配置对象的方式。在Application对应的配置中
backend/web/index.php

$config = yii\helpers\ArrayHelper::merge(
  require(__DIR__.'/../../common/config/main.php'),
);
(new yii\web\Application($config)).run();

$config本质是各项配置项的数组,Yii统一使用数组的方式对对象进行配置,而实现其关键在于yii\base\Object定义的构造函数,对应的构造流程为

//构建函数以$config数组作为参数被自动调用
public function __construct($config=[])
{
  if(!empty($config)){
    //构造函数调用Yii::configure()对对象进行配置
    Yii::configure($this,$config);
  }
//构造函数调用对象的init()进行初始化
  $this->init();
}

数组配置对象$config的秘密在于Yii::configure()
/vendor/yiisoft/yii2/BaseYii.php

//配置过程
public static function configure($object,$properties)
{
 //遍历$config配置数组,键名作为属性名,为属性赋值。
  foreach($properties as $name=>$value){
    $object->name = $value;
  }
  return $object;
}

实现YII统一配置方式要点

  • 继承自 yii\base\Object
  • 为对象属性提供setter(),以正确处理配置过程。
  • 若需重载构造函数,将$config作为构造函数最后一个参数传递给父构造器。
  • 重载构造函数后需要调用父构造器
  • 若重载yii\base\Object::init(),必须在重载函数开头调用父类init()。

若配置项也是一个数组或对象,怎么办呢?
秘密在于setter(),由于$app进行配置时最终会调用Yii::configure(),该函数不区分配置项类型,直接遍历赋值。

属性

属性用于表征类的状态,从访问形式上看属性与成员变量没有区别。其区别在于,成员变量是就类的结构构成而言的概念,而属性是就类的功能逻辑而言的概念。

  • 成员变量是一个内在概念,反映的是类的结构构成。
  • 属性是一个外在概念,反映的是类的逻辑意义。
  • 成员变量没有读写权限控制,而属性可指定为只读或只写,或可读可写。
  • 成员变量不对读出作任何后处理,不对写入作任何预处理,而属性则可以。
  • public成员变量可视为一个可读可写、无任何预处理后或后处理的属性。
  • private成员变量由于外部不可见,与属性外在特性不相符,所以不能视为属性。
  • 属性会由某个或某些成员变量来表示,但属性与成员变量没有必然的对应关系。

Yii中由yii\base\Obect提供对属性的支持,Yii中属性通过php魔法函数__get()__set()产生作用。

  public function __get($name)
  {
    $getter = 'get'.$name;
    if(method_exists($this,$getter)){
      return $this->getter();
    }elseif(method_exists($this,'set'.$name)){
      throw new InvalidCallException('Getting write-only property: '.get_class($this).'::'.$name);
    }else{
      throw new UnkownPropertyException('Getting unknown property:'.get_class($this).'::'.$name);
    }
  }    
  
  public function __set($name,$value)
  {
    $setter = 'set'.$name;
    if(method_exists($this,$setter)){
      $this->$setter($value);
    }elseif(method_exists($this,'get'.$name)){
      throw new InvalidCallException('Setting read-only property:'.get_class($this).'::'.$name);
    }else{
      throw new UnknownPropertyException('Setting unknown property: '.get_class($this).'::'.$name);
    }
  }

实现属性的步骤
在读取和写入对象的一个不存在的成员变量时,__get__set()会被自动调用。Yii利用这点提供对属性的支持。因此要实现属性,通常由三个步骤:

  • 继承自 yii\base\Object
  • 声明一个用于保存该属性的私有成员变量
  • 提供getter和setter函数,用于访问和修改私有成员变量。

值得注意的是

  • 自动调用__get()__set()时机仅发生在访问不存在的成员变量时,若成员变量使用public修饰,则不会被调用。
  • 由于php对类方法不区分大小写,意味着属性名也是不区分大小写的。
  • __get()__set()都是public的,意味着所有属性都是public的。
  • __get()__set()都是非静态的,意味着无法使用static的属性。

属性相关方法

  • __isset()用于测试属性值是否不为null,在isset($object->property)时自动调用。
  • __unset()用于将属性设置为null,在unset($object->property)时自动调用。
  • hasProperty()用于测试是否具有某个属性,即定义了getter或setter。
  • canGetProperty()测试一个属性是否可读
  • canSetProperty()测试一个属性是否可写

环境部署

cd yii.cn
# 创建git仓库
git init
# 创建开发分支
git checkout -b dev
# 复制backend到api
cp -r backend/ api
# 修改环境配置文件加入api应用
vim environment/index.php

配置文件权限
environments/index.php

<?php
return [
    'Development' => [
        'path' => 'dev',
        'setWritable' => [
            //加入api模块并自动生成
            'api/models',
            'api/views',
            'api/controllers',
            'api/runtime',
            'api/web/assets',
            //后台权限设置
            'backend/models',
            'backend/views',
            'backend/controllers',
            'backend/runtime',
            'backend/web/assets',
            //前台权限设置
            'frontend/models',
            'frontend/views',
            'frontend/controllers',
            'frontend/runtime',
            'frontend/web/assets',
            //公共文件权限设置
            'common/models',
        ],
        'setExecutable' => [
            'yii',
            'yii_test',
        ],
        'setCookieValidationKey' => [
            'api/config/main-local.php',
            'backend/config/main-local.php',
            'frontend/config/main-local.php',
        ],
    ],
    'Production' => [
        'path' => 'prod',
        'setWritable' => [
            'api/runtime',
            'backend/runtime',
            'api/web/assets',
            'backend/web/assets',
            'frontend/runtime',
            'frontend/web/assets',
        ],
        'setExecutable' => [
            'yii',
        ],
        'setCookieValidationKey' => [
            'api/config/main-local.php',
            'backend/config/main-local.php',
            'frontend/config/main-local.php',
        ],
    ],
];

使用./init命令生成应用

本地域名配置

  • 前台 yii.cn
  • 后台 adm.yii.cn
  • 接口 api.yii.cn
# 配置vim
wget -qO- https://raw.github.com/ma6174/vim/master/setup.sh | sh -x
# 配置nginx多域名
sudo /usr/local/nginx/conf/vhost/yii.conf
server
    {
        listen 80;
        #listen [::]:80;
        server_name yii.cn ;
        index index.html index.htm index.php default.html default.htm default.php;
        root  /home/wwwroot/yii.cn/frontend/web/;

        include other.conf;
        #error_page   404   /404.html;
        include enable-php.conf;

        location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$
        {
            expires      30d;
        }

        location ~ .*\.(js|css)?$
        {
            expires      12h;
        }

        location ~ /\.
        {
            deny all;
        }

        access_log  /home/wwwlogs/yii.cn.log;
    }
# nginx重新加载配置生效
service nginx reload

用户登录

创建数据库

# 连接数据库
mysql -uroot -proot
# 查看数据库
show databases;
# 创建数据库
create database yii;
# 为数据库创建专用账户
grant all privileges on yii.* to yii@localhost identified by "yii"

配置数据库连接参数
yii/common/config/main-local.php
修改默认数据迁移文件
/console/migrations/m130524_201442_init.php

<?php

use yii\db\Migration;

class m130524_201442_init extends Migration
{
    const TBLNAME = "{{%user}}";//表名
    //多操作使用safeUp
    public function safeUp()
    {
        $tableOptions = null;
        if ($this->db->driverName === 'mysql') {
            //自增主键从100开始表示前100的账户用于特殊设置,其次为避免安全隐患
            $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB AUTO_INCREMENT=100';
        }

        $this->createTable(self::TBLNAME, [
            'id' => $this->primaryKey(),
            'username' => $this->string()->notNull()->unique(),
            'auth_key' => $this->string(32)->notNull(),
            'password_hash' => $this->string()->notNull(),
            'password_reset_token' => $this->string()->unique(),
            'email' => $this->string()->notNull()->unique(),
            'role'=>$this->integer(11)->notNull()->defaultValue(10),

            'status' => $this->smallInteger()->notNull()->defaultValue(10),
            'created_at' => $this->integer(11)->notNull(),
            'updated_at' => $this->integer(11)->notNull(),
        ], $tableOptions);
        //创建索引
        $this->createIndex('index_username', self::TBLNAME, ['username'], true);
        $this->createIndex('index_email',self::TBLNAME, ['email'],true);
    }

    public function safeDown()
    {
        $this->dropTable(self::TBLNAME);
    }
}

使用migration创建数据表

./yii migrate

进入数据库查看创建的表结构

show create table user;

新增记录

创建控制器
/cosole/controllers/InitController.php

<?php
namespace console\controllers;

use common\models\User;

class InitController extends \yii\console\Controller
{
    //新增用户
    public function actionUser()
    {
        echo "Create init user...\n";
        //获取参数
        $username = $this->prompt('Username:');
        $email = $this->prompt('Email:');
        $password = $this->prompt('Password:');
        $auth_key = $this->prompt('Authkey:');
        //设置字段
        $model = new User();
        $model->username = $username;
        $model->email = $email;
        $model->password = $password;
        $model->auth_key = $auth_key;
        //保存失败输出错误
        if(!$model->save()){
            foreach($model->getErrors() as $error){
                foreach($error as $err){
                    echo "$err\n";
                }
            }
            return 1;
        }
        //命令行返回0为正确
        return 0;

    }
}

使用GII创建模型,用于验证规则。
http://yii.cn/index.php?r=gii
修改user模型文件的验证规则
/common/models/user.php

    public function rules()
    {
        return [
            ['status', 'default', 'value' => self::STATUS_ACTIVE],
            ['status', 'in', 'range' => [self::STATUS_ACTIVE, self::STATUS_DELETED]],

            [['username', 'email'], 'required'],
            [['username', 'email'], 'string', 'max' => 255],
            [['username', 'email'], 'unique'],
            //用户名为字母数字组合
            [['username'], 'match', 'pattern'=>'/^[a-z]\w*$/i'],
            //邮箱格式
            [['email'], 'email'],
        ];
    }

文章与评论

使用migration生成数据表

// 创建文章
./yii migration/create section
// 创建评论
./yii migration/create comment

修改migration数据迁移文件
/console/migrations/
文章表

<?php

use yii\db\Migration;

class m170519_152033_section extends Migration
{
    const TBLNAME = "{{%section}}";
    public function safeUp()
    {
        $tableOptions = null;
        if($this->db->driverName === 'mysql'){
            $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB AUTO_INCREMENT=100';
        }
        $this->createTable(self::TBLNAME, [
            'id'=>$this->primaryKey(),
            'title'=>$this->string(100)->notNull(),
            //无限级分类
            'ancestor'=>$this->integer(11)->defaultValue(null),
            'parent'=>$this->integer(11)->defaultValue(null),
            //双向数据链表
            'next'=>$this->integer(11)->defaultValue(null),
            'prev'=>$this->integer(11)->defaultValue(null),
            'toc_mode'=>$this->smallInteger()->defaultValue(0),
            'status'=>$this->smallInteger()->defaultValue(0),
            'comment_status'=>$this->smallInteger()->defaultValue(0),
            'comment_num'=>$this->integer()->defaultValue(0),
            'content'=>$this->text(),
            'ver'=>$this->bigInteger()->notNull()->defaultValue(0),
            'created_at'=>$this->integer(11)->notNull()->defaultValue(0),
            'updated_at'=>$this->integer(11)->defaultValue(null),
            'created_by'=>$this->integer(11)->defaultValue(null),
            'updated_by'=>$this->integer(11)->defaultValue(null),
        ],$tableOptions);
        //添加外键
        $this->addForeignKey('fk_section_ancestor', self::TBLNAME,'[[ancestor]]', self::TBLNAME, '[[id]]', 'RESTRICT', 'CASCADE');
        $this->addForeignKey('fk_section_parent', self::TBLNAME,'[[parent]]', self::TBLNAME, '[[id]]', 'RESTRICT', 'CASCADE');
        $this->addForeignKey('fk_section_next', self::TBLNAME,'[[next]]', self::TBLNAME, '[[id]]', 'RESTRICT', 'CASCADE');
        $this->addForeignKey('fk_section_prev', self::TBLNAME,'[[prev]]', self::TBLNAME, '[[id]]', 'RESTRICT', 'CASCADE');
        $this->addForeignKey('fk_section_createdby', self::TBLNAME,'[[created_by]]', '{{%user}}', '[[id]]', 'RESTRICT', 'CASCADE');
        $this->addForeignKey('fk_section_updatedby', self::TBLNAME,'[[updated_by]]', '{{%user}}', '[[id]]', 'RESTRICT', 'CASCADE');
    }

    public function safeDown()
    {
        $this->dropTable(self::TBLNAME);
    }
}

评论表

<?php

use yii\db\Migration;

class m170519_152248_comment extends Migration
{
    const TBLNAME = "{{%comment}}";
    public function safeUp()
    {
        $tableOptions = null;
        if($this->db->driverName === 'mysql'){
            $tableOptions = 'CHARACTER SET utf8 COLLATE utf8_unicode_ci ENGINE=InnoDB AUTO_INCREMENT=100';
        }
        $this->createTable(self::TBLNAME, [
           'id'=>$this->primaryKey(),
            'section_id'=>$this->integer(11)->notNull(),
            'parent'=>$this->integer(11)->defaultValue(null),
            'status'=>$this->smallInteger()->defaultValue(0),
            'thumbsup'=>$this->integer()->defaultValue(0),
            'thumbsdown'=>$this->integer()->defaultValue(0),
            'content'=>$this->text(),
            'created_at'=>$this->integer(11)->notNull()->defaultValue(0),
            'updated_at'=>$this->integer(11)->notNull()->defaultValue(0),
            'created_by'=>$this->integer(11)->defaultValue(null),
            'updated_by'=>$this->integer(11)->defaultValue(null),
        ],$tableOptions);
        //添加外键
        $this->addForeignKey('fk_comment_section', self::TBLNAME,'[[section_id]]', '{{%section}}', '[[id]]', 'RESTRICT', 'CASCADE');
        $this->addForeignKey('fk_comment_parent', self::TBLNAME,'[[parent]]', self::TBLNAME, '[[id]]', 'RESTRICT', 'CASCADE');
        $this->addForeignKey('fk_comment_createdby', self::TBLNAME,'[[created_by]]', '{{%user}}', '[[id]]', 'RESTRICT', 'CASCADE');
        $this->addForeignKey('fk_comment_updatedby', self::TBLNAME,'[[updated_by]]', '{{%user}}', '[[id]]', 'RESTRICT', 'CASCADE');
    }

    public function safeDown()
    {
        $this->dropTable(self::TBLNAME);
    }

}

注意在创建表失败后由于外键造成删除失败的解决方案

set foreign_key_checks = 0;
drop table if exists migration;
drop table if exists user;
drop table if exists section;
drop table if exists comment;
set foreign_key_checks = 1;

使用GII生成model

4933701-d07eefbe0701cdb5.png
GII生成模型

本地化设置
/common/main-local.php

<?php
return [
    'vendorPath' => dirname(dirname(__DIR__)) . '/vendor',
    //设置语种
    'language'=>'zh-CN',
    'components' => [
        'cache' => [
            'class' => 'yii\caching\FileCache',
        ],
        //本地化设置
        'i18n' => [
            'translations' => [
                'common*' => [
                    'class' => 'yii\i18n\PhpMessageSource',
                    'basePath' => '@common/messages',
                    'fileMap' => [
                        'common' => 'common.php',
                        'common/section' => 'section.php',
                        'common/comment' => 'comment.php',
                    ],
                ],
                'backend*' => [
                    'class' => 'yii\i18n\PhpMessageSource',
                    'basePath' => '@backend/messages',
                    'fileMap' => [
                        'backend' => 'backend.php',
                        'backend/section' => 'section.php',
                        'backend/comment' => 'comment.php',
                    ],
                ],
                'frontend*' => [
                    'class' => 'yii\i18n\PhpMessageSource',
                    'basePath' => '@frontend/messages',
                    'fileMap' => [
                        'frontend' => 'frontend.php',
                        'frontend/section' => 'section.php',
                        'frontend/comment' => 'comment.php',
                    ],
                ],
                'api*' => [
                    'class' => 'yii\i18n\PhpMessageSource',
                    'basePath' => '@api/messages',
                    'fileMap' => [
                        'api' => 'api.php',
                        'api/section' => 'section.php',
                        'api/comment' => 'comment.php',
                    ],
                ],
                '*' => [
                    'class' => 'yii\i18n\PhpMessageSource',
                    'basePath' => '@app/messages',
                ],
            ],
        ],
    ],
];

创建本地化语言包
/common/messages/zh-CN/section.php

<?php
return [
    'Id' => '标识',
    'Title' => '标题',
    'Parent' => '父章节',
    'Next' => '下一章节',
    'Prev' => '前一章节',
    'Toc Mode' => '目录模式',
    'Status' => '状态',
    'Comment Mode' => '评论模式',
    'Comment Num' => '评论数',
    'Content' => '内容',
    'Ver' => '版本',
    'Created At' => '创建于',
    'Updated At' => '更新于',
    'Created By' => '创建者',
    'Updated By' => '更新者',
];

/common/messages/zh-CN/comment.php

<?php
return [
    'Id' => '标识',
    'Section' => '章节',
    'Parent' => '父评论',
    'Status' => '状态',
    'Comment Mode' => '评论模式',
    'Comment Num' => '评论数',
    'Content' => '内容',
    'Ver' => '版本',
    'Created At' => '创建于',
    'Updated At' => '更新于',
    'Created By' => '创建者',
    'Updated By' => '更新者',
];

创建前后台语言包文件
/backend/messages/zh-CN/backend.php
/backend/messages/zh-CN/section
/backend/messages/zh-CN/comment.php

<?php
return [

];

生成CURD操作文件

4933701-a0840fdb9f7a4b54.png
生成CURD操作文件

修改后台公共语言包
/backend/messages/zh-CN/backend.php

<?php
return [
    'Home'=>'首页',
    'Create'=>'创建',
    'Update'=>'更新',
    'Search'=>'搜索',
    'Reset'=>'重置',
    'Delete'=>'删除',
    'Are you sure you want to delete this item?'=>'确定删除吗?',
];

https://github.com/linuor

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值