# 一次编写，随处运行

Write once - run anywhere

PEAR MDB Database Abstraction Layer
PEAR MDB 数据库抽象层

While this is a Java marketing phrase it is also a key feature of PHP. Many business models depend on operation system independence to ensure that products can be sold to a wide range of customers. So why lock yourself in with a specific database vendor? Database abstraction layers allow you to develop your application independent of a database. But often they eat more performance than you are willing to give or they do not abstract enough to eliminate all database specific code.

This article will give a good introduction to the database abstraction package PEAR MDB. The focus will be explaining the more advanced features of MDB like data type abstraction and the XML based schema management that go beyond what other similar packages offer. A basic level of understanding of PHP and SQL is recommended.

Why another database class?

Often, web projects are added to existing IT infrastructures, where the client already made a choice of what RDBMS (relational database management system) to work with. Even if that is not the case different budgets might affect what database you chose for deployment. Finally, you as the developer simply might prefer not to lock yourself in with a specific vendor. So far this meant to keep multiple versions for each supported database or giving up more performance and ease of use than necessary: Enter PEAR MDB.

MDB is a database abstraction layer that aims to make writing RDBMS independent PHP application development a straightforward process. Most other so called database abstraction layers for PHP only provide a common API for all supported databases and only very limited abstraction (mostly only for sequences). MDB on the other hand can be used to abstract all data being send and received from the database. Even database schemas can be defined in a RDBMS independent format. But it does this while retaining a high level of performance and ease of use. This was achieved by closely examining two popular database abstraction layers, PEAR DB and Metabase, and merging them. But during the merging the opportunity was also used to clean up their merged APIs as well as any performance hindering design patterns.
MDB 是着眼于使得编写 RDBMS 无关的 PHP 程序成为简单的过程的数据库抽象层。大部分其他的 PHP 的所谓数据库抽象层紧紧给所有支持的数据库提供了一个公用 API 以及非常有限的抽象（大部分只是针对序列的）。MDB 另一方面能够用来抽象所有数据库发送和接收的数据。甚至数据库 schema 都能被定义为 RDBMS 无关的格式。但是它提供这些功能的同时仍然保持了很高的性能以及简单易用。这是通过深入观察两个流行的数据库抽象层，PEAR DB 和 Metabase， 之后并且对它们进行了融合后获得的。而且在融合过程中，趁着这个机会清理了它们融合后的 API 以及任何影响性能的设计。

How did MDB come to be?
MDB 是怎样出现的？

Back in fall 2001, I was looking for a database abstraction package that would make my companies application framework RDBMS independent. The goal was to reduce database specific code to zero. The only package I found that offered such features was Metabase. But Metabase had a somewhat uncomfortable API that was partly a result of the compatibility to PHP3. This also made Metabase slower than it needed to be for our purposes, since we did not need PHP3 compatibility. Nonetheless, we decided that Metabase is our only option. But even after adding a performance enhancing patch to Metabase we felt that we were giving up too much performance. We met with the author of Metabase at the International PHP Conference 2001 and we talked about the benefits of having something like Metabase as part of the PEAR project. Shortly afterwards a discussion began once more in the PEAR mailing list about the potential benefits of a merge of PEAR DB and Metabase. After much discussion at my company we decided to take up this task. After several months of hard work we now have the first stable release of MDB.

What does MDB do for you?
MDB 给你提供了什么？

MDB combines most of the features of PEAR DB and Metabase. Actually, the only feature that is missing is PEAR DB's feature of returning an object as a result set. This feature was dropped because the feature's usage never became abundant but the performance penalty was quite apparent. A lot of development time was spend on making the API as intuitive as possible as well. Finally, MDB provides this functionality at a very high level of performance that is at least as fast as PEAR DB and much faster than Metabase. Here is the list of the most important features:
MDB 结合了 PEAR DB 和 Metabase 的大部分特性。实际上，PEAR DB 的特性中唯一不再存在的是作为结果集返回一个对象。我们放弃了这个特性是因为这个特性不常用而且对于性能的损失是非常明显的。许多开发上的时间用在了使得 API 尽可能的好用。最终，MDB 非常高地提供了这些功能至少和 PEAR DB 一样快而且比 Metabase 快很多。这些最重要地特性的列表：

