成员属性与外在关系php,YII2 技术剖析

YII基础

YII是一个给予组件的框架,YII几乎所有核心类都派生于(继承自)yii\base\Component。

在YII1中的组件是CComponent,YII2将YII1中的CCompnent拆分为两个类yii\base\Obect和yii\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

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

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

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/

文章表

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);

}

}

评论表

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

a50523028a6a

GII生成模型

本地化设置

/common/main-local.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

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

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

return [

];

生成CURD操作文件

a50523028a6a

生成CURD操作文件

修改后台公共语言包

/backend/messages/zh-CN/backend.php

return [

'Home'=>'首页',

'Create'=>'创建',

'Update'=>'更新',

'Search'=>'搜索',

'Reset'=>'重置',

'Delete'=>'删除',

'Are you sure you want to delete this item?'=>'确定删除吗?',

];

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值