免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 2795 | 回复: 3
打印 上一主题 下一主题

[pear 数据库]PEAR::MDB2模块调用机制 [复制链接]

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2006-07-12 01:42 |只看该作者 |倒序浏览
从使用PEAR以来,使用得最多的是PEAR::DB,但是这么几年来,PEAR::DB一直没有什么新的变化。在最新的一个项目里,我开始尝试使用 PEAR::MDB2,这个包现在已经替代了PEAR::DB,而且提供了丰富的特性,从它的结构上来看,也提供了更多的扩展可能。
可能是作者忙于开发或者这个包太新,相关的文档没有跟上,所以只能自己阅读代码来搞清楚它提供的各种方法。
如何使用最基本的连接、查询等,在PEAR手册中(http://pear.php.net/manual/en/package.database.mdb2.php)已经提供了简单的说明,在此我只讨论MDB2如何调用各种模块的机制。
除了基本的sql查询功能外,MDB2还提供了几个扩展模块,比如Manager(操纵数据库对象),这个模块可以添加、删除数据库,添加、修改、删除表。要使用这个模块提供的方法有三种方式:
$mdb->loadModule('Manager');
$mdb->manager->createTable($name, $fields);   // PHP4
或者
$mdb->loadModule('Manager');
$mdb->create($name, $fields);                 // PHP5
或者
$mdb->mgCreateTable($name, $fields);          // PHP5
前两种方式必须事先用loadModule('Manager')载入模块,否则无法调用。
先来研究一下loadModule()

  1. function &loadModule($module, $property = null, $phptype_specific = null)
  2. {
  3.     if (!$property) {
  4.         $property = strtolower($module);
  5.     }

  6.     if (!isset($this->{$property})) {
  7.         $version = $phptype_specific;
  8.         if ($phptype_specific !== false) {
  9.             $version = true;
  10.             $class_name = 'MDB2_Driver_'.$module.'_'.$this->phptype;
  11.             $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php';
  12.         }
  13.         if ($phptype_specific === false
  14.             || (!MDB2::classExists($class_name) && !MDB2::fileExists($file_name))
  15.         ) {
  16.             $version = false;
  17.             $class_name = 'MDB2_'.$module;
  18.             $file_name = str_replace('_', DIRECTORY_SEPARATOR, $class_name).'.php';
  19.         }

  20.         $err = MDB2::loadClass($class_name, $this->getOption('debug'));
  21.         if (PEAR::isError($err)) {
  22.             return $err;
  23.         }

  24.         // load modul in a specific version
  25.         if ($version) {
  26.             if (method_exists($class_name, 'getClassName')) {
  27.                 $class_name_new = call_user_func(array($class_name, 'getClassName'), $this->db_index);
  28.                 if ($class_name != $class_name_new) {
  29.                     $class_name = $class_name_new;
  30.                     $err = MDB2::loadClass($class_name, $this->getOption('debug'));
  31.                     if (PEAR::isError($err)) {
  32.                         return $err;
  33.                     }
  34.                 }
  35.             }
  36.         }

  37.         if (!class_exists($class_name)) {
  38.             $err =& $this->raiseError(MDB2_ERROR_LOADMODULE, null, null,
  39.                 "unable to load module '$module' into property '$property'");
  40.             return $err;
  41.         }
  42.         $this->{$property} =& new $class_name($this->db_index);
  43.         $this->modules[$module] =& $this->{$property};
  44.         if ($version) {
  45.             // this will be used in the connect method to determine if the module
  46.             // needs to be loaded with a different version if the server
  47.             // version changed in between connects
  48.             $this->loaded_version_modules[] = $property;
  49.         }
  50.     }

  51.     return $this->{$property};
  52. }
复制代码

前边一部分是根据模块的名字载入对应的文件,并且检查文件中是否包含了正确的类,稍微有点点搅,不过熟悉目录结构之后就可以看懂。(看不明白也无所谓,只要不自己写模块)

关键在于实例化的部分:
$this->{$property} =& new $class_name($this->db_index);
$this->modules[$module] =& $this->{$property};
在实例化了Manager模块包含的类之后,把这个实例作为当前MDB2实例的同名属性,另外再添加为modules属性数组的元素。

所以在PHP4下,可以用$mdb->manager->createTable($name, $fields);调用模块的方法。
调用的时候注意用模块名字的小写,因为$property = strtolower($module);

如果是PHP5的话,就可以使用另外两种更加方便的调用方式,原因是PHP5的新特性__call()方法。

  1. function __call($method, $params)
  2. {
  3.     $module = null;
  4.     if (preg_match('/^([a-z]+)([A-Z])(.*)$/', $method, $match)
  5.         && isset($this->options['modules'][$match[1]])
  6.     ) {
  7.         $module = $this->options['modules'][$match[1]];
  8.         $method = strtolower($match[2]).$match[3];
  9.         if (!isset($this->modules[$module]) || !is_object($this->modules[$module])) {
  10.             $result =& $this->loadModule($module);
  11.             if (PEAR::isError($result)) {
  12.                 return $result;
  13.             }
  14.         }
  15.     } else {
  16.         foreach ($this->modules as $key => $foo) {
  17.             if (is_object($this->modules[$key])
  18.                 && method_exists($this->modules[$key], $method)
  19.             ) {
  20.                 $module = $key;
  21.                 break;
  22.             }
  23.         }
  24.     }
  25.     if (!is_null($module)) {
  26.         return call_user_func_array(array(&$this->modules[$module], $method), $params);
  27.     }
  28.     trigger_error(sprintf('Call to undefined function: %s::%s().', get_class($this), $method), E_USER_ERROR);
  29. }
复制代码

当调用不存在的mgCreateTable()和createTable()方法时,__call()方法会被调用。

preg_match('/^([a-z]+)([A-Z])(.*)$/', $method, $match)
试图把调用的方法名拆分成为"mg C reateTable"(mgCreateTable)三段
所以使用这种方法要注意大小写,不然无法正确的拆分

isset($this->options['modules'][$match[1]])
第一段是模块名的缩写,把这个缩写拿到$mdb->options属性中去比较

默认的,options属性的内容是

  1. $mdb->options['modules'] = array(
  2.     'ex' => 'Extended',
  3.     'dt' => 'Datatype',
  4.     'mg' => 'Manager',
  5.     'rv' => 'Reverse',
  6.     'na' => 'Native',
  7.     'fc' => 'Function',
  8. );
复制代码

$module = $this->options['modules'][$match[1]];
当第一段的内容是mg时,对应的模块就是Manager了

$method = strtolower($match[2]).$match[3];
那么方法就是createTable了

if (!isset($this->modules[$module]) || !is_object($this->modules[$module]))
$result =& $this->loadModule($module);
在modules属性中查看是否已经有被调用模块的实例,modules属性是用来存放模块实例的数组,刚才在loadModule()部分已经有说明
如果没有对应的实例则loadModule()来创建实例,所以这种调用方式不用事先loadModule()

foreach ($this->modules as $key => $foo)
当三段法无效的时候,遍历当前所有的模块实例

if (is_object($this->modules[$key]) && method_exists($this->modules[$key], $method)
使用method_exists()函数寻找每个实例是否有被调用的方法

return call_user_func_array(array(&$this->modules[$module], $method), $params);
最后调用找到的模块实例的对应方法

从以上分析可以看出MDB2对它的扩展模块的调用方式,只要遵守这些约定,我们也可以自行扩展出自己的模块。
不过$mdb->createTable()这种调用方式有个问题,如果两个模块都具有相同的方法就可能出问题,写自己的模块需要注意这一点。

暂时先写到这里,以后再继续讨论几个内置模块的具体使用。

[ 本帖最后由 夜猫子 于 2006-7-12 02:00 编辑 ]

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
2 [报告]
发表于 2006-07-12 01:44 |只看该作者

用PEAR::MDB2 Manager模块控制表

PEAR::MDB2除了提供PEAR::DB已有的sql处理功能之外,还提供了一个非常强大的扩展功能:添加/删除/修改数据库对象
可以控制的数据库对象有:

    * 库
    * 表
    * 索引(普通索引、主键、唯一索引)

调用Manager模块
$mdb->loadModule('Manager');
或者直接用魔法调用方式
$mdb->mgCreateTable();
$mdb->mgAlterTable();
模块调用机制参看《PEAR::MDB2模块调用机制》

创建数据库
$mdb->createDatabase($dbname);
建立数据库很简单,只要指定数据库名字就好,可惜的是,创建新数据库时不能指定更多的参数,比如postgresql建立数据库时还可以指定encoding、owner等等。

定义字段
MDB2把字段的数据类型抽象为:

    * text   字符类型
    * clob   大对象(文本)
    * blob   大对象(二进制)
    * integer    整数
    * boolean    布尔类型
    * date       日期类型(YY-MM-DD)
    * time       时间类型(HH:MM:SS)
    * timestamp  日期加时间(YYYY-MM-DD HH:MM:SS)
    * float      浮点数类型
    * decimal    任意精度数值类型

字段的参数有:

    * length    长度
    * type      数据类型
    * default   默认值
    * unsigned  无符号
    * notnull   不允许空值
    * fixed     字符类型是否用空格填充不足的长度

例如我想要,创建一个user_info表,包含三个字段:id(编号 整数)、user_name(用户名)、password(MD5密码),字段的描述就是

  1. $fields = array(
  2.     'id' => array(
  3.         'type'      => 'integer',
  4.         'notnull'   => 1
  5.     ),
  6.     'username' => array(
  7.         'type'      => 'text',
  8.         'length'    => 20,
  9.         'notnull'   => 1
  10.     ),
  11.     'password' => array(
  12.         'type'      => 'text',
  13.         'length'    => 32,
  14.         'notnull'   => 1
  15.     )
  16. );
复制代码

如果给text类型指定length,字段会使用varchar或者char,如果fixed为真则为char,fixed为假则为varchar,fixed默认为false
如果没有给text指定length,使用text类型
某些DBMS可以把默认值设置为函数,比如now(),但是在这里无法设置默认值为函数,如果指定函数默认值,默认值实际上设置成函数运行后的值

创建表
创建名字叫user_info的用户信息表
$mdb->createTable('user_info', $fields);

修改表
表的修改使用alterTable()方法,可以对表进行的操作有

    * name    重命名表名
    * add     添加字段
    * remove  删除字段
    * change  修改字段
    * rename  字段重命名

添加字段
给user_info表添加三个字段,realname(真实姓名)、reg_date(注册时间)、login_time(最后一次登录时间)

  1. $change = array(
  2.     'add' => array(
  3.         'realname'  => array(
  4.             'type'      => 'text',
  5.             'length'    => 20
  6.         ),
  7.         'reg_date'  => array(
  8.             'type'      => 'timestamp',
  9.             'notnull'   => 1
  10.         ),
  11.         'login_time' => array(
  12.             'type'      => 'timestamp'
  13.         )
  14.     )
  15. );
  16. $mdb->alterTable('user_info', $change);
复制代码

修改字段
把reg_date字段从timestamp类型改变为date类型,realname不允许为空

  1. $change = array(
  2.     'change' => array(
  3.         'reg_date' => array(
  4.             'type'  => 'timestamp',
  5.             'definition' => array(
  6.                 'type'  => 'date'
  7.             )
  8.         ),
  9.         'realname' => array(
  10.             'notnull' => 1
  11.         )
  12.     )
  13. );
  14. $mdb->alterTable('user_info', $change);
复制代码

删除字段
删除login_time字段

  1. $change = array(
  2.     'remove' => array(
  3.         'login_time' => array()
  4.     )
  5. );
  6. $mdb->alterTable('user_info', $change);
复制代码

字段重命名
重命名realname为real_name

  1. $change = array(
  2.     'rename' => array(
  3.         'realname' => array(
  4.             'name' => 'real_name'
  5.         )
  6.     )
  7. );
  8. $mdb->alterTable('user_info', $change);
复制代码

重命名表
把user_info表重命名为userinfo

  1. $change = array(
  2.     'name' => 'userinfo'
  3. );
  4. $mdb->alterTable('user_info', $change);
复制代码

创建索引
在id字段上创建主键

  1. $con_define = array(        // 主键定义
  2.     'fields' => array('id' => array()),
  3.     'primary' => 1
  4. );
  5. $con_name = 'pk_user_info_id';  // 主键名
  6. $mdb->createConstraint('user_info', $con_name, $con_define);
复制代码

在username字段上创建唯一索引

  1. $con_define = array(        // 唯一索引定义
  2.     'fields' => array('username' => array()),
  3.     'unique' => 1
  4. );
  5. $con_name = 'uk_user_info_username';    // 唯一索引名
  6. $mdb->createConstraint('user_info', $con_name, $con_define);
复制代码

删除刚才建立的约束
$mdb->dropConstraint('user_info', 'pk_user_info_id');
$mdb->dropConstraint('user_info', 'uk_user_info_username');

在username、password上建立双字段索引

  1. $idx_define = array(
  2.     'fields' => array(
  3.         'username' => array(),
  4.         'password' => array()
  5.     )
  6. );
  7. $idx_name = 'idx_user_info_auth';
  8. $mdb->createIndex('user_info', $idx_name, $idx_define);
复制代码

删除idx_user_info_auth索引
$mdb->dropIndex('user_info', 'idx_user_info_auth');

Manager模块对一些通常的操作进行了封装,可以满足大部分常见的需求,但是如果你需要是更加精细的控制,Manager就无法做到了,比如:
// PostgreSQL
CREATE INDEX code_idx ON films(code) TABLESPACE indexspace

最后建议有疑惑的时候看还是看看相关的代码,代码就是最好的文档

[ 本帖最后由 夜猫子 于 2006-7-12 01:49 编辑 ]

论坛徽章:
1
荣誉版主
日期:2011-11-23 16:44:17
3 [报告]
发表于 2006-07-12 01:55 |只看该作者
pear安装方法,参见:http://bbs.chinaunix.net/viewthread.php?tid=22107
pear安装好之后,PHP安装目录下会有一个pear.bat文件,可以在命令行下执行


pear list                 // 显示已经安装的pear包
pear install  packagename    // 安装指定的包,比如 pear install mdb2
pear uninstall packagename // 卸载指定的包
pear list-upgrades      // 显示安装的包里可以升级的包
pear upgrade-all   // 升级所有可以升级的包
pear help         // 显示帮助

[ 本帖最后由 夜猫子 于 2006-7-12 01:58 编辑 ]

论坛徽章:
0
4 [报告]
发表于 2006-07-12 09:35 |只看该作者
好文章,我转到pearchina那去了,应该没有问题八
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP