假定您是一名新的 PHP 項目的工程團隊主管,並且要求必備的條件都已滿足,初步的數據模型包括大約 150 個表。現在該考慮一下進度表了。估計您每天能編寫一個數據庫訪問類並完成其單元測試,因此,150 個表要用 150 天的時間。假設每個月工作 20 天,是不是要用將近八個月的時間來編寫數據庫訪問層?
這樣做可能不行,需要有一種方法來縮短所需的時間。您可以使用持久性框架,但即使這種框架能夠把時間減半,仍然還需要四個月的時間。您還可以編寫通用的類庫,但是這樣的庫使用起來復雜不說,而且還非常容易出錯,難以調試。
也可以編寫一兩個示例類,然後使用代碼生成器建立其他的類。在最糟糕的情況下,編寫生成器本身就可能要用一個月的時間。但是此後,所有代碼的生成只要花費幾分鐘的時間。此外,由於 SQL 架構與數據庫訪問代碼不匹配造成的缺陷也可以完全避免,因為 SQL 架構和訪問代碼都是由生成器建立的。
這樣的計劃可行嗎?當然,本文就告訴您如何做,並說明為何應用程序更加容易維護,更加健壯,並且編寫起來更順手。本系列有兩篇文章,第 1 部分先介紹代碼生成的基礎,然後討論構建生成器的一部分,為給定的模型創建 SQL。第 2 部分將深入 XSL,通過遍歷創建 PHP 的代碼來說明如何完成生成器。
代碼生成基礎
首先,我們需要一個生成器,它接受數據庫的抽象模型並建立 SQL 架構和 PHP 數據庫訪問類。圖 1 給出了這種模型,並顯示了為其他技術(如 Java™ 代碼)生成數據庫代碼的可能性。
圖 1. 生成器的基本信息流
圖 1 中的虛線框(SQL 模型和數據庫訪問模型)表示從抽象模型創建的臨時模型。點線框(Java)表示另一種可能模型。
下一個問題是采用何種技術。您可以使用 Java 代碼、Perl、Python 或 Ruby 編寫生成器。因為這項任務比較簡單,也可以用 Velocity 或 XSLT 這樣的模板引擎來實現該任務。XSLT 2.0 這樣的模板引擎就是一個很好的起點,它不僅本身具有強大的功能,而且還可以嵌入到 Java 代碼中進一步擴展。
XSLT 2.0
XSLT 2.0 為尋求代碼生成模板引擎的開發人員提供了廣泛的語言能力。本文就利用這些特性構建一個健壯的生成器,從抽象的表定義生成 SQL 和 PHP。在 XSLT 1.x 中只能使用單層轉換,但是在 XSLT 2.0 中可以建立將要生成代碼的中間模型。這一功能使理解和維護生成器更加容易,而且可以將生成器重定位到其他目標語言。
作為一種 XML 轉換技術,XSLT 可以將 XML 轉換到其他 XML 或者文本。我們的這個代碼生成器項目中同時用到了這兩個工具,其中的 XSLT 2.0 對 XSLT 1.x 作了很多改進,本文將使用標准第二版中的三個新特性:
函數:現在可以自定義函數。對 XSLT 第一版而言,這是一個重大的改進,第一版中使用的模板語法太羅嗦。
中間樹:XSLT 的第一個版本只能對輸入的 XML 樹進行操作。XSLT 2.0 可以在內存中建立中間樹,然後用它派生出其他模板。
結果文檔:現在單個模板可以生成多個輸出文件。我利用該特性生成 SQL 文件和單獨的 PHP 文件。
下面將說明如何構建生成器。
構建生成器
首先從生成器的輸入開始,清單 1 為一個簡單的圖書數據庫提供了表定義的例子。
清單 1. 輸入的抽象表定義
<?XML version="1.0" encoding="UTF-8"?>
<tables>
<table name="Author">
<fIEld name="first" type="text"/>
<fIEld name="last" type="text"/>
</table>
<table name="Publisher">
<fIEld name="name" type="text"/>
<fIEld name="last" type="text"/>
</table>
<table name="Book">
<fIEld name="name" type="text"/>
<fIEld name="author" type="id"/>
<fIEld name="publisher" type="id"/>
</table>
</tables>
這段簡單的 XML 腳本定義了三個數據庫表及其字段:Author(作者)、Publisher(出版商)和 Book(圖書)。整個 PHP 應用程序的輸入要大得多,可能也復雜得多,但是作為起點來說,這段代碼挺合適。
您可以創建一個生成器一次性地將這些 XML 轉化成 SQL 和 PHP,但是我不建議這樣做。這樣模板會變得非常復雜而難以維護。目前代碼生成的最佳實踐是采用多級模型,每一級都朝目標前進一步。與 Web 服務器中使用多層降低復雜性增強重用性一樣,這樣做降低了一次轉換的復雜性。
回頭看一看 圖 1,就會發現抽象模型為 SQL 生成了一個模型,又通過數據庫訪問模型為 PHP 生成了另一個模型。這些模型都保存在中間樹中,然後用於建立代碼。圖 2 畫出了在生成器中轉化的 Author 表。
圖 2. 單個 SQL 表的演化
圖 2 中最上方的方框顯示了原來的抽象表模型。中間的方框則是從抽象模型構建的 SQL 模型。最下面的方框顯示了最後的 SQL 輸出。可以看到,SQL 模型詳細規定了什麼將出現在最終的 SQL 文件中。代碼模板的任務就是將 XML 轉化成 SQL,從而能夠從單一的模型為不同數據庫生成代碼。現在來看一看 XSL。
生成 SQL
清單 2 顯示了生成器項目中主 XSL 樣式表的第一部分。
清單 2. 主生成器 XSL 樣式表的開頭部分
<?XML version="1.0" encoding="UTF-8"?>
<xsl:stylesheet XMLns:xsl="http://www.w3.org/1999/XSL/Transform"
XMLns:gen="http://www.codegeneration.Net/" version="2.0">
<!-- Output specifications -->
<xsl:output method="text"/>
<xsl:output method="xml" name="debug-XML" indent="yes"/>
<xsl:output method="text" name="sql" indent="yes"/>
<!-- SQL Templates -->
<xsl:include href="gen2-sql.xsl" />
<xsl:include href="gen2-sql-model.xsl" />
<xsl:include href="gen2-querIEs.xsl" />
<!-- Database Access Templates -->
<xsl:include href="gen2-dba.xsl" />
<xsl:include href="gen2-PHP.xsl" />
<!-- The generator main entry point -->
<xsl:template match="/">
<!-- Create the SQL model -->
<xsl:text>Building SQL model
</xsl:text>
<xsl:variable name="sql-model">
<xsl:call-template name="gen-sql-model">
<xsl:with-param name="model" select="."/>
</xsl:call-template>
</xsl:variable>
<!-- Dump it out for debugging -->
<xsl:text>Dumping SQL model
</xsl:text>
<xsl:result-document href="db/gen-tables.xml" format="debug-XML">
<xsl:copy-of select="$sql-model"/>
</xsl:result-document>
<!-- Generate the SQL from the SQL model -->
<xsl:text>Generating SQL
</xsl:text>
<xsl:result-document href="db/gen-tables.sql" format="sql" >
<xsl:apply-templates mode="sql" select="$sql-model/sql" />
<xsl:result-document>
腳本中最重要的部分就是創建 sql-model 變量的地方,該變量將保存 SQL 模型;另一個最重要部分是 result-document 標簽,它使用 sql-model 變量的內容創建 SQL 文件。這些標簽顯示了 XSLT 2.0 的兩個有價值的改進:使用 xsl:variable 標簽創建中間樹,使用 xsl:result-document 標簽創建多個輸出文件。
現在來看看從抽象表模型生成 SQL 模型的模板。清單 3 顯示了 gen-sql-model 模板。
清單 3. SQL 模型生成器
<?XML version="1.0" encoding="UTF-8"?>
<xsl:stylesheet XMLns:xsl="http://www.w3.org/1999/XSL/Transform"
XMLns:gen="http://www.codegeneration.Net/" version="2.0">
<!-- Builds the SQL model from the original data model -->
<xsl:template name="gen-sql-model">
<xsl:param name="model"/>
<sql>
<xsl:for-each select="$model/tables/table">
<create name="{lower-case(@name)}"
primary-key="{concat(lower-case(@name),'_id')}">
<fIEld name="{concat(lower-case(@name),'_id')}"
type="{gen:model-type-to-sql('integer')}"/>
<xsl:for-each select="fIEld">
<fIEld name="{lower-case(@name)}"
type="{gen:model-type-to-sql(@type)}"/>
</xsl:for-each>
</create>
</xsl:for-each>
</sql>
</xsl:template>
</xsl:stylesheet>
這個模板相當簡單:接受源模型並為 SQL 模型添加一些新的標簽。注意 gen:model-type-to-sql 函數的調用。調用的不是標准 XSLT 函數,而是清單 4 中所示的自定義函數。
清單 4. model-to-SQL-type 轉換函數
<xsl:function name="gen:model-type-to-sql">
<xsl:param name="type"/>
<xsl:choose>
<xsl:when test="$type eq 'text'">TEXT NOT NULL</xsl:when>
<xsl:when test="$type eq 'id'">INTEGER NOT NULL</xsl:when>
<xsl:when test="$type eq 'integer'">INTEGER NOT NULL</xsl:when>
</xsl:choose>
</xsl:function>
創建新的 XPath 函數的能力是 XSLT 2.0 的新增功能。在以前,只能使用 xsl:call-template 語法,隨著參數的增加可能變得非常麻煩。使用模板作為函數也破壞了模板名稱空間。
生成 SQL 的最後一步是將 SQL 模型格式化為 SQL。可以使用清單 5 中所示的兩個模板。
清單 5. SQL 生成模板
<!-- Template for SQL create tags -->
<xsl:template match="create" mode="sql">
DROP TABLE IF EXISTS <xsl:value-of select="@name" />;
CREATE <xsl:value-of select="@name" /> (
<xsl:apply-templates mode="sql" select="fIEld" />
PRIMARY KEY ( <xsl:value-of select="@primary-key" /> )
);
</xsl:template>
<!-- Template for SQL fIEld tags -->
<xsl:template match="fIEld" mode="sql">
<xsl:value-of select="concat(@name,' ',@type,',')" /><xsl:text>
</xsl:text>
</xsl:template>
這些就是所有真正需要采用的模板。我有一個與 SQL 模型中的 create 標簽相對應的模板。該模板使用 apply-templates 標簽處理其子標簽,這些標簽是用第二個模板格式化的。
回顧一下 圖 2,要注意 XML 結構和 SQL 結構的相似性。create 標簽包含幾個字段標簽。與 SQL 類似,create 命令也包含幾個字段參數。按照與代碼相同的方式構造模型可以極大地簡化代碼模板。
結束語
本文介紹了 XSLT 2.0 中的一些新特性,即擴展的語言特性和為所要生成的代碼構建中間模型的能力,並示范了如何利用這些特性建立健壯的生成器,從抽象的表模型生成 SQL。
第 2 部分將說明如何生成代碼的 PHP 部分,從而為 Web 服務器提供數據庫訪問,還將介紹 XSLT 2.0 中的其他一些新特性。