OO-style API
prepared queries emulation
full data type abstraction for all data passed to and from the database (including LOB support)
transaction support
database/table/index/sequence creation/dropping/altering
RDBMS independent database schema management
Integrated into the PEAR framework (PEAR Installer, PEAR error handling etc.)

OO 风格的 API

RDBMS 无关的数据库 schema 管理

So how does it work?

MDB provides some very advanced abstraction features. It is important to keep in mind that these features are optional. But using them is critical in writing RDBMS independent PHP applications. An example of how the basics of MDB work can be found under Links & Literature at the end of the article. As stated earlier, the focus of the article is to introduce the features that set MDB apart from other database abstraction layers for PHP. You can find example scripts for all code examples found in this article on the CD that is packaged with this issue.
MDB 提供了一些非常先进的抽象特性。记住这些特性只是供选择的是很重要的。但是在编写 RDBMS 无关的 PHP 程序时使用它们是非常重要的。一个展示使用 MDB 是多么简单的例子在文章的结尾的 "链接和文献" 部分。如前面所说，文章的焦点是介绍使得 MDB 与其他 PHP 数据库抽象层不同的那些特性。你可以在随本期文章一同包装的 CD 中找到所有这些例子脚本的代码。

But first we will need to get MDB installed. This is actually quite easy using the PEAR installer. I cannot cover the entire PEAR Installer within this article but I hear the next issue will talk about great details about all the ins and outs of the PEAR framework. There is work going on to make the Installer work on Windows but the support is still a bit flaky. For *nix systems you will need a CGI version of PHP installed on your system and simply run the following command:

lynx -source go-pear.org|php

After completing the installation process you simply need to type one more command and you are all set.

pear install MDB

If the above does not work for you there is always the option of getting the package directly from the PEAR MDB homepage. The URL is listed at the bottom of the article.

Making use of data type abstraction

Since most databases tend to have some specialities or quirks it is important for MDB to hide these differences from the developer. MDB achieves this by defining its own internal data types: text, boolean, integer, decimal, float, date, time, time stamp, large objects (files). All data that is passed to and from the database may be converted from MDB's internal format to the databases internal format. The accompanying example scripts to this section can be found in the datatype directory. Let us look at the following query:

$session = '098f6bcd4621d373cade4e832627b4f6'; // set time out to 30 minutes$timeout = time()+60*30;
// SELECT query showing how the datatype conversion works
$query = 'SELECT createtime, user_id FROM sessions';$query .= ' WHERE session = '.$session;$query .= ' AND lastaccess < '.$timeout; This query will most likely fail if it were send to a database. The reason being that the value stored in$name would need to be converted to the correct string format. This would mean the contents of $name would have to have special characters escaped and quotes placed around. PEAR DB provides the method DB:.quote() for this. In MDB the method is called MDB::getTextValue(). The difference is that MDB offers such a method for every data type listed above. So we can also convert$timeout to the correct format.

$timeout = MDB_date::unix2Mdbstamp($timeout);
// SELECT query showing how the datatype conversion works
$query = 'SELECT createtime, user_id FROM sessions';$query .= ' WHERE session = '.$mdb->getTextValue($session);
$query .= ' AND lastaccess < '.$mdb->getTimestampValue($timeout); For the sake of the example let us assume that we only want to retrieve the first row. MDB::queryRow() fetches the first row, he frees the result set and returns the content, so it is exactly what we want. 为了作个演示，让我们假定我仅仅想要获取第一行。MDB::queryRow() 获得第一行，它释放结果集并且返回其内容，因而它正是我们所要的。$result = $mdb->queryRow($query);

But different RDBMS return data like dates in different formats. So, if I then want to do some date arithmetic it is important that data is always returned in the same format regardless of the RDBMS chosen. This can be done semi-automatically by MDB. All you need to do is tell MDB what type your result columns will have and MDB handles the conversion. The easiest way is to pass such information with the query method call:

