您可以想像,约束 getXML()
方法的输出,使其匹配某些约束条件非常容易。只需将合适的 WHERE 子句添加到 SQL 查询中即可。另外也可以使用 XPath 构造创建 XML 节点树的过滤子集并将其返回给调用方。
在清单 12 中,查看一个实现此功能的简单示例,修改 清单 11 并约束输出 XML 以便只列出欧洲的国家和城市,使用了一个 XPath 条件:
< ? php // include required files include 'XML/Query2XML.php' ; include 'MDB2.php' ; try { // initialize Query2XML object $ q2x = XML_Query2XML: : factory( MDB2: : factory( 'mysql://root:pass@localhost/world' ) ) ; // generate SQL query // get results as XML $ sql_1 = "SELECT * FROM Country" ; $ sql_2 = "SELECT * FROM City WHERE CountryCode = ? ORDER BY Population DESC LIMIT 5" ; $ xml = $ q2x - > getXML( $ sql_1 , array ( 'idColumn' = > 'code' , 'rootTag' = > 'countries' , 'rowTag' = > 'country' , 'attributes' = > array ( 'code' , 'name' , 'continent' ) , 'elements' = > array ( 'cities' = > array ( 'sql' = > array ( 'data' = > array ( 'code' ) , 'query' = > $ sql_2 ) , 'idColumn' = > 'id' , 'rootTag' = > 'cities' , 'rowTag' = > 'city' , 'attributes' = > array ( 'name' , 'district' , 'population' ) ) ) ) ) ; // now, further filter the XML using XPath // return only those nodes which have the attribute 'continent=Europe' // as a DOMNodeList $ xpath = new DOMXPath( $ xml ) ; $ nodelist = $ xpath - > query( "/countries/country[@continent='Europe']" ) ; // generate a new DOM tree using the XPath result set // create the root element // import each node from the node list and append to the new DOM tree $ dom = new DOMDocument; $ root = $ dom - > createElement( 'countries' ) ; $ dom - > appendChild( $ root ) ; $ x = 0; while ( $ node = $ nodelist - > item( $ x ) ) { $ node = $ dom - > importNode( $ node , true ) ; $ root - > appendChild( $ node ) ; $ x + + ; } // print XML header ( 'Content-Type: text/xml' ) ; $ dom - > formatOutput = true ; echo $ dom - > saveXML( ) ; } catch ( Exception $ e ) { echo $ e - > getMessage( ) ; } ? >
这段脚本的第一小段跟以前一样 — 两个嵌套的 SQL 查询,内部查询使用外部查询的数据生成一个国家和城市数据列表。但是此时,并没有立即打印 XML 或将其传递给 XSLT 处理器,而是初始化了 DOMXPath 对象,并从原始的 XML 树创建了一个新的 DOMNodeList。此 DOMNodeList 使用 XPath 查询以确保它只包含 continent
属性的值为 Europe
的
元素。一旦创建 DOMNodeList 之后,就会初始化新的 DOMDocument 并将此 DOMNodeList 一个节点接一个节点地导入其中,生成一个新的 XML 文档。
清单 13 给出了输出的一个片段:
< ? xml version = "1.0" ? > < countries> < country code= "NLD" name= "Netherlands" continent= "Europe" > < cities> < city name= "Amsterdam" district= "Noord-Holland" population= "731200" / > < city name= "Rotterdam" district= "Zuid-Holland" population= "593321" / > < city name= "Haag" district= "Zuid-Holland" population= "440900" / > < city name= "Utrecht" district= "Utrecht" population= "234323" / > < city name= "Eindhoven" district= "Noord-Brabant" population= "201843" / > < / cities> < / country> < country code= "ALB" name= "Albania" continent= "Europe" > < cities> < city name= "Tirana" district= "Tirana" population= "270000" / > < / cities> < / country> < country code= "AND" name= "Andorra" continent= "Europe" > < cities> < city name= "Andorra la Vella" district= "Andorra la Vella" population= "21189" / > < / cities> < / country> < country code= "BEL" name= "Belgium" continent= "Europe" > < cities> < city name= "Antwerpen" district= "Antwerpen" population= "446525" / > < city name= "Gent" district= "East Flanderi" population= "224180" / > < city name= "Charleroi" district= "Hainaut" population= "200827" / > < city name= "Liège" district= "Liège" population= "185639" / > < city name= "Bruxelles [Brussel]" district= "Bryssel" population= "133859" / > < / cities> < / country> . . . < countries>
在基于 XML 的应用程序的实际开发中,XML 文档中保存的信息不太可能来自一个源。除了一个或多个 SQL 结果集外,它还可能包含来自磁盘文件的数据、来自外部 Web 服务的数据,以及来自系统进程表的数据。为了应对这些情形,XML_Query2XML 提供了一种方法,用于将来自非 SQL 源的数据集成到 getXML()
方法返回的 XML 中。
XML_Query2XML 允许开发人员定义定制的回调函数,由输出 XML 中的特定元素调用。这些回调函数将在内部获得所需的数据,将其转换为 XML,并将此 XML(像 DOMNode 实例一样)返回给调用方,适合于在 XML 文档树中合适的位置插入。这些回调函数必须跟在 getXML()
调用中的散列(#
)符号之后,并将自动接收当前 SQL 记录作为输入。
您也许会问这是否真的有用。这个问题最好用示例回答。首先,假设您希望生成一个 XML 文档,其中列出一些国家及其人口最多的城市。您前面见过的许多示例都可以实现这个功能。为了让示例变得更有趣些,我们对此 XML 进行增强,使用 GeoNames Web 服务的数据给 XML 附上每个指定城市的经纬度。
清单 14 给出了代码:
< ? php ini_set ( 'max_execution_time' , 120) ; // include required files include 'XML/Query2XML.php' ; include 'MDB2.php' ; try { // initalize Query2XML object $ q2x = XML_Query2XML: : factory( MDB2: : factory( 'mysql://root:pass@localhost/world' ) ) ; // generate SQL query // get results as XML $ sql = "SELECT Country.Code2 AS code, Country.Name AS country, City.Name AS city, City.Population AS population FROM Country, City WHERE Country.Code = City.CountryCode GROUP BY City.CountryCode HAVING City.Population = MAX(City.Population) ORDER BY City.Population DESC LIMIT 15" ; $ xml = $ q2x - > getXML( $ sql , array ( 'idColumn' = > 'code' , 'rootTag' = > 'countries' , 'rowTag' = > 'country' , 'attributes' = > array ( 'code' , 'name' = > 'country' ) , 'elements' = > array ( 'city' = > array ( 'elements' = > array ( 'name' = > 'city' , 'population' , 'location' = > '#getLocation' ) , ) ) , ) ) ; // print XML header ( 'Content-Type: text/html' ) ; $ xml - > formatOutput = true ; print $ xml - > saveXML( ) ; } catch ( Exception $ e ) { echo $ e - > getMessage( ) ; } // function to get data from GeoNames Web service // call GeoNames with country code and city name // create XML document fragment with returned values function getLocation( $ record ) { // get data and format into SimpleXML object $ sxml = simplexml_load_string ( file_get_contents ( "" . urlencode ( utf8_encode ( $ record [ 'city' ] ) ) . "&country=" . urlencode ( utf8_encode ( $ record [ 'code' ] ) ) ) ) ; // extract data from SimpleXML object // convert into DOMNode fragments $ dom = new DOMDocument( ) ; // generate node $ lat = $ dom - > createElement( 'lat' ) ; $ lat - > appendChild( $ dom - > createTextNode( $ sxml - > geoname{ 0} - > lat) ) ; // generate node $ long = $ dom - > createElement( 'long' ) ; $ long - > appendChild( $ dom - > createTextNode( $ sxml - > geoname{ 0} - > lng) ) ; return array ( $ lat , $ long ) ; } ? >
在 清单 14 中,对 getXML()
的调用执行 SELECT 查询,该查询将各个城市按其国家代码分组,然后选择一个人口数量最多的城市。此数据然后被转换为下面的 XML 文档(清单 15):
< ? xml version = "1.0" encoding = "UTF-8" ? > < countries> < country code= "AW" name= "Aruba" > < city> < name> Oranjestad< / name> < population> 29034< / population> < / city> < / country> . . . < / countries>
下一个任务是获取每个城市的经纬度并将其插入到上面的文档树中(参见 清单 14 )。此信息来自 GeoNames Web 服务,可以通过 REST 访问此服务,并公开一个 search()
方法,用于返回指定位置的地理信息。对 Web 服务进行完整描述超出了本文的范围,但是您可以在本文参考资料部分阅读更多信息。
清单 16 显示了一个查询 'Berlin, Germany'
时的 GeoNames 响应包示例:
< ? xml version = "1.0" encoding = "UTF-8" standalone= "no" ? > < geonames> < totalResultsCount> 807< / totalResultsCount> < geoname> < name> Berlin< / name> < lat> 52. 5166667< / lat> < lng> 13. 4< / lng> < geonameId> 2950159< / geonameId> < countryCode> DE< / countryCode> < countryName> Germany< / countryName> < fcl> P< / fcl> < fcode> PPLC< / fcode> < / geoname> < / geonames>
正如您所看到的,这个响应包包含了关于指定位置的各种信息,包括我们最为关心的:经纬度。
现在,仔细查看 清单 13 中的 getXML()
调用。注意,选项数组的 location
键并没有链接到查询结果集中的字段,而是链接到了回调函数 getLocation()
。这意味着每次 getXML()
处理来自 SQL 结果集的记录时,它将同一条记录作为字段-值对的关联数组传递给 getLocation()
回调函数。getLocation()
方法反过来使用 REST 调用 GeoNames Web 服务的 search()
方法,将来自 SQL 记录的城市和国家名称作为参数传递给它,并作为 SimpleXML 对象搜索响应。SimpleXML 表示法然后可用于在响应包中寻找
和
元素,将它们转换为两个单独的 DOMNode 实例,并将它们传回 getXML()
作为数组插入树中。
最后,清单 17 给出了输出 XML 的外观:
< ? xml version = "1.0" encoding = "UTF-8" ? > < countries> < country code= "IN" name= "India" > < city> < name> Mumbai ( Bombay) < / name> < population> 10500000< / population> < location> < lat> 18. 975< / lat> < long> 72. 8258333< / long> < / location> < / city> < / country> < country code= "KR" name= "South Korea" > < city> < name> Seoul< / name> < population> 9981619< / population> < location> < lat> 37. 5663889< / lat> < long> 126. 9997222< / long> < / location> < / city> < / country> . . . < / countries>
正如此示例所阐明那样,使用定制的回调函数是一种将其他源的数据提取到 getXML()
生成的 XML 输出中的简单方法。清单 14 连接到外部 Web 服务;您可以同样轻松地将外部文件或 XML-RPC 调用的输出导入到最终的 XML 树中。
XML_Query2XML 的另外一个有用的应用是将数据库表的内容转储为基于 XML 的格式,用于存储和备份目的。这类备份脚本背后的逻辑很简单:从数据库取得一个表列表,迭代此列表并使用如 DESC ?
和 SELECT * FROM ? SQL
之类的命令分别提取每个表的模式和记录。如果您跟随本文的思路,可能已经想到需要使用 getXML()
方法调用以完成此任务。
执行此任务比最初预想的要困难一些,主要是由于 MDB2 抽象层有一些限制:即,在准备好的查询中不能处理列和表名的占位符。这让使用之前提到的 DESC ?
和 SELECT * FROM ?
查询非常困难,因为 MDB2 层在遇到此类查询时将只是生成错误。
那该怎么办?发挥一点创造力,如清单 18 所示:
< ? php ini_set ( 'max_execution_time' , 120) ; // include required files include 'XML/Query2XML.php' ; include 'MDB2.php' ; // set database name $ db = 'world' ; try { // initialize Query2XML object $ q2x = XML_Query2XML: : factory( MDB2: : factory( 'mysql://root:pass@localhost/' . $ db ) ) ; // SQL query to get table list // note: this SQL query varies from database to database $ sql = "SHOW TABLES" ; $ xml = $ q2x - > getXML( $ sql , array ( 'idColumn' = > false , 'rootTag' = > 'database' , 'rowTag' = > 'table' , 'attributes' = > array ( 'name' = > 'tables_in_' . $ db ) ) ) ; // get a list of all the nodes $ nodelist = $ xml - > getElementsByTagName( "table" ) ; // iterate over the nodes $ x = 0; while ( $ node = $ nodelist - > item( $ x ) ) { // extract the table name $ table = $ node - > attributes- > getNamedItem( 'name' ) - > nodeValue; // get table description // as DOM document // note: this SQL query varies from database to database $ sql_1 = 'DESC ' . $ table ; $ schema = $ q2x - > getXML( $ sql_1 , array ( 'idColumn' = > 'field' , 'rowTag' = > 'define' , 'rootTag' = > 'schema' , 'elements' = > array ( '*' ) ) ) ; // get table contents // as another DOM document $ sql_2 = 'SELECT * FROM ' . $ table ; $ data = $ q2x - > getXML( $ sql_2 , array ( 'idColumn' = > false , 'rowTag' = > 'record' , 'rootTag' = > 'data' , 'elements' = > array ( '*' ) ) ) ; // iterate over the $schema DOM document // use XPath to get the node and all its children // import it into the main XML tree, under the corresponding element // credit: Igor Kraus, for this suggestion $ xpath = new DOMXPath( $ schema ) ; $ query = $ xpath - > query( '//schema' ) ; for ( $ i = 0; $ i < $ query - > length; $ i + + ) { $ xml - > documentElement- > childNodes- > item( $ x ) - > appendChild( $ xml - > importNode( $ query - > item( $ i ) , true ) ) ; } // do the same for the $data DOM document $ xpath = new DOMXPath( $ data ) ; $ query = $ xpath - > query( '//data' ) ; for ( $ i = 0; $ i < $ query - > length; $ i + + ) { $ xml - > documentElement- > childNodes- > item( $ x ) - > appendChild( $ xml - > importNode( $ query - > item( $ i ) , true ) ) ; } // increment counter for the next run $ x + + ; } // write output to disk // print success/error message $ xml - > formatOutput = true ; if ( $ xml - > save( '/tmp/dump.xml' ) ) { echo 'Data successfully saved!' ; } else { echo 'Data could not be saved!' ; } } catch ( Exception $ e ) { echo $ e - > getMessage( ) ; } ? >
这看起来很复杂,但实际上很简单:
。获取此列表的 SQL 命令随数据库的不同而有所不同,清单 18 中的脚本使用 MySQL 的 SHOW TABLES
命令,但是这并不能在不同的 RDBMS 之间移植。如果使用不同的数据库系统,您可能需要更改命令。此命令的输出是 XML 文档,存储为 $xml
并且类似于清单 19:
< ? xml version = "1.0" encoding = "UTF-8" ? > < database> < table name= "City" / > < table name= "Country" / > < table name= "CountryLanguage" / > < / database>
2.下一步,使用 getElementsByTagName()
方法获取 前一步 中生成的 元素的集合,后面在一个循环中对其进行了处理,在循环的每次迭代中,创建了两个新的 XML 文档:$schema
包含了关于表的字段结构的信息(见 清单 20 ),$data
保存了表的实际记录(参见 清单 21 ):
< ? xml version = "1.0" encoding = "UTF-8" ? > < schema> < define> < field> ID< / field> < type> int( 11) < / type> < null> NO< / null> < key> PRI< / key> < default/ > < extra> auto_increment< / extra> < / define> < define> < field> Name< / field> < type> char( 35) < / type> < null> NO< / null> < key/ > < default/ > < extra/ > < / define> < define> . . . < / define> < / schema>
< ? xml version = "1.0" encoding = "UTF-8" ? > < data> < record> < id> 1< / id> < name> Kabul< / name> < countrycode> AFG< / countrycode> < district> Kabol< / district> < population> 1780000< / population> < / record> < record> < id> 2< / id> < name> Qandahar< / name> < countrycode> AFG< / countrycode> < district> Qandahar< / district> < population> 237500< / population> < / record> < record> . . . < / record> < / data>
3.继续进行该循环迭代,将两个独立的 XML 文档 $schema
和 $data
导入到父 XML 文档 $xml
中。 在前一示例中所见的 XPath 提供了一种简单的方法从 $schema
和 $xml
中提取 XML 节点碎片;DOM 扩展的 importNode()
方法处理其余文档,方法是外科手术般地将这些碎片插入到 XML 树主干的适当位置。
清单 22 显示了最终输出的一个片段:
< ? xml version = "1.0" encoding = "UTF-8" ? > < database> < table name= "City" > < schema> < define> < field> ID< / field> < type> int( 11) < / type> < null> NO< / null> < key> PRI< / key> < default/ > < extra> auto_increment< / extra> < / define> < define> < field> Name< / field> < type> char( 35) < / type> < null> NO< / null> < key/ > < default/ > < extra/ > < / define> . . . < / schema> < data> < record> < id> 1< / id> < name> Kabul< / name> < countrycode> AFG< / countrycode> < district> Kabol< / district> < population> 1780000< / population> < / record> < record> < id> 2< / id> < name> Qandahar< / name> < countrycode> AFG< / countrycode> < district> Qandahar< / district> < population> 237500< / population> < / record> . . . < / data> < / table> < table> . . . < / table> < / database>
清单 23 给出的是由 XML_Query2XML 类的开发者 Lukas Feiler 提供了一个更优雅的解决方案:
< ? php ini_set ( 'max_execution_time' , 120) ; // credit: Lukas Feiler, // include files require_once 'XML/Query2XML.php' ; require_once 'MDB2.php' ; // initialize MDB abstraction layer // load MDB manager $ mdb2 = MDB2: : factory( 'mysql://root:pass@localhost/world' ) ; $ mdb2 - > loadModule( 'Manager' ) ; // initialize Query2XML object $ q2x = XML_Query2XML: : factory( $ mdb2 ) ; // get table list $ tables = $ mdb2 - > listTables( ) ; // dynamically generate $options array // once for each table $ elements = array ( ) ; for ( $ i = 0; $ i < count ( $ tables ) ; $ i + + ) { $ elements [ 'table' . $ i ] = array ( 'rowTag' = > 'table' , 'attributes' = > array ( 'name' = > ':' . $ tables [ $ i ] ) , 'elements' = > array ( 'record' = > array ( 'idColumn' = > false , 'sql' = > 'SELECT * FROM ' . $ tables [ $ i ] , 'elements' = > array ( '*' ) ) ) ) ; } // get data from tables as XML $ xml = $ q2x - > getXML( false , array ( 'idColumn' = > false , 'rowTag' = > '__tables' , 'rootTag' = > 'database' , 'elements' = > $ elements ) ) ; // write output to disk // print success/error message $ xml - > formatOutput = true ; if ( $ xml - > save( '/tmp/dump.xml' ) ) { echo 'Data successfully saved!' ; } else { echo 'Data could not be saved!' ; } ? >
此解决方案首先加载 MDB2 Manager 模块,然后使用该模块的 listTables()
方法以一种数据库独立的方式检索数据库中所有表的列表。然后迭代此表列表,动态地针对每个迭代生成一个新的 elements
数组。一旦处理完所有的表,使用动态生成的 elements
数组调用 getXML()
,生成整个数据库的 XML 转储文件并将其写入磁盘。清单 24 给出了输出文件中的一个片段:
< ? xml version = "1.0" encoding = "UTF-8" ? > < database> < table name= "city" > < record> < id> 1< / id> < name> Kabul< / name> < countrycode> AFG< / countrycode> < district> Kabol< / district> < population> 1780000< / population> < / record> < record> < id> 2< / id> < name> Qandahar< / name> < countrycode> AFG< / countrycode> < district> Qandahar< / district> < population> 237500< / population> < / record> . . . < / table> < table name= "country" > < record> < code> AFG< / code> < name> Afghanistan< / name> < continent> Asia< / continent> < region> Southern and Central Asia< / region> < surfacearea> 652090. 00< / surfacearea> < indepyear> 1919< / indepyear> < population> 22720000< / population> < lifeexpectancy> 45. 9< / lifeexpectancy> < gnp> 5976. 00< / gnp> < gnpold/ > < localname> Afganistan/ Afqanestan< / localname> < governmentform> Islamic Emirate< / governmentform> < headofstate> Mohammad Omar< / headofstate> < capital> 1< / capital> < code2> AF< / code2> < / record> . . . < / table> < table> . . . < / table> < / database>
正如前面这些清单所演示的,XML_Query2XML 包可以实现的功能远不止仅将 SQL 结果集转换为 XML。它可以用作各种应用程序的启用程序,范围涵盖了从简单的 SQL-to-HTML 转换程序到利用各种输入源(包括 Web 服务、磁盘文件和多个数据库系统)创建复杂 XML 文档的各种工具。出于以上原因,值得将其添加到 PHP 开发人员的工具箱。下次当您需要在 PHP/XML 应用程序和 SQL 数据库之间创建接口时试试这个包,自己体验一下效果!
阅读(1769) | 评论(0) | 转发(0) |