你可能有一个站点,有些页面要处理数据库。你可能想使用PHPLIB,但是不想为了适应它而改变现存的数据库。这儿给出实现你的梦想的答案。PHPLIB和多个数据库。
实现它需要对PHPLIB进行扩充。本文解释了如何创建扩充。你会发现,这篇文章会帮助你在其它方面对PHPLIB进行扩充。阅读完这篇文章后,思考一下PHPLIB可以完成你想要的98%的情况吧。
这篇文章所提及的PHPLIB的扩充的建议已经提交给了PHPLIB的开发组。因此,在未来的版本中,可能会出现这些扩充。在你的网页中的其它的代码将帮助你组织你的数据库管理。
数据库管理
你可以将每一个表放在一个巨大的数据库中。然而,总有一天会刺伤你。对数据库进行管理将会使创伤减小到最小。当你的数据库对一个服务器来说太大时会出现什么情况?当一个服务器不能处理IO吞吐量或者没有足够的内存进行处理又会怎么样?将现有的数据库进行拆分很难,但是从分离的数据库开始则容易多了,并且好的数据库管理会很有帮助。
如果你经营一家书店,你可能有作者列表,带有价格的书目列表,当前库存列表和订单列表。随着你的业务的发展,订单列表会增加,并且每一个订单会占用很多的磁盘空间。一种可能性就是有一天你会将订单直接放进财务系统。
现在开始将订单放在分离的数据库中。因为库存的数量是随着订单而变化的,所以将库存数据放在同一个数据库中。
作者列表和书目列表为一些静态信息,它们会经常被读到,但是很少改变。实际上,唯一的改变可能就是对于作者记录会每5年一次,这可能是当这个作者写了一本新书(或死亡了)。这些数据可能使用与订单数据库完全不同的配置。
包含PHPLIB
PHPLIB通过一个名为DB_Sql的类来操作SQL数据库。在你的代码中包含适合你的数据库的版本。在这个例子中,我使用MySQL版本。
为了在你的代码中得到DB_Sql,在PHPLIB要求的目录下安装PHPLIB文件。然后,找到你的cgi-bin目录,然后在cgi-bin目录下创建phplib目录。接着,拷贝所有的PHPLIB中的.inc文件到phplib目录下。最后,将phplib目录放在php.ini文件中include_path = 的那行上。
include_path是PHP引用在include()或require()中文件名的地方。在我的NT工作站上,包含路径是
include_path = ".;i:/project52/includes;i:/project52/phplib";
在Linux机器上,则为
include_path = ".;/home/httpd/includes;/home/httpd/phplib";
在每一个PHP页面的顶端为
require(common.php3);
?>
common.php3在包含目录中,包含对每一页都通用的所有的数据和函数。在common.php3中,为
require(db_mysql.inc);
require(ct_sql.inc);
require(session.inc);
require(auth.inc);
require(perm.inc);
require(user.inc);
require(page.inc);
?>
阅读PHPLIB文档(http://phplib.netuse.de),也可以在http://www.phpbuilder.com下查找一些好文章,来了解你需要包括些什么。Db_mysql.inc包含了DB_Sql类的定义。如果你想将MySQL改换成PostGreSQL数据库,将db_mysql.inc改成对db_pgsql.inc的包含。那里有10个.inc文件,涵盖了MS SQL,Oracle,Sybase和其它一些数据库。
请注意,在这个例子中,require()和include()是完全一样的。Require()和include()工作方式不同,当用在代码中间或在if()语句中时,结果是不一样的。
扩充PHPLIB
PHPLIB处理数据库是通过从DB_Sql类创建的一个对象实现的。Db_mysql.inc包含了DB_Sql类,为MySQL 进行了修改。我们将通过向common.php3添加代码来扩充DB_Sql,在包含db_mysql.inc的行的后面。
DB_Sql包含了很多进行查询的函数。我们想改变的一个是:
/* public: 连接管理 */
function connect($Database = "", $Host = "", $User = "", $Password = "") {
/* 缺省处理 */
if ("" == $Database)
$Database = $this->Database;
if ("" == $Host)
$Host = $this->Host;
if ("" == $User)
$User = $this->User;
if ("" == $Password)
$Password = $this->Password;
/* 建立连接,选择数据库 */
if ( 0 == $this->Link_ID ) {
$this->Link_ID=mysql_pconnect($Host, $User, $Password);
if (!$this->Link_ID) {
$this->halt("pconnect($Host, $User, $Password) failed.");
return 0;
}
if (!@mysql_select_db($Database,$this->Link_ID)) {
$this->halt("cannot use database ".$this->Database);
return 0;
}
}
return $this->Link_ID;
}
?>
在你的db_mysql.inc中找到connnect()函数(或针对你的数据库的.inc文件),然后将其拷贝到common.php3中,在db_mysql.inc包含之后的某个地方。你应该将其封装在类的定义中,并按照本文最后所描述的那样。
我发现那段代码很难读懂。因此,要做的第一件事就是让拷贝的代码可读:
/* public: 连接管理 */
function connect($Database = "", $Host = "", $User = "", $Password = "") {
/* 缺省处理 */
if ("" == $Database) {
$Database = $this->Database;
}
if ("" == $Host) {
$Host = $this->Host;
}
if ("" == $User) {
$User = $this->User;
}
if ("" == $Password) {
$Password = $this->Password;
}
/* 建立连接选择数据库 */
if ( 0 == $this->Link_ID ) {
$this->Link_ID=mysql_pconnect($Host, $User, $Password);
if (!$this->Link_ID) {
$this->halt("pconnect($Host, $User, $Password) failed.");
return 0;
}
if (!@mysql_select_db($Database,$this->Link_ID)) {
$this->halt("cannot use database ".$this->Database);
return 0;
}
}
return $this->Link_ID;
}
?>
我将代码进行缩近排列,这样对于包括起来的代码,层次关系可以让我对括号(译注:指大括号)进行匹配。这样做可以避免象因为丢失括号而引起的错误。对于单独的行我也增加了括号。PHP允许你当if语句后只有单一代码行时不使用括号。一旦你添加了额外的代码,这个简写就失败了。我建议总是加上括号,以免在后面增加代码时出现错误。
现在,该修改connect代码了。注意connect()代码是如何检测一个连接的存在,并且当连接不存在时是如何创建连接的。这个connect()函数在每一个数据库查询之前被调用。不幸的是,当创建连接时它只选择一次数据库。如果PHP页面使用一个以上的数据库,connect()代码将不会看到数据库的变化。
有几种方式可以修改代码。我们正在寻找对PHPLIB影响最小的修改方法,并且可以在我们需要诊断一个 问题时,可以让我们显示活动数据库的状态。需要超出PHPLIB的两个变量是连接id和数据库名称。因此,使这两个变量对PHPLIB外部可见。在common.php3:
$db_connection = 0; // 普通数据库连接id
$db_database = ""; // 当前数据库名字
?>
接着,我们修改PHPLIB来保存连接id和数据库名字在这些字段中。你的其它的代码可以设置和使用同一字段。如果你需要在诊断问题时知道哪一个数据库正在使用,在你的页面中插入这些代码:
Print("
db_database: " . $db_database . "
");
?>
(有一些更简洁的方法来书写打印语句。这个方法可以在彩色代码编辑器中加亮变量名。这个方法对于数组和其它复合变量名工作也很稳定。)
我们如何让connect()来使用新的变量呢?我们可在项部加入额外的行,所以你可以:
{
globals $db_connect, $db_database;
/* 缺省处理 */
?>
这行使我们的外部变量在connect()中有效。
下面是更传统些的方法。在$db_database后面直接加入:
function db_connect($db_connect_host="", $db_connect_user="",$db_connect_pass="") {
globals $db_connect;
if(!empty($db_connect_host)) {
$db_connect = mysql_pconnect($db_connect_host,
$db_connect_user, $db_connect_pass);
}
return($db_connect);
}
function db_database($db_database_new="") {
globals $db_database;
if(!empty($db_database_new)) {
$db_database = @mysql_select_db($db_database_new, db_connect());
}
return($db_database);
}
?>
通过定义这些通用函数一次,你可以在各种地方得到通用变量,不需要在所有地方增加全局行。这儿就是使用了我们的db函数的通用函数:
function connect($Database = "", $Host = "", $User = "", $Password = "") {
/* 缺省处理 */
if ("" == $Database) {
$Database = $this->Database;
}
if ("" == $Host) {
$Host = $this->Host;
}
if ("" == $User) {
$User = $this->User;
}
if ("" == $Password) {
$Password = $this->Password;
}
/* 建立连接,选择数据库 */
if ( 0 == db_connect()) {
$this->Link_ID = db_connect($Host, $User, $Password);
if (!$this->Link_ID) {
$this->halt("pconnect($Host, $User, $Password) failed.");
return 0;
}
}
if (0 != db_connect()) {
if($Database != db_database()) {
$this->Database = db_database($Database))
if(empty($this->Database)) {
$this->halt("cannot use database " . $this->Database);
return 0;
}
}
}
return $this->Link_ID;
}
?>
请注意这些小地方的修改:
对于数据库的测试是在连接测试之外的,以便connect()可以检测一个新的数据库,甚至当已经存在一个当前连接的时候。这就是说,我们将db_connect()同0进行比较两次。这样的结果值得做一些小改动。
我们将数据库的连接与数据库的选择放在PHPLIB之外,这样在代码中需要进行数据库选择的地方我们可以使用相同的函数。
在这种情况下只有一个不好的地方:我们假设相同的主机,用户和口令,用于所有的数据库操作。如果你使用一个用户登录,使用特别的权限处理指定的数据库,你将不得不为这种处理创建一个特别的连接。怎么办呢?定义变量:
$db_host = "";
$db_user = "";
$db_pass = "";
?>
扩充db_database()函数,比较当前用户和主机名同特殊的用户和主机。你也可以加入:
$db_type = "";
?>
然后用它来保存数据库的类型,MySQL,Oracle,等等,这样你就可以处理多个数据库。
修改代码以便可以处理多个数据库要复杂一点。你需要修改查询函数,还包括连接与选择函数。你也许想阅读一下关于PHP的ODBC方式的连接,在PHPLIB中使用ODBC选项。ODBC可以以一种通用的方法处理很多的数据库,但可能有些慢。ODBC可以允许你使用相同的代码在多个类型的数据库上。如果你确实使用了多个数据库类型,可能会遇到对数据格式的要求不同的问题和不同数据库之间的差异的问题。ODBC简化了连接,但是没有完善数据库解释数据和SQL的方法。
现在开始关于派生对象类的简短教学。connect()函数被封装在类的定义中:
class DB_Sql {
}
?>
当我们拷贝这个函数到common.php3中时,我们需要派生DB_Sql类。我们通过封装connect()来实现:
class db_DB_Sql extends DB_Sql {
}
?>
可以查阅PHP的文档关于对象和类的内容,看一下"extends"做了些什么。用最少的话来说就是:在派生中定义的每个东西替换或覆盖了原始定义的东西。
现在可以使用db_DB_Sql了。当你安装好PHPLIB时,你会有一条语句,写为:
$x = new DB_Sql;
?>
将其改成:
$x = new db_DB_Sql;
?>
这样就会使用修改后的类,代替了原始的类。
你现在已经成了一个对象,类,OOP的专家了,可以要求每年薪水为,100了。(老外胆子够大)
我们做了一个有效的修改,而且对PHPLIB代码的影响最小。记录下修改的痕迹,这样你可以将其重用于PHPLIB的新版本中。如果在数据库处理中出现错误,你可以在外部的函数中插入print语句,看一看在连接时发生了什么。现在你可以做更多的事情了,而没有修改PHPLIB代码。
如果SQL看上去失败了,你可以将qurey()函数从db_mysql.inc中的DB_Sql中拷贝到common.PHP3中的db_DB_Sql中去,然后插入print语句,查看SQL的使用情况。
PHPLIB会记录cookie。在PHPLIB中间的一条print语句可能会产生错误消息,是关于输出HTTP头信息的问题。可以忽略错误,或者将诊断信息写到一个磁盘文件中去。
开始为:
$db_log_file = "t:/diag.txt";
或相似的语句,用来指向一个在磁盘某个地方的文件。在Windows下,要确信使用了一个存在的目录,否则你会得到一个奇怪的错误
现在定义:
function db_log($db_log_message) {
globals $db_log_file;
$db_log_f = fopen($db_log_file, "a");
fwrite($db_log_f, date("Y m d H:i:s")." ".$db_log_message."rn");
fclose($db_log_f);
}
?>
任何时候你需要查看发生了什么,象这样加入日志信息:
db_log("current database: " . db_database());
?>
你可以使用一些内建的日志技术和系统日志。使用系统日志时,可能因为没有处理正确的目录,从而可能要搜索大量的文件,却只为一点点信息。这种分离的日志可以在测试过程中向你提供一些控制。我建议在操作前后加入日志,象:
db_log("current database: " . db_database());
db_database("bookcatalogue");
db_log("current database: " . db_database());
?>
记住在你的数据库处理中使用正确的数据库,这样你就不用查询PHPLIB数据库了。你可能喜欢为数据库函数函数创建一个封装函数,或者修改你使用的函数。如果你使用mysql_query(),你可以首先使用db_database()。你也可以替换:
db_database("bookcatalogue");
$result = mysql_query("select * from?", db_connect());
?>
成
$result = mysql_db_query(db_database("bookcatalogue"), "select * from?",
db_connect());
?>
建议做成函数:
function db_query($db_query_database, $db_query_sql) {
return(mysql_db_query(db_database($db_query_database), $db_query_sql,
db_connect());
}
?>
现在你可以实现
使用PHPLIB(和任何相似的软件)处理多个数据库
扩充类/对象
插入诊断检查
将日志信息写入文件中
以相反的次序实践它们。在日志文件可工作后,然后是诊断检查,然后是对类的扩充,然后是多个数据库。