$types = array('timestamp', 'integer');$result = $mdb->queryRow($query, $types); This tells MDB that the first column of the result set is of the type timestamp' and the second is of the type integer'. All methods that allow querying can take such meta-information as an optional parameter. The data can also be set later using MDB::setResultTypes(). Depending on the database that the data is retrieved from, it will then convert the returned data accordingly. The MDB internal data format for timestamps is the ISO 8601 standard. Other packages such as PEAR::Date can handle this format. MDB also provides a small number of methods for date format conversion in the MDB_Date class that can be included optionally. 这告诉 MDB 结果集的第一列类型是 'timestamp' 以及第二列是'integer'。所有查询函数能够接受这样的元信息作为可选的参数。数据还能事后用 MDB::setResultTypes() 来设置。取决于数据获取于的数据库，它然后将被相应的转换返回的数据。MDB 内部的 timestamps 的数据格式是遵循 ISO 8601 标准的。其他像 PEAR::Date 这样的包能够处理这种格式。MDB 还在 MDB_Date 类中提供了一些数据格式转换函数，它们能够被可选的包含。 Since pretty much every RDBMS returns integer data the same way there is no need to convert integer data. So, in order to gain a slight performance improvement you could do the following: 因为相当多的 RDBMS 以相同的方法返回整数数据，没有必要转换整数数据。因而，为了获得稍许的性能改进你能够这么做：$types = array('timestamp');
$result =$mdb->queryRow($query,$types);

This way only the first column of the result set would be converted. Of course this may become an issue if MDB would be used in conjunction with a database that does return integers differently. However unlikely, the slight performance increase might not be worth this risk. But again it shows that the usage of these features is optional.

Listing 1 shows an example use of prepared queries. These can be quite convenient if you have to run a number of queries where the only difference is in the data that is being passed to the database while the structure of the query remains the same. Advanced databases can store the parsed query in memory to offer a performance boost.
Listing 1 展示了一个使用预准备的查询的例子。如果你必须运行大量查询而唯一的差别是数据传递给数据库，但是查询的结构还是一样的，这些能够相当的方便。高级的数据库能够在内存中储存解析好的查询来加速性能。

Listing 1

$alldata = array( array(1, 'one', 'un'), array(2, 'two', 'deux'), array(3, 'three', 'trois'), array(4, 'four', 'quatre') );$p_query = $mdb->prepareQuery('INSERT INTO numbers VALUES (?,?,?)');$param_types = array('integer', 'text', 'text');

foreach ($alldata as$row) {
$mdb->execute($p_query, NULL, $row,$param_types);
}

Each of the 4 arrays that are stored in $alldata will be used in an execute statement. The data will automatically be converted to the correct format. Since this is an insert statement the second parameter for MDB::execute() is set to NULL because we will not have any result columns for which we would need to set data types. 在$alldata 中储存的所有四个数组将用于 execute 语句。数据将自动被转换为正确的格式。因为这是一个插入语句，MDB::execute() 的第二个参数被设置为 NULL 因为我们将没有任何结果列需要我们设置数据类型。

Among the supported data type are also LOB's (Large OBjects) which allow you to store files into a database. Binary files are stored in BLOBs (Binary Large OBject) and normal text files are stored on CLOBs (Character Large OBject). In MDB you can only store LOB's using prepared INSERT and UPDATE queries. Using either MDB::setParamBlob() or MDB::setParamClob() you can set the values of the LOB field in a prepared query. Both methods expect to be passed a LOB object however which can be created using MDB::createLob().

$binary_lob = array( 'Type' => 'inputfile', 'FileName' => './myfile.gif' );$blob = $mdb->createLob($binary_lob);

$character_lob = array( 'Type' => 'data', 'Data' => 'this would be a very long string container the CLOB data' );$clob = $mdb->createLob($character_lob);

As you can see MDB::createLob() is passed an associative array. The value for the Type key may be one of the following: data, inputfile or outputfile. The first two are used when you want to write a LOB into the database. If you have the LOB stored in a variable you should use data while inputfile should be used to read the LOB directly from a file. Finally, outputfile should be used when you want to retrieve a LOB from the database. Depending on if you are using data or inputfile you need to specify a value for the Filename key or the Data key as seen in the above example. Now, we will store the above LOB's in the database.

$p_query =$mdb->prepareQuery('INSERT INTO files (id, b_data, c_data) VALUES (1, ?, ?)');

$mdb->setParamBlob($p_query, 1 , $blob, 'b_data');$mdb->setParamClob($p_query, 2 ,$clob, 'c_data');

$result =$mdb->executeQuery($p_query); In order to fetch the above file from the database we will need to first select the data from the database and create a LOB object using MDB::createLob(). This time we will set Type' to outputfile'. 为了从数据库中获取上面的文件，我们需要首先从数据库中选择数据并且使用 MDB::createLob() 创建 LOB 对象。这次我们将设置 'Type' 为 'outputfile'$mdb->query('SELECT b_data FROM files WHERE id = 1');

$binary_lob = array( 'Type' => 'outputfile', 'Result' =>$result,
'Row' => 0,
'Field' => 'b_data',
'Binary' => 1,
'FileName' => './myfile2.gif'
);
$blob =$mdb->createLob($binary_lob); Now we can read the LOB from the result set using MDB::readLob(). Passing a length of 0 to MDB::readLob() means that the entire LOB is read and stored in the file we specified above. Once we are done we can free the resources. Alternatively, you can set any length larger than zero and read the LOB using a while loop checking MDB::endofLob(). 现在我们能够使用 MDB::readLob() 从结果集中读取 LOB。传递长度 0 给 MDB::readLob() 意味着整个 LOB 被读取和储存在我们前面指定的文件中。一旦任务完成了，我们可以把资源释放了。你也可以设置任何大于零的长度并且使用一个 while 循环检查 MDB::endofLob() 来读取 LOB。$mdb->readLob($blob,$data, 0);

It is important to note that you may not mix this method of fetching with the bulk fetching methods like MDB::fetchAll() as this will cause problems in most PHP database extensions. At some point MDB may be able to retrieve LOB's using the bulk fetching methods.

As we have seen in this section MDB features its own set of native data types that are automatically mapped to native data types in the database. This ensures that no matter what data we send or retrieve from the database it will always be in the same format no matter what RDBMS is used. As I have mentioned in the opening paragraph of this section this obviously requires that the data types used in the database are what MDB expects. This requirement was made to ensure that the mapping is done with a minimal performance loss. The next section will teach us how MDB assists with using the correct data types in the database.

Making use of XML schema files

With the features described in the last paragraph you can write truly database independent applications. But MDB tries to go one step further: It allows you to define your schemas in XML. A manager converts this schema into the necessary SQL statements for each RDBMS. This means that you can use the same schema for any of the supported RDBMS. The examples for this section can be found in the xml_schema directory.

We will now write an XML schema file from scratch. First we must define an XML document. The database definition is contained within a database tag. The name of the database is defined using the name tag. The create tag tells the manager if the database should be created if it does not yet exist. If you split up your schema into several files you will only need to set create to 1 in the file you will submit first to the manager.

<?xml version="1.0" encoding="ISO-8859-1" ?>
<database>
<name>auth</name>
<create>1</create>
</database>

As you may have guessed from the database name auth the purpose of this database is to store user data for a simple authentication application. Listing 2 defines a table in which we can store the user data.

Listing 2

<table>
<name>users</name>
<declaration>
<field>
<name>user_id</name>
<type>integer</type>
<notnull>1</notnull>
<unsigned>1</unsigned>
<default>0</default>
</field>
<field>
<name>handle</name>
<type>text</type>
<length>20</length>
<notnull>1</notnull>
<default></default>
</field>
<field>
<name>is_active</name>
<type>boolean</type>
<notnull>1</notnull>
<default>N</default>
</field>
</declaration>
</table>

