Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2384054
  • 博文数量: 473
  • 博客积分: 12252
  • 博客等级: 上将
  • 技术积分: 4307
  • 用 户 组: 普通用户
  • 注册时间: 2007-10-12 10:02
文章分类

全部博文(473)

文章存档

2012年(8)

2011年(63)

2010年(73)

2009年(231)

2008年(98)

分类:

2010-01-29 13:38:08



您可以想像,约束 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();
}
?>


这看起来很复杂,但实际上很简单:

  1. 。获取此列表的 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>



digg 提交到 Digg
del.icio.us 发布到 del.icio.us
Slashdot 提交到 Slashdot!

正如前面这些清单所演示的,XML_Query2XML 包可以实现的功能远不止仅将 SQL 结果集转换为 XML。它可以用作各种应用程序的启用程序,范围涵盖了从简单的 SQL-to-HTML 转换程序到利用各种输入源(包括 Web 服务、磁盘文件和多个数据库系统)创建复杂 XML 文档的各种工具。出于以上原因,值得将其添加到 PHP 开发人员的工具箱。下次当您需要在 PHP/XML 应用程序和 SQL 数据库之间创建接口时试试这个包,自己体验一下效果!

阅读(1769) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~