Yii框架模型类的实现以及PHP5动态语言特性的应用
Yii框架提供一个代码生成器gii, 我们一般用它来生成模型类代码。模型类是对数据(表)操作进行封装 不过在模型类中你看不到get/set属性的方法,甚至看不到和表字段关联的属性成员变量,但并不影响我们直接操作其属性,仿佛这些属性就在那里一样。 其具体实现
Yii框架提供一个代码生成器gii, 我们一般用它来生成模型类代码。模型类是对数据(表)操作进行封装
不过在模型类中你看不到get/set属性的方法,甚至看不到和表字段关联的属性成员变量,但并不影响我们直接操作其属性,仿佛这些属性就在那里一样。
其具体实现方式,正是一些设计模式和PHP5动态语言特性的一个很好的应用案例。
举个例子,如下一个用户模型类,对应的数据表为users
现在我们想读取一条user记录,首先当然得构造一个User对象
$user = new User();
可以执行一下var_dump($user),你会发现这个$user有个私有的_md属性(模型的元数据),该属性类型为CActiveRecordMetaData。
在这个_md变量中包含了数据表结构定义(Schema)。
究竟执行了什么代码,会构造出这样一个对象,并且读取了数据表结构定义,下面我们来跟踪一下:
1、和其他面向对象语言一样,在调用new创建一个对象时,首先会调用类的构造函数,如下:
/** * Constructor. * @param string $scenario scenario name. See {@link CModel::scenario} for more details about this parameter. */ public function __construct($scenario='insert') { if($scenario===null) // internally used by populateRecord() and model() return; $this->setScenario($scenario); $this->setIsNewRecord(true); $this->_attributes=$this->getMetaData()->attributeDefaults; $this->init(); ...... }可以看到模型的构造函数调用了getMetaData方法,并且还给模型对象的属性成员变量($this->_attributes)赋予了缺省值。看起来模型对象元数据的构造以及数据表shema的读取和这个函数有关,继续
/** * Returns the meta-data for this AR * @return CActiveRecordMetaData the meta for this AR class. */ public function getMetaData() { if($this->_md!==null) return $this->_md; else return $this->_md=self::model(get_class($this))->_md; }getMetaData函数调用了self::model方法,这个函数我们很熟悉,是一个静态方法,根据类名返回模型类的静态实例。/** * Returns the static model of the specified AR class. * The model returned is a static instance of the AR class. */ public static function model($className=__CLASS__) { if(isset(self::$_models[$className])) return self::$_models[$className]; else { $model=self::$_models[$className]=new $className(null); $model->_md=new CActiveRecordMetaData($model); $model->attachBehaviors($model->behaviors()); return $model; } }
注意self::$_models是一个单例模式静态变量,你的应用所加载过的模型都被放在该对象数组中统一管理,你可以把它看作集中的模型对象管理器。看来,即使模型不再被实际使用,已经建立的模型对象也不会被释放。上述代码中创建了一个CActiveRecordMetaData对象,即前述的数据表Schema,注意User模型本身被作为其构造函数的参数被传递了进去,这里类似于应用了一种委托的模式,即模型类把获取数据表Schema的任务委托给CActiveRecordMetaData类。继续看下去,/** * Constructor. * @param CActiveRecord $model the model instance */ public function __construct($model) { $this->_model=$model; $tableName=$model->tableName(); if(($table=$model->getDbConnection()->getSchema()->getTable($tableName))===null) throw new CDbException(Yii::t('yii','The table "{table}" for active record class "{class}" cannot be found in the database.', array('{class}'=>get_class($model),'{table}'=>$tableName))); ...... }上述代码中getTable($tableName),这里应该是getTable('User')函数调用了CMysqlSchema的loadTable方法,最终通过SQL语句
SHOW FULL COLUMNS FROM ...获取到数据表的结构定义并赋值给了模型对象。
现在我们才刚刚了解到User对象中的元数据和缺省属性成员变量值是怎么来的。
接下来,才是动态语言特性相关部分,我们看看如何通过user对象,来所谓“动态”的操作数据属性的。
数据库中的users表中有Email字段,那么我们现在想给新创建的user对象的Email属性赋值,如下:
$user->Email = 'iefreer@hotmail.com';
如果是传统面向对象语言如c++/java,这里会报编译错误,因为User类没有定义Email成员变量。
而对于PHP5而言,由于语言对动态特性(魔法函数)的支持,这样的调用没有任何问题。我们看看它内部是怎么实现的。
如我之前的PHP语言动态特性文章中所言,设置对象的一个不存在的属性,会触发该对象的__set魔法函数:
/** * PHP setter magic method. * This method is overridden so that AR attributes can be accessed like properties. * @param string $name property name * @param mixed $value property value */ public function __set($name,$value) { if($this->setAttribute($name,$value)===false) { if(isset($this->getMetaData()->relations[$name])) $this->_related[$name]=$value; else parent::__set($name,$value); } }
上述代码中的setAttribute函数会把Email添加到$_attributes这个数组类型的成员变量中,也就是$_attributes充当了模型所对应的数据表属性动态管理器的功能。再看读取user对象属性的语句:
$email = $user->Email;
类似的,该语句将触发CActiveRecord类的__get魔法函数,会返回$_attributes数组中相应属性的值。
by iefreer