As you can see, things can get a bit lengthy here as to be expected when using XML. No need to worry: We are working on a browser based tool called MDB_frontend that will make this process much easier. I will talk about this project further down into this article a bit more. Hopefully, the advantage of this pretty verbose representation of the table is that things are somewhat self explanatory. The table in the last example is called users and we have defined 3 fields: user_id of type integer, handle of type text and is_active of type boolean. Remember that MDB handles the type abstraction for you if you pass it the necessary metadata as shown in the previous section. You also need not to worry about what MDB maps these types to in your RDBMS. The other tags you can use in each of the field declarations are optional: length, notnull, unsigned and default.

The next thing that we now need to do is to ensure that the user_id is unique by placing the proper index on the user_id field. The index definition goes within the declaration tag (Listing 3).

Listing 3:

<table>
<name>users</name>
<declaration>
<index>
<unique>1</unique>
<name>user_id_index</name>
<field>
<name>user_id</name>
<sorting>ascending</sorting>
</field>
</index>
</declaration>
</table>

The definition in listing 3 would create a unique ascending index named user_id_index on the field user_id. Of course, we could have specified more than one field in the index definition by simply adding another field tag. What we are still missing now is a sequence to generate unique user id's for us:

<sequence>
<name>users_user_id</name>
<start>1</start>
<on>
<table>users</table>
<field>user_id</field>
</on>
</sequence>

The last example is pretty mind blowing. Going through line by line we see that we first open a sequence tag followed by a name tag which specifies the name of the sequence. This is followed by a start tag that defines the initial value of the sequence. Now, we open an optional on tag. Here we need to set a specific field within a table. This information is used by the manager to set the value of the sequence to the maximum value in the user_id field of the users table. If the users table is empty the value specified in the start tag is used instead. Please note that the value specified in the start tag is the first value that will be returned if you call MDB::nextId().

Of course, you can also initialize a table with any values. For example you may want to initialize the above table with a maintenance user that you always want to include with your application. To do this we need to add an initialization tag to the table tag. Listing 4 defines one row after another enclosed with an insert tag.

Listing 4

<table>
<name>users</name>
<initialization>
<insert>
<field>
<name>user_id</name>
<value>1</value>
</field>
<field>
<name>handle</name>
<value>default</value>
</field>
<field>
<name>is_active</name>
<value>Y</value>
</field>
</insert>
</initialization>
</table>

As you can see from the last example all we have to do is to define a value for each field of the table. We now have the necessary basics to create an XML schema for MDB. The next step is to pass this schema file to the MDB manager.

