Chinaunix首页 | 论坛 | 博客
  • 博客访问: 2453165
  • 博文数量: 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 数据库之间创建接口时试试这个包,自己体验一下效果!

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