$manager = new MDB_Manager;$input_file = 'auth.schema';
// we do not have to connect to a specify a specific database at this time
$dsn = "mysql://$user:$pass@$host";
$manager->connect($dsn);
$manager->updateDatabase($input_file, $input_file. '.before'); We now have a new database called auth with a table called users. There is one index on the field user_id. There is one row in the table as well. We also have a sequence called users_user_id which will be initialized at 1. The next value in the sequence will therefore be 2. Finally, a copy of the schema was created with the name auth.schema.before. This happened because we passed the optional second parameter to MDB_Manager::updateDatabase(). In the next section we will see why this copy is created. 我们现在有了一个新的名字叫 auth 的数据库，它有一个表叫 users。在域 user_id 有一个索引。而且在表中还有一行。我们还有一个序列称为 users_user_id，它将被初始化为 1。因此序列中的下一个值就是 2。最后，schema 的一个拷贝以名字auth.schema.before 被创建。这是因为我们给 MDB_Manger::updateDatabase() 传递了可选的第二个参数。在下一节我们将看到为什么要创建这个拷贝。 This is all fairly amazing but it gets better. It is often the case an application needs to be changed at some point. For example we may decide we want to change the name of the table from users to people. We also want to add a field called pwd to store the password field (please check the textbox Reserved Words). 所有这些都非常令人惊奇但是它变得更好。许多情况下程序需要在某些地方作出改变。例如我们可能决定需要把表的名字从 users 变成 people。我们可能还需要增加一个域 pwd 来储存密码域（请检查 textbox 的保留字）。 Reserved Words 保留字 The reason we do not call the field password is that this is a reserved word for field names in Interbase. Since we want to be RDBMS independent the MDB manager will either issue a warning or fail if the option fail_on_invalid_names is set to true (which is the default). 我们没有称那个域为 password 的原因是那是 Interbase 中一个域名的保留字。因为我们需要 RDBMS 独立，MDB 管理器要么给出一个警告要么在 fail_on_invalid_names 选项被设置为真的时候（这是缺省值）失败。 In the old days you would now be in a bit of pain to alter all your existing installations to this new schema. But thanks to MDB this can be automated. In listing 5 are the changes we make to our table definition: 在过去的时候，你可能现在正处于把你所有已经有的东西变成这种新的 schema 的痛苦之中。但是由于 MDB 这些工作能够自动完成。在 listing 5 中是我们对我们的表格定义进行的修改： Listing 5 <table> <name>people</name> <was>users</was> <declaration> <field> <name>pwd</name> <type>text</type> <length>32</length> <notnull>1</notnull> <default></default> </field> </declaration> </table> Now we want the manager to make the necessary alterations, but before I want to mention a possible pitfall. Since we renamed the table users to people we also have to change all references to the old name like in the sequence we build. There the reference in the on tag needs to be changed to point to the people table. To achieve this we pass the new and the old version of the schema to the manager. This is why we created a .before file when we first called MDB_Manager::updateDatabase(). This ensures that we have an old version of the schema to compare the new version with. 现在我们想要管理器来作出必要的改变，但是在此之前我像提一下可能的陷阱。因为我们把表从 users 更名为 people，我们还需要把所有对原来名字的引用进行更改，比如我们建立的序列。在 on 标签中的索引需要更改为指向 people 表。为了达到这个目的，我们把 shcema 的新旧版本传递给管理器。这酒是为什么我们在第一次调用 MDB_Manager::updateDatabase() 时我们创建一个 .before 文件的原因。这确保了我们有一个旧版本的 shcema 来与新的版本进行比照。$input_file = 'auth.schema';
$manager->updateDatabase($input_file, $input_file.'.before'); That's all! The users table is now called people and now we also have a pwd field. 所有的就是这样！users 表现在称为 people 并且我们也有了一个 pwd 域。 I now want to look at one last feature of the XML schema format. This feature is especially important if you want to programmatically use the manager. Imagine that you have several customers that have the same authentication application running on your database server. Every customer has a database running on this server with the same schema but one minor difference: the name of the database. While it may be feasible to keep separate schema files for each client because the update cycles will not be the same this is not the case for our sample authentication application. Here all clients will be updated at the same time. The XML schema format allows us to use the variable tag for this. 我现在要看看 XML schema 格式的最后一个特性。如果你想要编程性的使用管理器，这个特性尤其重要。假设你有好几个有相同验证程序运行在你的数据库服务器的客户。 每个客户有一个服务器运行在这个服务器有相同的 schema 只有微小的区别：数据库的名字。可能为每个客户单独保存 schema 文件是可行的因为更新周期可能不是一样的，这不是我们例子验证程序的情况。这儿所有的客户同时更新。XML schema 文件允许我们为此可以使用变量。 <?xml version="1.0" encoding="ISO-8859-1" ?> <database> <name><variable>name</variable></name> </database> We can now set the variable name at run time to whatever we may need. 我们现在在运行时设置变量为任意我们需要的东西。 foreach($clients as $name) {$variables = array('name' => $name)$manager->updateDatabase($input_file,$input_file.'.before', $variables); } The XML schema management is another important piece in the database abstraction concept that MDB provides. It allows us to keep our schema definition independent of a specific RDBMS. But using this format also ensures that the correct native data types are used so that MDB can correctly map its native data types. Finally, since the format is based on XML it is much easier to write tools that generate or read XML schema files. XML schema 管理是 MDB 提供的数据库抽象概念的另外一个非常重要的部分。它使得我们保持我们的 schema 定义与特定的 RDBMS 无关。但是使用这个格式还确保了使用正确的原生数据类型因而 MDB 能够正确地映射它的原生数据类型。最后，因为数据是基于 XML 的，编写产生或者读取 XML schema 文件的工具要容易一些。 Sounds great but my application already uses ... 听起来不错但是我的应用程序已经使用了…… Most readers probably find themselves in a position where they already have a number of applications that run on some other database abstraction layer. Due to MDB's heritage most PEAR DB users should find that MDB feels very similar, since the API of MDB is based on that of PEAR DB. Metabase users should find that all their favourite functions have their counterpart in MDB. The XML schema format is exactly the same as in Metabase. A complete guide to porting your existing applications to MDB is beyond the scope of this article, instead I will use this space to give some tips. If you have any specific questions feel free to email me. 大部分读者可能发现它们处于这样的境地——他们已经有了大量运行于其他数据库抽象层的程序。由于 MDB 的出身，大部分 PEAR DB 的用户应当发现 MDB 感觉上非常类似，因为 MDB 的 API 是基于 PEAR DB 的。Metabase 用户应当发现他们所有偏爱的功能都在 MDB 中有对应的东西。XML schema 格式和 Metabase 中的是一摸一样的。一个完全的指导来引导你把已经写好的程序移植到 MDB 中超出了本文的范围，但是我将利用这个机会给一些提示。如果你有任何具体的问题，放心的发信来询问我。 To port your PEAR DB application to MDB the best place to start is the PEAR wrapper. For one you can run your application using the PEAR wrapper. The wrapper of course does add a little bit of overhead so you will probably want to port to the native interface at some point. The first step then should be listing all PEAR DB methods that your application currently uses. Then look at the wrapper for any differences in the API. There are two key differences you will notice: result sets are not objects anymore and all of the querying methods allow you to pass the data types of the result set which will result in slight changes in the parameter order. The first difference means that instead of calling the fetch method on the result object: 为了把你的 PEAR DB 程序移植到 MDB，最好的起点是 PEAR wrapper。你能使用 PEAR wrapper 来运行你的程序。wrapper 当然增加了一些额外负担，因而你可能有些想要移植到原生的接口。那么第一步是列出所有你程序当前使用的 PEAR DB 函数。然后看看 wrapper 从中找出任何 API 上的区别。有两个你要注意的关键区别：结果集不再是对象而且所有的允许你传递结果集的数据类型的查询方法将导致参数顺序上的少许改变。第一个区别意味着不能再结果对象上调用获取函数。$result = $db->query($sql);
$row =$result->fetchRow();

You will now have to call the MDB object for fetching:

$result =$mdb->query($sql);$row = $mdb->fetchRow($result);

The second difference is quite easily fixed by looking at the wrapper. As you can see in the wrapper you may simply pass NULL where MDB would otherwise expect data types in the result set. Now, your application should work with MDB. Of course, you are now not really taking advantage of the advanced features of MDB. This most likely will require some changes to your current database schema. The manager can attempt to reverse engineer an XML schema file from an existing database. A very simple front end can be found in the MDB package: the reverse_engineer_xml_schema.php script. Most likely you will need to manually fix the resulting XML schema file, but it will give you a nice starting point.

If you want to port your existing application from Metabase to MDB you will have to change all of your function calls. Looking at the Metabase wrapper it will become quite obvious what changes need to be made. If you know regular expressions well you might even be able to get most of the work done with a few such replacements. Anyways, you should be up and running your old beloved advanced abstraction features but now using MDB in no time. What you will probably notice is that the method names are much shorter now. If you do some benchmarking you will also see a nice performance increase.

So what does the future look like for MDB?

At the time this article publishes MDB will have moved on from the original 1.0 release. Next to the original MySQL and PostGreSQL drivers MDB will also have an ODBC driver and possibly even more drivers. This is one key area that is focused on during the development of MDB. Once MDB has caught up with PEAR DB in terms of drivers it is likely to become the standard database abstraction layer in the PEAR framework.

But there is another key area of development: the MDB_frontend project. The MDB_frontend will be a phpMyAdmin like webfrontend based on MDB and the MDB manager. With this tool you will be able to browse databases stored on any RDBMS that MDB supports. The MDB_frontend will show both the native and the MDB data types. Emulated features such as sequences in MySQL will be hidden. The user will simply see a list of sequences and not a table storing the value of the sequence which is how sequences are emulated in MySQL. Furthermore the MDB_frontend will assist in porting existing databases to match the native data types that MDB expects to be used. It will also help in creating and updating XML schema files. Some initial work has been completed but much more work is needed before a public release can be expected.

While drivers and the MDB_frontend are the focus of all development currently, there are other things that MDB users may need: Like the integration of bulk fetching of LOB fields, others may need foreign and primary key support. As always in opensource things will go faster if you participate in testing and implementation. But I am also thankful for any other feedback like feature requests.

Some final thoughts

After months of hard work MDB is gaining acceptance among the current PEAR DB and Metabase users. I also hope that people that so far have not been convinced by other database abstraction layers realize the benefits that MDB holds for them. Of course, there are still a lot of applications that need to be tailored specifically to one RDBMS where a tool like MDB just ads unnecessary overhead and restrictions. Overall I am very pleased that we made the decision in my company to lead the MDB development. In the beginning, we were all a bit worried that by attempting to please both the PEAR DB and Metabase users the result would end up pleasing neither side. Another source of concern was if the PHP community would assist in the development or not. I am very happy that the PHP community came through and helped in writing drivers and helping on the core of MDB as well. Therefore we consider this project to be a huge success. We are sure that together MDB will be improved even further. And we are happy to have helped making PHP even better.

Lukas Smith is the lead author of PEAR MDB. He actively contributes to various PHP opensource projects and is a founder of the company BackendMedia which specializes in PHP development.
Lukas Smith 是 PEAR DB 的主要作者。它积极地给多个 PHP 开远项目进行贡献并且是专注于 PHP 开发的 BackendMeida 公司的建立者。

PEAR MDB homepage: pear.php.net/package-info.php?package=MDB

PEAR MDB documentation: www.backendmedia.com/MDB/docs/

PEAR MDB sample script: cvs.php.net/co.php/pear/MDB/MDB_test.php

PEAR DB homepage: pear.php.net/package-info.php?package=DB

Metabase homepage: www.phpclasses.org/mirrors.html?page=%2Fbrowse.html%2Fpackage%2F20.html

Simple benchmark: freshmeat.net/screenshots/30313/

• 本文已收录于以下专栏：

## Metabase研究

• bin330720911
• 2018年02月06日 18:17
• 67

## SQL Server获取存储过程、表、自定义函数的最后修改时间

SQL Server获取存储过程、表、自定义函数的最后修改时间 select [name], create_date, modify_date, type_desc FROM sys.all...
• ghlfllz
• 2016年06月12日 22:28
• 1517

## [php]一次编写，随处运行

• Penlee
• 2005年01月25日 12:44
• 1086

## 1.2—Java之旅：一次编写，随处运行

﻿﻿ JAVA的特性和优势 ·简单性：就是c++语法的纯净版。没有头文件、指针运算、结构、联合、操作符重载、虚基类等等。由于语法基于c，因此学习起来完全不费力。 ·面向对象：面向对象是一...
• quanzhanjavabiji
• 2017年06月23日 14:17
• 190

## Unable to access the IIS metabase.You do not have sufficient privilege

• woniu1104913
• 2015年09月24日 09:57
• 1650

## 修改MetaBase.xml后不能保存的两种解决方

• ghlfllz
• 2016年06月08日 11:57
• 310

## Java程序为何“编译一次，到处运行”

• Lilibaobei1314
• 2014年06月26日 11:46
• 1974

## PEAR MDB 数据库抽象层 —— 一次编写—随处运行

Write once - run anywhere一次编写——随处运行这 是Java的一句行销口号，但是它同时也是PHP的关键特性之一。许多商业模型依赖于操作系统无关性来保证产品能够销售给广泛的客户群...
• jinci
• 2007年09月06日 23:12
• 358

## 一次编写，随处运行

• taowen2002
• 2004年02月04日 10:00
• 1990

## 常用的javascript function

function funCheckEmpty(m_Text) { var strTmp=m_Text if (strTmp == null || strTmp == "") { return fals...
• fzzf
• 2005年12月13日 14:31
• 1483

举报原因： 您举报文章：一次编写，随处运行 色情 政治 抄袭 广告 招聘 骂人 其他 (最多只允许输入30个字)